How to use Postgres JSONB datatype with JPA?
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); } }}
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.