我的PostgreSQL DB(9.2)中有一个带有JSON类型列的表。我很难将此列映射到JPA2实体字段类型。
我尝试使用String但是当我保存实体时,我得到一个异常,它无法将字符转换为JSON。
处理JSON列时使用的正确值类型是什么?
@Entity
public class MyEntity {
private String jsonPayload; // this maps to a json column
public MyEntity() {
}
}
一个简单的解决方法是定义文本列。
答案 0 :(得分:74)
如果您有兴趣,可以使用以下几个代码片段来获取Hibernate自定义用户类型。首先扩展PostgreSQL方言,告诉它关于json类型,感谢Craig Ringer的JAVA_OBJECT指针:
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。下面的实现将String值映射到json数据库类型,反之亦然。记住字符串在Java中是不可变的。可以使用更复杂的实现将自定义Java bean映射到存储在数据库中的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;
}
}
现在剩下的就是注释实体。把这样的东西放在实体的类声明中:
@TypeDefs( {@TypeDef( name= "StringJsonObject", typeClass = StringJsonUserType.class)})
然后注释该属性:
@Type(type = "StringJsonObject")
public String getBar() {
return bar;
}
Hibernate将负责为您创建具有json类型的列,并来回处理映射。将其他库注入用户类型实现以获得更高级的映射。
如果有人想玩它,这是一个快速示例GitHub项目:
答案 1 :(得分:36)
对于数据类型转换,PostgreSQL过于严格,非常严格。它不会隐式地将text
投射到类似文本的值,例如xml
和json
。
解决此问题的严格正确方法是编写使用JDBC setObject
方法的自定义Hibernate映射类型。这可能有点麻烦,所以你可能只想通过创建一个较弱的演员来使PostgreSQL不那么严格。
正如@markdsievers在评论和this blog post中所指出的,本答案中的原始解决方案绕过了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指出了这个问题。
答案 2 :(得分:13)
正如我在this article中所解释的那样,使用Hibernate持久化JSON对象非常容易。
您不必手动创建所有这些类型,您可以简单地获取 它们通过Maven Central使用以下依赖项:
<dependency> <groupId>com.vladmihalcea</groupId> <artifactId>hibernate-types-52</artifactId> <version>${hibernate-types.version}</version> </dependency>
有关详细信息,请查看hibernate-types open-source project。
现在,解释它是如何运作的。
我写了关于如何在PostgreSQL和MySQL上映射JSON对象的an article。
对于PostgreSQL,您需要以二进制形式发送JSON对象:
public class JsonBinaryType
extends AbstractSingleColumnStandardBasicType<Object>
implements DynamicParameterizedType {
public JsonBinaryType() {
super(
JsonBinarySqlTypeDescriptor.INSTANCE,
new JsonTypeDescriptor()
);
}
public String getName() {
return "jsonb";
}
@Override
public void setParameterValues(Properties parameters) {
((JsonTypeDescriptor) getJavaTypeDescriptor())
.setParameterValues(parameters);
}
}
JsonBinarySqlTypeDescriptor
看起来像这样:
public class JsonBinarySqlTypeDescriptor
extends AbstractJsonSqlTypeDescriptor {
public static final JsonBinarySqlTypeDescriptor INSTANCE =
new JsonBinarySqlTypeDescriptor();
@Override
public <X> ValueBinder<X> getBinder(
final JavaTypeDescriptor<X> javaTypeDescriptor) {
return new BasicBinder<X>(javaTypeDescriptor, this) {
@Override
protected void doBind(
PreparedStatement st,
X value,
int index,
WrapperOptions options) throws SQLException {
st.setObject(index,
javaTypeDescriptor.unwrap(
value, JsonNode.class, options), getSqlType()
);
}
@Override
protected void doBind(
CallableStatement st,
X value,
String name,
WrapperOptions options)
throws SQLException {
st.setObject(name,
javaTypeDescriptor.unwrap(
value, JsonNode.class, options), getSqlType()
);
}
};
}
}
和JsonTypeDescriptor
是这样的:
public class JsonTypeDescriptor
extends AbstractTypeDescriptor<Object>
implements DynamicParameterizedType {
private Class<?> jsonObjectClass;
@Override
public void setParameterValues(Properties parameters) {
jsonObjectClass = ( (ParameterType) parameters.get( PARAMETER_TYPE ) )
.getReturnedClass();
}
public JsonTypeDescriptor() {
super( Object.class, new MutableMutabilityPlan<Object>() {
@Override
protected Object deepCopyNotNull(Object value) {
return JacksonUtil.clone(value);
}
});
}
@Override
public boolean areEqual(Object one, Object another) {
if ( one == another ) {
return true;
}
if ( one == null || another == null ) {
return false;
}
return JacksonUtil.toJsonNode(JacksonUtil.toString(one)).equals(
JacksonUtil.toJsonNode(JacksonUtil.toString(another)));
}
@Override
public String toString(Object value) {
return JacksonUtil.toString(value);
}
@Override
public Object fromString(String string) {
return JacksonUtil.fromString(string, jsonObjectClass);
}
@SuppressWarnings({ "unchecked" })
@Override
public <X> X unwrap(Object value, Class<X> type, WrapperOptions options) {
if ( value == null ) {
return null;
}
if ( String.class.isAssignableFrom( type ) ) {
return (X) toString(value);
}
if ( Object.class.isAssignableFrom( type ) ) {
return (X) JacksonUtil.toJsonNode(toString(value));
}
throw unknownUnwrap( type );
}
@Override
public <X> Object wrap(X value, WrapperOptions options) {
if ( value == null ) {
return null;
}
return fromString(value.toString());
}
}
现在,您需要在类级别或 package-info.java 包级别描述符中声明新类型:
@TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
实体映射将如下所示:
@Type(type = "jsonb")
@Column(columnDefinition = "json")
private Location location;
如果你正在使用Hibernate 5或更高版本,则JSON
类型为registered automatically by Postgre92Dialect。
否则,您需要自己注册:
public class PostgreSQLDialect extends PostgreSQL91Dialect {
public PostgreSQL92Dialect() {
super();
this.registerColumnType( Types.JAVA_OBJECT, "json" );
}
}
答案 3 :(得分:11)
如果有人感兴趣,您可以在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;
答案 4 :(得分:3)
我在Postgres(javax.persistence.PersistenceException:org.hibernate.MappingException:JDBC类型的无方言映射:1111)中遇到了类似的问题,当执行本机查询(通过EntityManager)时,虽然实体检索了投影中的json字段class已经使用TypeDefs进行了注释。 在HQL中翻译的相同查询没有任何问题。 要解决这个问题,我必须以这种方式修改JsonPostgreSQLDialect:
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回答)。
答案 5 :(得分:2)
我尝试了很多我在互联网上找到的方法,其中大多数都没有用,有些方法太复杂了。下面的代码对我有用,如果你对PostgreSQL类型验证没有严格的要求,那么它就更简单了。
将PostgreSQL jdbc字符串类型设置为未指定,如
<connection-url>
jdbc:postgresql://localhost:test?stringtype=unspecified
</connection-url>
答案 6 :(得分:1)
void remove_value_to_value_as_ring(std::list<int>& l, int val1, int val2)
{
auto it1 = std::find(l.begin(), l.end(), val1);
if (it1 == l.end()) {
return; // Not found
}
auto it2 = std::find(it1, l.end(), val2);
if (it2 != l.end()) {
l.erase(it1, it2);
return;
}
it2 = std::find(l.begin(), it1, val2);
if (it2 == it1) {
return; // Not found
}
l.erase(it1, l.end());
l.erase(l.begin(), std::next(it2));
}
答案 7 :(得分:1)
我遇到了这个问题,不想通过连接字符串启用内容,并允许隐式转换。最初,我尝试使用@Type,但是由于我使用的是自定义转换器来将Map序列化到JSON或从JSON反序列化,因此无法应用@Type注释。原来我只需要在@Column批注中指定columnDefinition =“ json”。
@Convert(converter = HashMapConverter.class)
@Column(name = "extra_fields", columnDefinition = "json")
private Map<String, String> extraFields;
答案 8 :(得分:1)
以上所有解决方案对我都不起作用。最后我使用原生查询来插入数据。
步骤-1 创建一个抽象类AbstractEntity,它将实现Persistable 带注释@MappedSuperclass(javax.persistence 的一部分) 第 -2 步在本课程中创建您的序列生成器,因为您无法使用本机查询生成序列器。 @Id @GeneratedValues @Column private Long seqid;
不要忘记 - 您的实体类应该扩展您的抽象类。 (帮助您的序列正常工作它也可以在日期上工作(检查日期我不确定))
Step- 3 在 repo 接口中编写原生查询。
value="INSERT INTO table(?,?)values(:?,:cast(:jsonString as json))",nativeQuery=true
步骤 - 4 这会将您的 java 字符串对象转换为 json 并在数据库中插入/存储,并且您还可以在每次插入时增加序列。
我在使用转换器工作时遇到了转换错误。我个人也避免在我的项目中使用 type-52。 如果对你们有用,请点赞我的回答。
答案 9 :(得分:0)