Hibernate 如何将grails域类属性配置为存储为(postgres 9.4)jsonb?

Hibernate 如何将grails域类属性配置为存储为(postgres 9.4)jsonb?,hibernate,grails,gorm,postgresql-9.4,Hibernate,Grails,Gorm,Postgresql 9.4,我尝试过如下配置域类: class Test { String data static constraints = { } static mapping = { data type: 'jsonb' } } 这会引发异常(最终原因是调用init方法失败;嵌套异常为org.hibernate.MappingException:无法确定以下列的类型:[org.hibernate.mapping.Column(data)]) 我还尝试了c

我尝试过如下配置域类:

class Test {

    String data

    static constraints = {
    }

    static mapping = {
        data type: 'jsonb'
    }
}
这会引发异常(最终原因是调用init方法失败;嵌套异常为org.hibernate.MappingException:无法确定以下列的类型:[org.hibernate.mapping.Column(data)])

我还尝试了
column:'data',sqlType:'jsonb'
,它创建了一个名为
data
text

如何正确地告诉grails使用
jsonb
作为sql列类型?有可能吗

(带有hibernate 4的9.4-1200.jdbc4版本中使用了postgresql jdbc驱动程序。)

您可以使用该插件在域类中使用一些postgresql本机类型

目前,插件支持Json,但不支持Jsonb类型。您可以在中获得有关json支持的更多信息


免责声明:我是该插件的开发人员之一。

