如何使用Hibernate映射postgresql json数据类型?

时间:2015-01-12 00:14:52

标签: json hibernate postgresql jpa tomee

我正在关注以下网址中提到的示例? Mapping postgreSQL JSON column to Hibernate value type

但总是得到以下例外:

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;

实体Pojos如下所示:

@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。

以下是Dialect扩展类: 我尝试了两种注册类型,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;
    }
}

2 个答案:

答案 0 :(得分:0)

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

答案 1 :(得分:0)

这项工作对我来说:

您的实体:

@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&lt; - &gt; 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平台= com.yourpackage.JSONPostgreSQLDialect