Im not finding a way to map the JSON and JSONB datatypes from PostgreSQL using JPA (EclipseLink). Is some one using this datatypes with JPA and can give me some working examples?
All the answers helped me to reach the final solution that is ready for JPA and not EclipseLink or Hibernate specifically.
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import javax.json.Json;
import javax.json.JsonObject;
import javax.persistence.Converter;
import org.postgresql.util.PGobject;
@Converter(autoApply = true)
public class JsonConverter implements javax.persistence.AttributeConverter<JsonObject, Object> {
private static final long serialVersionUID = 1L;
private static ObjectMapper mapper = new ObjectMapper();
@Override
public Object convertToDatabaseColumn(JsonObject objectValue) {
try {
PGobject out = new PGobject();
out.setType("json");
out.setValue(objectValue.toString());
return out;
} catch (Exception e) {
throw new IllegalArgumentException("Unable to serialize to json field ", e);
}
}
@Override
public JsonObject convertToEntityAttribute(Object dataValue) {
try {
if (dataValue instanceof PGobject && ((PGobject) dataValue).getType().equals("json")) {
return mapper.reader(new TypeReference<JsonObject>() {
}).readValue(((PGobject) dataValue).getValue());
}
return Json.createObjectBuilder().build();
} catch (IOException e) {
throw new IllegalArgumentException("Unable to deserialize to json field ", e);
}
}
}
AttributeConverter
that ends up with Object
is broken with Hibernate
. I just get an error saying "unknown jdbc type" or something like that. Shame really, since this approach is much prettier, with less boiler plate. –
Xochitlxp setPGobject
, which can only be called manually or if you use setObject
you have to specify the type to java.sql.Types.OTHER
, which I can't do. I modified the driver to send it there if unknown and the right class, only to run into ClassLoader conflicts. If I load the driver in the webapp I get conflict when loading, but not setting. If I load it in the tomcat/lib
directory, I get the conflict when setting. –
Singularize pom.xml
as compile (include in war) and deployed on Tomcat 8.5 (older versions don't search the war for jdbc drivers) without having it in it's lib
path, so it could only have found it in the war
. The classloader conflict occurred when the pgjdbc driver tried to persist an entity. Then I put it in as provided
scope, so it's not included in the war, and added the jar in tomcat/lib
. The conflict was when I read an entity. The problem is from Tomcat. It might work right under Glassfish or another server. –
Singularize org.hibernate.MappingException: No Dialect mapping for JDBC type: SOME_RANDOM_NUMBER
and apparently developers are not going to fix it. The proposed by them solution is using Generic Hibernate Types (heavy stuff) vladmihalcea.com/2016/06/20/… –
Qualitative Edit: I see now that this is pretty much Hibernate
dependent. But perhaps you can find something similar for EclipseLink
.
I'll just add what I have as an answer, it originates from another SO answer but whatever. This will map jsonb
to JsonObject
of Google gson
, but you can change it to something else if needed. To change to something else, change nullSafeGet
, nullSafeSet
and deepCopy
methods.
public class JsonbType implements UserType {
@Override
public int[] sqlTypes() {
return new int[] { Types.JAVA_OBJECT };
}
@Override
public Class<JsonObject> returnedClass() {
return JsonObject.class;
}
@Override
public boolean equals(final Object x, final Object y) {
if (x == y) {
return true;
}
if (x == null || y == null) {
return false;
}
return x.equals(y);
}
@Override
public int hashCode(final Object x) {
if (x == null) {
return 0;
}
return x.hashCode();
}
@Nullable
@Override
public Object nullSafeGet(final ResultSet rs,
final String[] names,
final SessionImplementor session,
final Object owner) throws SQLException {
final String json = rs.getString(names[0]);
if (json == null) {
return null;
}
final JsonParser jsonParser = new JsonParser();
return jsonParser.parse(json).getAsJsonObject();
}
@Override
public void nullSafeSet(final PreparedStatement st,
final Object value,
final int index,
final SessionImplementor session) throws SQLException {
if (value == null) {
st.setNull(index, Types.OTHER);
return;
}
st.setObject(index, value.toString(), Types.OTHER);
}
@Nullable
@Override
public Object deepCopy(@Nullable final Object value) {
if (value == null) {
return null;
}
final JsonParser jsonParser = new JsonParser();
return jsonParser.parse(value.toString()).getAsJsonObject();
}
@Override
public boolean isMutable() {
return true;
}
@Override
public Serializable disassemble(final Object value) {
final Object deepCopy = deepCopy(value);
if (!(deepCopy instanceof Serializable)) {
throw new SerializationException(
String.format("deepCopy of %s is not serializable", value), null);
}
return (Serializable) deepCopy;
}
@Nullable
@Override
public Object assemble(final Serializable cached, final Object owner) {
return deepCopy(cached);
}
@Nullable
@Override
public Object replace(final Object original, final Object target, final Object owner) {
return deepCopy(original);
}
}
To use this, do:
public class SomeEntity {
@Column(name = "jsonobject")
@Type(type = "com.myapp.JsonbType")
private JsonObject jsonObject;
In addition, you need to set your dialect to indicate that JAVA_OBJECT
= jsonb
:
registerColumnType(Types.JAVA_OBJECT, "jsonb");
I think I found an analogy to Hibernate's UserType for EclipseLink.
http://www.eclipse.org/eclipselink/documentation/2.6/jpa/extensions/annotations_ref.htm#CHDEHJEB
You have to make a class that implements org.eclipse.persistence.mappings.converters.Converter
and does the conversion for you, then use the @Convert
annotation on every field where you are using that type.
For anyone looking for a Mysql solution with the JSON column type, here it is. FWIW I am using EclipseLink but this is a pure JPA solution.
@Column(name = "JSON_DATA", columnDefinition="JSON")
@Convert(converter=JsonAttributeConverter.class)
private Object jsonData;
and
@Converter
public class JsonAttributeConverter implements AttributeConverter <Object, String>
{
private JsonbConfig cfg = new JsonbConfig().withFormatting(true);
private Jsonb jsonb = JsonbBuilder.create(cfg);
@Override
public String convertToDatabaseColumn(Object object)
{
if (object == null) return null;
return jsonb.toJson(object);
}
@Override
public Object convertToEntityAttribute(String value)
{
if (value == null) return null;
return jsonb.fromJson(value, value.getClass());
}
}
@Column(columnDefinition = "jsonb")
@JdbcTypeCode(SqlTypes.JSON)
private String columnYouSetAsJsonb; // Replace "columnYouSetAsJsonb" with the appropriate field/column name
This is the simplest way I have found to set a field in the Entity class. It works well for me for saving and querying.
I don't have much time to investigate how it is implemented. It would be nice if someone else explain how it works.
© 2022 - 2024 — McMap. All rights reserved.