Java 将PostgreSQL JSON列映射到Hibernate实体属性
在我的PostgreSQL DB(9.2)中有一个表,其列类型为JSON。我很难将此列映射到JPA2实体字段类型 我尝试使用字符串,但当我保存实体时,我得到一个异常,它无法将字符转换为JSON 处理JSON列时要使用的正确值类型是什么Java 将PostgreSQL JSON列映射到Hibernate实体属性,java,json,hibernate,postgresql,jpa,Java,Json,Hibernate,Postgresql,Jpa,在我的PostgreSQL DB(9.2)中有一个表,其列类型为JSON。我很难将此列映射到JPA2实体字段类型 我尝试使用字符串,但当我保存实体时,我得到一个异常,它无法将字符转换为JSON 处理JSON列时要使用的正确值类型是什么 @Entity public class MyEntity { private String jsonPayload; // this maps to a json column public MyEntity() { } } 一个简
@Entity
public class MyEntity {
private String jsonPayload; // this maps to a json column
public MyEntity() {
}
}
一个简单的解决方法是定义一个文本列
PostgreSQL对数据类型转换过于严格,令人烦恼。它不会隐式地将文本
转换为类似文本的值,例如xml
和json
解决此问题的严格正确方法是编写一个自定义Hibernate映射类型,该类型使用JDBCsetObject
方法。这可能有点麻烦,所以您可能只想通过创建一个较弱的强制转换来降低PostgreSQL的严格性
正如@markdsievers在注释和中指出的,这个答案中的原始解决方案绕过了JSON验证。所以这不是你真正想要的。更安全的做法是:
CREATE OR REPLACE FUNCTION json_intext(text) RETURNS json AS $$
SELECT json_in($1::cstring);
$$ LANGUAGE SQL IMMUTABLE;
CREATE CAST (text AS json) WITH FUNCTION json_intext(text) AS IMPLICIT;
AS IMPLICIT
告诉PostgreSQL它可以转换,而无需显式地告诉它,这样就可以工作:
regress=# CREATE TABLE jsontext(x json);
CREATE TABLE
regress=# PREPARE test(text) AS INSERT INTO jsontext(x) VALUES ($1);
PREPARE
regress=# EXECUTE test('{}')
INSERT 0 1
感谢@markdsievers指出这个问题。
PostgreSQL对数据类型转换过于严格,令人烦恼。它不会隐式地将文本
转换为类似文本的值,例如xml
和json
解决此问题的严格正确方法是编写一个自定义Hibernate映射类型,该类型使用JDBCsetObject
方法。这可能有点麻烦,所以您可能只想通过创建一个较弱的强制转换来降低PostgreSQL的严格性
正如@markdsievers在注释和中指出的,这个答案中的原始解决方案绕过了JSON验证。所以这不是你真正想要的。更安全的做法是:
CREATE OR REPLACE FUNCTION json_intext(text) RETURNS json AS $$
SELECT json_in($1::cstring);
$$ LANGUAGE SQL IMMUTABLE;
CREATE CAST (text AS json) WITH FUNCTION json_intext(text) AS IMPLICIT;
AS IMPLICIT
告诉PostgreSQL它可以转换,而无需显式地告诉它,这样就可以工作:
regress=# CREATE TABLE jsontext(x json);
CREATE TABLE
regress=# PREPARE test(text) AS INSERT INTO jsontext(x) VALUES ($1);
PREPARE
regress=# EXECUTE test('{}')
INSERT 0 1
感谢@markdsievers指出了这个问题。如果您有兴趣,这里有一些代码片段,可以帮助您正确地使用Hibernate自定义用户类型。首先扩展PostgreSQL方言,告诉它json类型,感谢Craig Ringer提供的JAVA_对象指针:
import org.hibernate.dialect.PostgreSQL9Dialect;
import java.sql.Types;
/**
* Wrap default PostgreSQL9Dialect with 'json' type.
*
* @author timfulmer
*/
public class JsonPostgreSQLDialect extends PostgreSQL9Dialect {
public JsonPostgreSQLDialect() {
super();
this.registerColumnType(Types.JAVA_OBJECT, "json");
}
}
接下来实现org.hibernate.usertype.usertype。下面的实现将字符串值映射到json数据库类型,反之亦然。请记住,字符串在Java中是不可变的。可以使用更复杂的实现将自定义JavaBean映射到存储在数据库中的JSON
package foo;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.usertype.UserType;
import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
/**
* @author timfulmer
*/
public class StringJsonUserType implements UserType {
/**
* Return the SQL type codes for the columns mapped by this type. The
* codes are defined on <tt>java.sql.Types</tt>.
*
* @return int[] the typecodes
* @see java.sql.Types
*/
@Override
public int[] sqlTypes() {
return new int[] { Types.JAVA_OBJECT};
}
/**
* The class returned by <tt>nullSafeGet()</tt>.
*
* @return Class
*/
@Override
public Class returnedClass() {
return String.class;
}
/**
* Compare two instances of the class mapped by this type for persistence "equality".
* Equality of the persistent state.
*
* @param x
* @param y
* @return boolean
*/
@Override
public boolean equals(Object x, Object y) throws HibernateException {
if( x== null){
return y== null;
}
return x.equals( y);
}
/**
* Get a hashcode for the instance, consistent with persistence "equality"
*/
@Override
public int hashCode(Object x) throws HibernateException {
return x.hashCode();
}
/**
* Retrieve an instance of the mapped class from a JDBC resultset. Implementors
* should handle possibility of null values.
*
* @param rs a JDBC result set
* @param names the column names
* @param session
* @param owner the containing entity @return Object
* @throws org.hibernate.HibernateException
*
* @throws java.sql.SQLException
*/
@Override
public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) throws HibernateException, SQLException {
if(rs.getString(names[0]) == null){
return null;
}
return rs.getString(names[0]);
}
/**
* Write an instance of the mapped class to a prepared statement. Implementors
* should handle possibility of null values. A multi-column type should be written
* to parameters starting from <tt>index</tt>.
*
* @param st a JDBC prepared statement
* @param value the object to write
* @param index statement parameter index
* @param session
* @throws org.hibernate.HibernateException
*
* @throws java.sql.SQLException
*/
@Override
public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws HibernateException, SQLException {
if (value == null) {
st.setNull(index, Types.OTHER);
return;
}
st.setObject(index, value, Types.OTHER);
}
/**
* Return a deep copy of the persistent state, stopping at entities and at
* collections. It is not necessary to copy immutable objects, or null
* values, in which case it is safe to simply return the argument.
*
* @param value the object to be cloned, which may be null
* @return Object a copy
*/
@Override
public Object deepCopy(Object value) throws HibernateException {
return value;
}
/**
* Are objects of this type mutable?
*
* @return boolean
*/
@Override
public boolean isMutable() {
return true;
}
/**
* Transform the object into its cacheable representation. At the very least this
* method should perform a deep copy if the type is mutable. That may not be enough
* for some implementations, however; for example, associations must be cached as
* identifier values. (optional operation)
*
* @param value the object to be cached
* @return a cachable representation of the object
* @throws org.hibernate.HibernateException
*
*/
@Override
public Serializable disassemble(Object value) throws HibernateException {
return (String)this.deepCopy( value);
}
/**
* Reconstruct an object from the cacheable representation. At the very least this
* method should perform a deep copy if the type is mutable. (optional operation)
*
* @param cached the object to be cached
* @param owner the owner of the cached object
* @return a reconstructed object from the cachable representation
* @throws org.hibernate.HibernateException
*
*/
@Override
public Object assemble(Serializable cached, Object owner) throws HibernateException {
return this.deepCopy( cached);
}
/**
* During merge, replace the existing (target) value in the entity we are merging to
* with a new (original) value from the detached entity we are merging. For immutable
* objects, or null values, it is safe to simply return the first parameter. For
* mutable objects, it is safe to return a copy of the first parameter. For objects
* with component values, it might make sense to recursively replace component values.
*
* @param original the value from the detached entity being merged
* @param target the value in the managed entity
* @return the value to be merged
*/
@Override
public Object replace(Object original, Object target, Object owner) throws HibernateException {
return original;
}
}
然后对属性进行注释:
@Type(type = "StringJsonObject")
public String getBar() {
return bar;
}
Hibernate将负责为您创建json类型的列,并处理来回映射。在用户类型实现中插入其他库以实现更高级的映射
如果有人想玩GitHub项目,这里有一个快速示例:
如果您感兴趣,这里有一些代码片段,可以让您正确地使用Hibernate自定义用户类型。首先扩展PostgreSQL方言,告诉它json类型,感谢Craig Ringer提供的JAVA_对象指针:
import org.hibernate.dialect.PostgreSQL9Dialect;
import java.sql.Types;
/**
* Wrap default PostgreSQL9Dialect with 'json' type.
*
* @author timfulmer
*/
public class JsonPostgreSQLDialect extends PostgreSQL9Dialect {
public JsonPostgreSQLDialect() {
super();
this.registerColumnType(Types.JAVA_OBJECT, "json");
}
}
接下来实现org.hibernate.usertype.usertype。下面的实现将字符串值映射到json数据库类型,反之亦然。请记住,字符串在Java中是不可变的。可以使用更复杂的实现将自定义JavaBean映射到存储在数据库中的JSON
package foo;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.usertype.UserType;
import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
/**
* @author timfulmer
*/
public class StringJsonUserType implements UserType {
/**
* Return the SQL type codes for the columns mapped by this type. The
* codes are defined on <tt>java.sql.Types</tt>.
*
* @return int[] the typecodes
* @see java.sql.Types
*/
@Override
public int[] sqlTypes() {
return new int[] { Types.JAVA_OBJECT};
}
/**
* The class returned by <tt>nullSafeGet()</tt>.
*
* @return Class
*/
@Override
public Class returnedClass() {
return String.class;
}
/**
* Compare two instances of the class mapped by this type for persistence "equality".
* Equality of the persistent state.
*
* @param x
* @param y
* @return boolean
*/
@Override
public boolean equals(Object x, Object y) throws HibernateException {
if( x== null){
return y== null;
}
return x.equals( y);
}
/**
* Get a hashcode for the instance, consistent with persistence "equality"
*/
@Override
public int hashCode(Object x) throws HibernateException {
return x.hashCode();
}
/**
* Retrieve an instance of the mapped class from a JDBC resultset. Implementors
* should handle possibility of null values.
*
* @param rs a JDBC result set
* @param names the column names
* @param session
* @param owner the containing entity @return Object
* @throws org.hibernate.HibernateException
*
* @throws java.sql.SQLException
*/
@Override
public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) throws HibernateException, SQLException {
if(rs.getString(names[0]) == null){
return null;
}
return rs.getString(names[0]);
}
/**
* Write an instance of the mapped class to a prepared statement. Implementors
* should handle possibility of null values. A multi-column type should be written
* to parameters starting from <tt>index</tt>.
*
* @param st a JDBC prepared statement
* @param value the object to write
* @param index statement parameter index
* @param session
* @throws org.hibernate.HibernateException
*
* @throws java.sql.SQLException
*/
@Override
public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws HibernateException, SQLException {
if (value == null) {
st.setNull(index, Types.OTHER);
return;
}
st.setObject(index, value, Types.OTHER);
}
/**
* Return a deep copy of the persistent state, stopping at entities and at
* collections. It is not necessary to copy immutable objects, or null
* values, in which case it is safe to simply return the argument.
*
* @param value the object to be cloned, which may be null
* @return Object a copy
*/
@Override
public Object deepCopy(Object value) throws HibernateException {
return value;
}
/**
* Are objects of this type mutable?
*
* @return boolean
*/
@Override
public boolean isMutable() {
return true;
}
/**
* Transform the object into its cacheable representation. At the very least this
* method should perform a deep copy if the type is mutable. That may not be enough
* for some implementations, however; for example, associations must be cached as
* identifier values. (optional operation)
*
* @param value the object to be cached
* @return a cachable representation of the object
* @throws org.hibernate.HibernateException
*
*/
@Override
public Serializable disassemble(Object value) throws HibernateException {
return (String)this.deepCopy( value);
}
/**
* Reconstruct an object from the cacheable representation. At the very least this
* method should perform a deep copy if the type is mutable. (optional operation)
*
* @param cached the object to be cached
* @param owner the owner of the cached object
* @return a reconstructed object from the cachable representation
* @throws org.hibernate.HibernateException
*
*/
@Override
public Object assemble(Serializable cached, Object owner) throws HibernateException {
return this.deepCopy( cached);
}
/**
* During merge, replace the existing (target) value in the entity we are merging to
* with a new (original) value from the detached entity we are merging. For immutable
* objects, or null values, it is safe to simply return the first parameter. For
* mutable objects, it is safe to return a copy of the first parameter. For objects
* with component values, it might make sense to recursively replace component values.
*
* @param original the value from the detached entity being merged
* @param target the value in the managed entity
* @return the value to be merged
*/
@Override
public Object replace(Object original, Object target, Object owner) throws HibernateException {
return original;
}
}
然后对属性进行注释:
@Type(type = "StringJsonObject")
public String getBar() {
return bar;
}
Hibernate将负责为您创建json类型的列,并处理来回映射。在用户类型实现中插入其他库以实现更高级的映射
如果有人想玩GitHub项目,这里有一个快速示例:
如果有人感兴趣,您可以将JPA 2.1
@Convert
/@Converter
功能与Hibernate一起使用。不过,您必须使用pgjdbc ng JDBC驱动程序。这样,您就不必为每个字段使用任何专有扩展、方言和自定义类型
@javax.persistence.Converter
public static class MyCustomConverter implements AttributeConverter<MuCustomClass, String> {
@Override
@NotNull
public String convertToDatabaseColumn(@NotNull MuCustomClass myCustomObject) {
...
}
@Override
@NotNull
public MuCustomClass convertToEntityAttribute(@NotNull String databaseDataAsJSONString) {
...
}
}
...
@Convert(converter = MyCustomConverter.class)
private MyCustomClass attribute;
@javax.persistence.Converter
公共静态类MyCustomConverter实现AttributeConverter{
@凌驾
@NotNull
公共字符串convertToDatabaseColumn(@NotNull MuCustomClass myCustomObject){
...
}
@凌驾
@NotNull
public MuCustomClass convertToEntityAttribute(@NotNull字符串databaseDataAsJSONString){
...
}
}
...
@Convert(converter=MyCustomConverter.class)
私有MyCustomClass属性;
如果有人感兴趣,您可以在Hibernate中使用JPA 2.1@Convert
/@Converter
功能。不过,您必须使用pgjdbc ng JDBC驱动程序。这样,您就不必为每个字段使用任何专有扩展、方言和自定义类型
@javax.persistence.Converter
public static class MyCustomConverter implements AttributeConverter<MuCustomClass, String> {
@Override
@NotNull
public String convertToDatabaseColumn(@NotNull MuCustomClass myCustomObject) {
...
}
@Override
@NotNull
public MuCustomClass convertToEntityAttribute(@NotNull String databaseDataAsJSONString) {
...
}
}
...
@Convert(converter = MyCustomConverter.class)
private MyCustomClass attribute;
@javax.persistence.Converter
公共静态类MyCustomConverter实现AttributeConverter{
@凌驾
@NotNull
公共字符串convertToDatabaseColumn(@NotNull MuCustomClass myCustomObject){
...
}
@凌驾
@NotNull
public MuCustomClass convertToEntityAttribute(@NotNull字符串databaseDataAsJSONString){
...
}
}
...
@Convert(converter=MyCustomConverter.class)
私有MyCustomClass属性;
我在执行本机查询(通过EntityManager)时遇到了与Postgres类似的问题(javax.persistence.PersistenceException:org.hibernate.MappingException:JDBC type:1111没有方言映射),该查询在投影中检索json字段,尽管实体类已用typedef注释。
在HQL中翻译的同一查询执行时没有出现任何问题。
为了解决这个问题,我必须以这种方式修改JSONPostgreSql方言:
public class JsonPostgreSQLDialect extends PostgreSQL9Dialect {
public JsonPostgreSQLDialect() {
super();
this.registerColumnType(Types.JAVA_OBJECT, "json");
this.registerHibernateType(Types.OTHER, "myCustomType.StringJsonUserType");
}
其中myCustomType.StringJsonUserType是实现json类型的类的类名(上面是Tim Fulmer的答案)。我在Postgres(javax.persistence.persistence)中遇到了类似的问题
@Convert(converter = HashMapConverter.class)
@Column(name = "extra_fields", columnDefinition = "json")
private Map<String, String> extraFields;