Created
April 29, 2018 15:49
-
-
Save iwalsh/2542acd4c37e512e8eb776e1eeebfd6e to your computer and use it in GitHub Desktop.
Hibernate UserType for marshalling Postgres jsonb columns <=> Jackson JsonNode objects
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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