Skip to content

Instantly share code, notes, and snippets.

@iwalsh
Created April 29, 2018 15:49
Show Gist options
  • Save iwalsh/2542acd4c37e512e8eb776e1eeebfd6e to your computer and use it in GitHub Desktop.
Save iwalsh/2542acd4c37e512e8eb776e1eeebfd6e to your computer and use it in GitHub Desktop.
Hibernate UserType for marshalling Postgres jsonb columns <=> Jackson JsonNode objects
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.google.common.base.Objects;
import java.io.IOException;
import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Properties;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.usertype.ParameterizedType;
import org.hibernate.usertype.UserType;
import org.postgresql.util.PGobject;
public class JSONBUserType implements UserType, ParameterizedType, Serializable {
private static final long serialVersionUID = 1;
private static final ObjectMapper mapper = new ObjectMapper().registerModule(new Jdk8Module());
private Class<?> classType;
@Override
public void setParameterValues(Properties params) {
String classTypeName = params.getProperty("classType");
try {
this.classType = ReflectHelper.classForName(classTypeName, this.getClass());
} catch (ClassNotFoundException e) {
throw new HibernateException("classType not found", e);
}
}
@Override
public Object assemble(Serializable cached, Object owner) throws HibernateException {
return this.deepCopy(cached);
}
@Override
public Object deepCopy(Object value) throws HibernateException {
try {
return (value == null) ? null : mapper.readValue(mapper.writeValueAsString(value), this.classType);
} catch (IOException e) {
throw new HibernateException("unable to deep copy object", e);
}
}
@Override
public Serializable disassemble(Object value) throws HibernateException {
try {
return mapper.writeValueAsString(value);
} catch (JsonProcessingException e) {
throw new HibernateException("unable to disassemble object", e);
}
}
@Override
public boolean equals(Object x, Object y) throws HibernateException {
return Objects.equal(x, y);
}
@Override
public int hashCode(Object x) throws HibernateException {
return Objects.hashCode(x);
}
@Override
public boolean isMutable() {
return true;
}
@Override
public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws HibernateException,
SQLException {
try {
String json = rs.getString(names[0]);
return (json == null) ? null : mapper.readValue(json, classType);
} catch (IOException e) {
throw new HibernateException("unable to read object from result set", e);
}
}
@Override
public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws HibernateException,
SQLException {
try {
String json = (value == null) ? null : mapper.writeValueAsString(value);
// N.B. Postgres-specific
PGobject pgo = new PGobject();
pgo.setType("jsonb");
pgo.setValue(json);
st.setObject(index, pgo);
} catch (JsonProcessingException e) {
throw new HibernateException(e);
}
}
@Override
public Object replace(Object original, Object target, Object owner) throws HibernateException {
return this.deepCopy(original);
}
@Override
public Class<?> returnedClass() {
return this.classType;
}
@Override
public int[] sqlTypes() {
return new int[]{Types.JAVA_OBJECT};
}
}
/* An example of using the new usertype in a Hibernate Entity class.
* I've omitted the imports and namespaces for brevity.
*
* In Postgres, each of these columns would have a type of `jsonb`.
*/
@Table(name = "sample_table", schema = "public")
@Entity
public class SampleEntity {
private ArrayNode arrayField;
private ObjectNode objectField;
private JsonNode genericField;
@Type(type = "JSONBUserType", parameters = { @Parameter(name = "classType", value = "ArrayNode") })
@Column(name = "json_array_field")
public ArrayNode getArrayField() {
return arrayField;
}
public void setArrayField(ArrayNode arrayField) {
this.arrayField = arrayField;
}
@Type(type = "JSONBUserType", parameters = { @Parameter(name = "classType", value = "ObjectNode") })
@Column(name = "json_object_field")
public ObjectNode getObjectField() {
return objectField;
}
public void setObjectField(ObjectNode objectField) {
this.objectField = objectField;
}
@Type(type = "JSONBUserType", parameters = { @Parameter(name = "classType", value = "JsonNode") })
@Column(name = "generic_json_field")
public JsonNode getGenericField() {
return genericField;
}
public void setGenericField(JsonNode genericField) {
this.genericField = genericField;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment