如何使用Hibernate映射PostgreSQL的JSON数据类型?

21

我正在按照下面URL中提到的示例进行操作?将PostgreSQL JSON列映射到Hibernate实体属性

但总是得到以下异常:

Caused by: org.hibernate.MappingException: No Dialect mapping for JDBC type: 2000
    at org.hibernate.dialect.TypeNames.get(TypeNames.java:76)
    at org.hibernate.dialect.TypeNames.get(TypeNames.java:99)
    at org.hibernate.dialect.Dialect.getTypeName(Dialect.java:310)
    at org.hibernate.mapping.Column.getSqlType(Column.java:226)
    at org.hibernate.mapping.Table.validateColumns(Table.java:369)
    at org.hibernate.cfg.Configuration.validateSchema(Configuration.java:1305)
    at org.hibernate.tool.hbm2ddl.SchemaValidator.validate(SchemaValidator.java:155)
    at org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:512)

我正在使用TomEE作为服务器,并尝试将Json主体存储到postgresql列中。我正在尝试将实体pojos映射到postgres数据类型结构。

有什么问题吗?或者是否有更好的技术来处理这种情况?请指向那个来源。

用于创建实体表的脚本如下:

    CREATE TABLE historyentity
    (
      id character varying(255) NOT NULL,
      userid character varying(255),
      lastchanged timestamp without time zone,
      type character varying(255),
      history json [],
      CONSTRAINT historyentity_pkey PRIMARY KEY (id),
      CONSTRAINT historyentity_userid_fkey FOREIGN KEY (userid)
          REFERENCES userentity (id) MATCH SIMPLE
          ON UPDATE NO ACTION ON DELETE NO ACTION
    )
    WITH (
      OIDS=FALSE
    );
    ALTER TABLE historyentity
      OWNER TO postgres;
    GRANT ALL ON TABLE historyentity TO postgres;

实体Pojo的样式如下:

    @Entity
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @TypeDefs({ @TypeDef(name = "StringJsonObject", typeClass = StringJsonUserType.class) })
    public class HistoryEntity {
    
        @Id
        private String id;
        private String userid;
        private String type;
        @Type(type = "StringJsonObject")
        private String history;
        private Date lastchanged;
    
    }

我正在使用Lombok来定义实体POJO。

下面是扩展方言类:

我尝试过注册类型Column和Hibernate,但两者都没有奏效。

    import org.hibernate.dialect.PostgreSQL82Dialect;
    
    public class JsonPostgreSQLDialect extends PostgreSQL82Dialect
    
        {
            @Inject
            public JsonPostgreSQLDialect()
            {
                super();
                   this.registerColumnType(Types.JAVA_OBJECT, "json");
                // this.registerHibernateType(Types.JAVA_OBJECT, "json");
            }
        }
以下类用于定义用户类型:
    import java.io.Serializable;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.sql.Types;
    
    import org.hibernate.HibernateException;
    import org.hibernate.engine.spi.SessionImplementor;
    import org.hibernate.usertype.UserType;
    
    
    public class StringJsonUserType implements UserType
    {
        private final int[] sqlTypesSupported = new int[]{ Types.JAVA_OBJECT };
    
        /**
         * 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 sqlTypesSupported;
        }
    
        /**
         * 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.
         *
         * @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 owner the containing entity  @return Object
         */
        @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
         */
        @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
         */
        @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
         */
        @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;
        }
    }

