【问题标题】:How can I map postgresql json data type using Hibernate?如何使用 Hibernate 映射 postgresql json 数据类型?
【发布时间】:2015-03-09 17:26:37
【问题描述】:

我正在按照以下 URL 中提到的示例进行操作? Mapping PostgreSQL JSON column to a Hibernate entity property

但总是得到以下异常:

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;

Entity 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 来定义实体 pojos。

以下是方言扩展类: 我已经尝试过注册类型 Column 和 Hibenate。但两者都没有成功。

    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;
        }
    }

【问题讨论】:

  • 您如何尝试将实体 pojos 映射到 postgres 数据类型结构?可以展示一段代码和配置代码吗?
  • 我刚刚描述了代码和postgres结构。希望对您有所帮助?
  • 你更新你的hibernate.properties了吗?您是否设置了正确的“方言”?
  • 您是否阅读过关于您所基于的答案的 cmets?来自 oliverguenther:此解决方案与 Hibernate 4.2.7 配合得很好,除非从 json 列中检索 null 时出现错误“No Dialect mapping for JDBC type: 1111”。但是,将以下行添加到方言类修复了它: this.registerHibernateType(Types.OTHER, "StringJsonUserType");
  • 看起来您在 db 中使用了 json 数组。所以它应该被映射到 String[] 或类似的东西。

标签: json hibernate postgresql jpa apache-tomee


【解决方案1】:

这对我有用:

您的实体:

@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,保证2种类型(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

【讨论】:

    【解决方案2】:

    Postgres JSON 类型已在 PostgreSQL92Dialect 中添加到 Hibernate。因此,您应该使用该方言或其子类之一,或者制作添加以下类型定义的自定义方言:

            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[] 类型(JSON 对象的 Postgres 数组)。此映射器仅适用于 JSON 对象(类型 json)。这个 JSON 对象很可能是 JSON 对象的 JSON 数组,但它必须是 json 类型。所以你应该改变你的数据库模式中的类型,或者实现一个可以处理数组的 UserType,但是第一个选项是最有可能的。

    【讨论】:

      猜你喜欢
      • 2010-12-11
      • 2010-12-29
      • 2011-06-26
      • 2012-12-23
      • 2019-12-10
      • 1970-01-01
      • 2020-09-14
      • 2010-11-09
      • 1970-01-01
      相关资源
      最近更新 更多