hibernate-reactive
hibernate-reactive copied to clipboard
Can't use hibernate6 json embeddable together with vertx-reactive -- expected String but got JsonObject
Vertx reactive driver return jsonb fields as a JsonObject, but hibernate only expects Strings, and seems theres no built-in user type to support vertx JsonObject There is JsonType in here, but to use new features the type should implement aggregate type which it doesn't
Which db are you using?
Hibernate Reactive recognizes the JsonObject as a type or you can use a converter.
Or, you can use the user type Json.
Sorry, we need to update the documentation with some examples
Please, let me know if this solve your problem
PostgreSQL 11 I can not use that unfortunately, just becuase I'm using new hibernate 6.2 feature when you can embed an object as json and make a query using their fields Here's the link https://docs.jboss.org/hibernate/orm/6.2/userguide/html_single/Hibernate_User_Guide.html#embeddable-mapping-aggregate
So my entities look like:
@Entity
class Foo {
@JdbcTypeCode(SqlTypes.JSON)
private EmbeddedObj embedded;
@Embeddable
public static class EmbeddedObj {
string bar;
}
}
And I also have a query like select f from Foo f where f.bar = 'baz'
The only I found is that SqlTypes.JSON maps to org.hibernate.dialect.PostgreSQLCastingJsonJdbcType and this class expects a string from resultSet (see org.hibernate.type.descriptor.jdbc.JsonJdbcType#getExtractor ) but vert.x driver puts JsonObject into RS.
As a work around I implemented custom JsonObject jsbc type which implements org.hibernate.type.descriptor.jdbc.AggregateJdbcType and by now it's ok to me, but I don't want to maintain this type and I guess that should be done somewhere in the box as it's done for basic type like here: https://github.com/hibernate/hibernate-reactive/pull/907/commits/1d1c56f3ce50a17bbc00da75b665157e93ba12ab
but for embeddable types as well
Thanks, it's an ORM new feature that we haven't implemented yet. But we will look into it
I'm looking for it.
By the way I could share what I have just implemented as I workaround, I'm sure that there are plenty of issues in the code but for now at least it work for me
import io.vertx.core.json.JsonObject;
import org.hibernate.dialect.PostgreSQLCastingJsonJdbcType;
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.ValueExtractor;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.AggregateJdbcType;
import org.hibernate.type.descriptor.jdbc.BasicBinder;
import org.hibernate.type.descriptor.jdbc.BasicExtractor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
public class JsonObjectJdbcType extends PostgreSQLCastingJsonJdbcType {
private final EmbeddableMappingType embeddableMappingType;
public JsonObjectJdbcType() {
this(true, null);
}
public JsonObjectJdbcType(boolean jsonb, EmbeddableMappingType embeddableMappingType) {
super(true, embeddableMappingType);
this.embeddableMappingType = embeddableMappingType;
}
@SuppressWarnings("unchecked")
protected <X> X fromJsonObject(JsonObject obj, JavaType<X> javaType, WrapperOptions options) {
if (obj == null) {
return null;
}
if (embeddableMappingType != null) {
//org.hibernate.sql.results.graph.embeddable.internal.NestedRowProcessingState#getJdbcValue wants to get array of field values for some reason
return (X) Arrays.stream(embeddableMappingType.getJavaType().getJavaTypeClass().getDeclaredFields())
.filter(field -> !Modifier.isTransient(field.getModifiers()))
.map(Field::getName)
.map(obj::getValue)
.toArray(Object[]::new);
}
return obj.mapTo(javaType.getJavaTypeClass());
}
protected <X> JsonObject toJsonObject(X value, JavaType<X> javaType, WrapperOptions options) {
if (value == null) {
return null;
}
return JsonObject.mapFrom(value);
}
@Override
public AggregateJdbcType resolveAggregateJdbcType(
EmbeddableMappingType mappingType,
String sqlType,
RuntimeModelCreationContext creationContext) {
return new JsonObjectJdbcType(true, mappingType);
}
@Override
public <X> ValueBinder<X> getBinder(JavaType<X> javaType) {
return new BasicBinder<>(javaType, this) {
@Override
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException {
final JsonObject json = ((JsonObjectJdbcType) getJdbcType()).toJsonObject(value, getJavaType(), options);
st.setObject(index, json);
}
@Override
protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) throws SQLException {
final JsonObject json = ((JsonObjectJdbcType) getJdbcType()).toJsonObject(value, getJavaType(), options);
st.setObject(name, json);
}
};
}
@Override
public <X> ValueExtractor<X> getExtractor(JavaType<X> javaType) {
return new BasicExtractor<>(javaType, this) {
@Override
protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException {
return fromJsonObject(rs.getObject(paramIndex, JsonObject.class), getJavaType(), options);
}
@Override
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
return fromJsonObject(statement.getObject(index, JsonObject.class), getJavaType(), options);
}
@Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
return fromJsonObject(statement.getObject(name, JsonObject.class), getJavaType(), options);
}
};
}
}
And I just register the type for package
@JdbcTypeRegistration(registrationCode = SqlTypes.JSON, value = JsonObjectJdbcType.class)
package com.example.dan1els.entity;
import org.hibernate.annotations.JdbcTypeRegistration;
import org.hibernate.type.SqlTypes;
import com.example.dan1els.misc.JsonObjectJdbcType;
Maybe it would help