要配置域以将
jsonb
类型映射到
String
,您可以:

  • 声明自己的
    org.hibernate.usertype.usertype
    。添加到
    src/java

    public class JSONBType implements UserType {
    
        @Override
        public int[] sqlTypes() {
            return new int[] { Types.OTHER };
        }
    
        @SuppressWarnings("rawtypes")
        @Override
        public Class returnedClass() {
            return String.class;
        }
    
        @Override
        public boolean equals(Object x, Object y) throws HibernateException {
            return (x != null) && x.equals(y);
        }
    
        @Override
        public int hashCode(Object x) throws HibernateException {
            return x.hashCode();
        }
    
        @Override
        public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor sessionImplementor, Object owner)
            throws HibernateException, SQLException {
            return rs.getString(names[0]);
        }
    
        @Override
        public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor sessionImplementor)
            throws HibernateException, SQLException {
            st.setObject(index, value, (value == null) ? Types.NULL : Types.OTHER);
        }
    
        @Override
        public Object deepCopy(Object value) throws HibernateException {
            if (value == null) return null;
            return new String((String)value);
        }
    
        @Override
        public boolean isMutable() {
            return false;
        }
    
        @Override
        public Serializable disassemble(Object value) throws HibernateException {
            return (Serializable)value;
        }
    
        @Override
        public Object assemble(Serializable cached, Object owner)
            throws HibernateException {
            return cached;
        }
    
        @Override
        public Object replace(Object original, Object target, Object owner)
            throws HibernateException {
            return deepCopy(original);
        }
    }
    
  • 之后,您只需在域中声明映射:

    static mapping = {
        data type: "your.package.JSONBType", sqlType: "jsonb"
    }
    

  • 您还可以将
    jsonb
    映射到现有的类或接口,而不是
    String
    ,而是直接映射到
    JSONObject
    。在这种情况下,GORM将负责序列化/反序列化json,您不再需要在应用程序中显式地执行此操作

    虽然,我很晚才回答这个问题,但我还是用一种非常简单的方法实现了这一点,而且工作非常顺利-

    我创建了一个自定义Hibernate类型,它实现了
    UserType

    package com.wizpanda.hibernate
    
    import groovy.transform.CompileStatic
    import org.grails.web.json.JSONObject
    import org.hibernate.HibernateException
    import org.hibernate.engine.spi.SessionImplementor
    import org.hibernate.usertype.UserType
    
    import java.sql.PreparedStatement
    import java.sql.ResultSet
    import java.sql.SQLException
    import java.sql.Types
    
    /**
     * An implementation of {@link org.grails.web.json.JSONObject} column using Hibernate custom types.
     * https://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#_custom_type
     * https://docs.jboss.org/hibernate/orm/current/javadocs/org/hibernate/usertype/UserType.html
     *
     * @author Shashank Agrawal
     */
    @CompileStatic
    class JSONObjectFooType implements UserType {
    
        @Override
        int[] sqlTypes() {
            return [Types.OTHER] as int[]
        }
    
        //@SuppressWarnings("rawtypes")
        @Override
        Class returnedClass() {
            return JSONObject.class
        }
    
        @Override
        boolean equals(Object x, Object y) throws HibernateException {
            return x && x.equals(y)
        }
    
        @Override
        int hashCode(Object x) throws HibernateException {
            return x.hashCode()
        }
    
        @Override
        Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) throws HibernateException, SQLException {
            String value = rs.getString(names[0])
            if (!value) {
                return null
            }
    
            return new JSONObject(value)
        }
    
        @Override
        void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws HibernateException, SQLException {
            String valueToPersist
    
            if (value) {
                if (value instanceof JSONObject) {
                    valueToPersist = value.toString()
                } else if (value instanceof String) {
                    valueToPersist = new JSONObject(value).toString(0)
                } else {
                    throw new HibernateException("Unknown type received for JSONObject based column")
                }
            }
    
            st.setObject(index, valueToPersist, Types.OTHER)
        }
    
        @Override
        Object deepCopy(Object value) throws HibernateException {
            if (!value) {
                return null
            }
            if (value instanceof JSONObject) {
                return new JSONObject(value.toString(0))
            }
    
            return value
        }
    
        @Override
        boolean isMutable() {
            return false
        }
    
        @Override
        Serializable disassemble(Object value) throws HibernateException {
            if (value instanceof JSONObject) {
                return value?.toString(0)
            }
    
            return value?.toString()
        }
    
        @Override
        Object assemble(Serializable cached, Object owner) throws HibernateException {
            if (!cached) {
                return null
            }
    
            return new JSONObject(cached.toString())
        }
    
        @Override
        Object replace(Object original, Object target, Object owner) throws HibernateException {
            return deepCopy(original)
        }
    }
    
    我使用的是
    org.grails.web.json.JSONObject
    ,因为它是grails内部的,所以您可以使用其他类似
    org.json.JSONObject
    或Groovy json来替换上面的引用

    现在,在您的域类中简单地使用它-

    class User {
    
        String email
        String name
        JSONObject settings
    
        static mapping = {
            settings type: JSONObjectFooType, sqlType: "text"
        }
    }
    

    纳马斯特

    游戏有点晚了,但对于后代来说,这里是@jasp提供的解决方案的我的版本。不同之处在于,此解决方案将
    Map
    对象持久化为JSON格式的文本列。它使用了包括Jackson libs在内的grails

    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.hibernate.HibernateException;
    import org.hibernate.engine.spi.SharedSessionContractImplementor;
    import org.hibernate.usertype.UserType;
    
    import java.io.IOException;
    import java.io.Serializable;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.sql.Types;
    import java.util.Map;
    
    public class JSONStringType implements UserType {
        private static final ObjectMapper _mapper = new ObjectMapper();
    
        @Override
        public int[] sqlTypes() {
            return new int[] { Types.VARCHAR };
        }
    
        @SuppressWarnings("rawtypes")
        @Override
        public Class returnedClass() {
            return String.class;
        }
    
        @Override
        public boolean equals(Object x, Object y) throws HibernateException {
            return (x != null) && x.equals(y);
        }
    
        @Override
        public int hashCode(Object x) throws HibernateException {
            return x.hashCode();
        }
    
        @Override
        public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws HibernateException, SQLException {
            try {
                String val = rs.getString(names[0]);
                return _mapper.readValue(val, Map.class);
            } catch (IOException e) {
                throw new HibernateException(e);
            }
        }
    
        @Override
        public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws HibernateException, SQLException {
            try {
                String val;
                if (value == null)
                    val = "{}";
                else if (value instanceof String)
                    val = (String)value;
                else
                    val = _mapper.writeValueAsString(value);
                st.setObject(index, val, (value == null) ? Types.NULL : Types.VARCHAR);
            } catch (JsonProcessingException e) {
                throw new HibernateException(e);
            }
        }
    
        @Override
        public Object deepCopy(Object value) throws HibernateException {
            if (value == null) return null;
            try {
                String val = _mapper.writeValueAsString(value);
                return val;
            } catch (JsonProcessingException e) {
                throw new HibernateException(e);
            }
        }
    
        @Override
        public boolean isMutable() {
            return false;
        }
    
        @Override
        public Serializable disassemble(Object value) throws HibernateException {
            return (Serializable)value;
        }
    
        @Override
        public Object assemble(Serializable cached, Object owner)
            throws HibernateException {
            return cached;
        }
    
        @Override
        public Object replace(Object original, Object target, Object owner)
            throws HibernateException {
            return deepCopy(original);
        }
    }
    
    用法:

    import your.package.JSONStringType
    
    class Book {
        String name
        String isbn
        Map attributes = [:]
    
        static constraints = {
        }
    
        static mapping = {
            attributes type: JSONStringType, sqlType: 'nvarchar(4000)'
        }
    }
    

    更改
    sqlType
    以匹配数据库列类型。对于SQL Server,
    nvarchar(4000)
    用于高效的JSON文档查询,或者
    nvarchar(MAX)
    用于大型JSON文档存储。

    由于不支持jsonb,严格来说,这并没有回答我的问题(无意冒犯)。我知道postgresql扩展;但grails根本不需要对该字段中的数据做任何事情,它可以将其内容视为文本。只是数据库应该知道它是jsonb。我们仍然非常感谢您的输入。如果您添加了一个插件未涵盖但在postgres sql中实现的功能列表,那就太好了。例如:查询json列的子元素。