你更新了你的hibernate.properties文件吗?你在那里设置了正确的“方言”吗? - giaffa86
1
你读过基于此答案的评论了吗?来自oliverguenther:这个解决方案在Hibernate 4.2.7中运行良好,但是当从json列检索null时出现错误“No Dialect mapping for JDBC type: 1111”。然而,将以下行添加到方言类中即可修复它:this.registerHibernateType(Types.OTHER, "StringJsonUserType"); - António Ribeiro
1
看起来你在数据库中使用了 JSON 数组。因此,它应该被映射为 String[] 或类似的东西。 - asm0dey
你可能想阅读《如何使用通用Hibernate类型映射JSON对象》(https://vladmihalcea.com/2016/06/20/how-to-map-json-objects-using-generic-hibernate-types/)。 - Edwin Dalorzo
你可以阅读这个链接,我认为会对你有所帮助:https://dev59.com/xGQo5IYBdhLWcg3wivwi。 - user2987802
显示剩余3条评论
2个回答

2
在Hibernate的PostgreSQL92Dialect中添加了Postgres JSON类型。因此,您应该使用该方言或其子类之一,或创建一个自定义方言,以添加以下类型定义:
        this.registerColumnType(2000, "json");

类型本身可以定义如下(以 Hibernate 5.x 为例):

public class JsonType implements UserType {

    public static final ObjectMapper MAPPER = new ObjectMapper();

    private int[] sqlTypes;
    private com.fasterxml.jackson.databind.ObjectWriter writer;
    private JavaType type;
    private boolean isBinary;
    private ObjectReader reader;

    public JsonType() {
        init(SimpleType.constructUnsafe(Object.class), false);
    }

    public JsonType(Class clazz, boolean isBinary) {
        this(SimpleType.construct(clazz), isBinary);
    }

    public JsonType(JavaType type, boolean isBinary) {
        init(type, isBinary);
    }

    protected void init(JavaType type, boolean isBinary) {
        this.type = type;
        this.isBinary = isBinary;
        this.reader = MAPPER.readerFor(type);
        this.writer = MAPPER.writerFor(type);
        this.sqlTypes = new int[]{Types.JAVA_OBJECT};
    }


    public boolean equals(Object x, Object y) throws HibernateException {
        if (x == y) {
            return true;
        } else if (x == null || y == null) {
            return false;
        } else {
            return x.equals(y);
        }
    }

    public int hashCode(Object x) throws HibernateException {
        return null == x ? 0 : x.hashCode();
    }

    public boolean isMutable() {
        return true;
    }

    @Override
    public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws HibernateException, SQLException {
        final Object result = rs.getObject(names[0]);
        if (!rs.wasNull()) {
            String content;

            if (result instanceof String) {
                content = (String) result;
            } else if (result instanceof PGobject) {
                // If we get directly the PGobject for some reason (more exactly, if a DB like H2 does the serialization directly)
                content = ((PGobject) result).getValue();
            } else {
                throw new IllegalArgumentException("Unknown object type (excepted pgobject or json string)");
            }
            if (content != null) {
                return convertJsonToObject(content);
            }
        }
        return null;
    }

    @Override
    public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws HibernateException, SQLException {
        if (value == null) {
            st.setObject(index, null);
            return;
        }
        PGobject pg = new PGobject();
        pg.setType(isBinary ? "jsonb" : "json");
        pg.setValue(convertObjectToJson(value));
        st.setObject(index, pg);
    }


    Object convertJsonToObject(String content) {
        try {
            return reader.readValue(content);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    String convertObjectToJson(Object object) {
        try {
            return writer.writeValueAsString(object);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public Object deepCopy(Object value) throws HibernateException {
        String json = convertObjectToJson(value);
        return convertJsonToObject(json);
    }


    public Object replace(Object original, Object target, Object owner)
        throws HibernateException {
        return deepCopy(original);
    }


    public Serializable disassemble(Object value) throws HibernateException {
        return (Serializable) deepCopy(value);
    }


    public Object assemble(Serializable cached, Object owner)
        throws HibernateException {
        return deepCopy(cached);
    }


    public int[] sqlTypes() {
        return sqlTypes;
    }


    public Class returnedClass() {
        return type.getRawClass();
    }
}

这个例子使用Jackson作为JSON(反)序列化的框架。

然后,您可以按照以下方式使用您的类型:

@Entity
@TypeDefs({@TypeDef( name= "StringJsonObject", typeClass = JsonType.class)})
public class MyEntity {

    @Type(type = "StringJsonObject")
    @Column(name="visuals", columnDefinition = "json")
    private Map<String, String> visuals;

}

但这与您实现的类型非常相似(可能是为了Hibernate 4.x)。那么为什么您的实现不起作用呢?这是因为您的字段实际上是json[]类型(Postgres的JSON对象数组)。此映射器仅适用于JSON对象(类型json)。此JSON对象可以是JSON对象的JSON数组,但它必须是json类型。因此,您应该更改数据库架构中的类型,或者实现一个可以使用数组的UserType,但第一种选项最有可能。


2
这对我有用:
你的实体:
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@TypeDef(name = "json", typeClass = JSONUserType.class, parameters = {
        @Parameter(name = JSONUserType.CLASS, value = "java.lang.String")})
    public class HistoryEntity {

        @Id
        private String id;
        private String userid;
        private String type;
        @Type(type = "json")
        private String history;
        private Date lastchanged;

    }

实现Hibernate的ParameterizedType和UserType以确保两种类型(json <-> string)之间的转换。

public class JSONUserType implements ParameterizedType, UserType {

    private static final ObjectMapper objectMapper = new ObjectMapper();
    private static final ClassLoaderService classLoaderService = new ClassLoaderServiceImpl();

    public static final String JSON_TYPE = "json";
    public static final String CLASS = "CLASS";

    private Class jsonClassType;

    @Override
    public Class<Object> returnedClass() {
        return Object.class;
    }

    @Override
    public int[] sqlTypes() {
        return new int[]{Types.JAVA_OBJECT};
    }

    @Override
    public Object nullSafeGet(ResultSet resultSet, String[] names, SessionImplementor session, Object owner) throws HibernateException, SQLException {
        try {
            final String json = resultSet.getString(names[0]);
            return json == null ? null : objectMapper.readValue(json, jsonClassType);
        } catch (IOException e) {
            throw new HibernateException(e);
        }
    }

    @Override
    public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws HibernateException, SQLException {
        try {
            final String json = value == null ? null : objectMapper.writeValueAsString(value);
            PGobject pgo = new PGobject();
            pgo.setType(JSON_TYPE);
            pgo.setValue(json);
            st.setObject(index, pgo);
        } catch (JsonProcessingException e) {
            throw new HibernateException(e);
        }
    }

    @Override
    public void setParameterValues(Properties parameters) {
        final String clazz = (String) parameters.get(CLASS);
        jsonClassType = classLoaderService.classForName(clazz);
    }

    @SuppressWarnings("unchecked")
    @Override
    public Object deepCopy(Object value) throws HibernateException {

        if (!(value instanceof Collection)) {
            return value;
        }

        Collection<?> collection = (Collection) value;
        Collection collectionClone = CollectionFactory.newInstance(collection.getClass());

        collectionClone.addAll(collection.stream().map(this::deepCopy).collect(Collectors.toList()));

        return collectionClone;
    }

    static final class CollectionFactory {
        @SuppressWarnings("unchecked")
        static <E, T extends Collection<E>> T newInstance(Class<T> collectionClass) {
            if (List.class.isAssignableFrom(collectionClass)) {
                return (T) new ArrayList<E>();
            } else if (Set.class.isAssignableFrom(collectionClass)) {
                return (T) new HashSet<E>();
            } else {
                throw new IllegalArgumentException("Unsupported collection type : " + collectionClass);
            }
        }
    }

    @Override
    public boolean isMutable() {
        return true;
    }

    @Override
    public boolean equals(Object x, Object y) throws HibernateException {
        if (x == y) {
            return true;
        }

        if ((x == null) || (y == null)) {
            return false;
        }

        return x.equals(y);
    }

    @Override
    public int hashCode(Object x) throws HibernateException {
        assert (x != null);
        return x.hashCode();
    }

    @Override
    public Object assemble(Serializable cached, Object owner) throws HibernateException {
        return deepCopy(cached);
    }

    @Override
    public Serializable disassemble(Object value) throws HibernateException {
        Object deepCopy = deepCopy(value);

        if (!(deepCopy instanceof Serializable)) {
            throw new SerializationException(String.format("%s is not serializable class", value), null);
        }

        return (Serializable) deepCopy;
    }

    @Override
    public Object replace(Object original, Object target, Object owner) throws HibernateException {
        return deepCopy(original);
    }
}

并扩展PostgreSQL94Dialect类以告诉序列化器匹配的类型:
public class JSONPostgreSQLDialect extends PostgreSQL94Dialect {

    public JSONPostgreSQLDialect() {
        super();
        registerColumnType(Types.JAVA_OBJECT, JSONUserType.JSON_TYPE);
    }
}

如果您使用Spring,则必须在application.properties中声明此最后一个类,如下所示: spring.jpa.database-platform=com.yourpackage.JSONPostgreSQLDialect

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接