如何将Grails领域类属性配置为存储为(Postgres 9.4) jsonb?

6

我尝试配置一个域类,如下:

class Test {

    String data

    static constraints = {
    }

    static mapping = {
        data type: 'jsonb'
    }
}

这会抛出一个异常(最终的原因是“调用init方法失败;嵌套异常是org.hibernate.MappingException: Could not determine type for: jsonb, at table: test, for columns: [org.hibernate.mapping.Column(data)]”)。
我还尝试了“column: 'data', sqlType: 'jsonb'”,它创建了一个名为“data”的text列。
如何正确地告诉grails使用jsonb作为sql列类型?这是否可能?
(postgresql jdbc驱动程序与hibernate 4一起使用版本9.4-1200.jdbc4。)
4个回答

4

要配置域以将jsonb类型映射为String,您可以执行以下操作:

  1. Declare your own org.hibernate.usertype.UserType. Add to 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);
        }
    }
    
  2. After that you can simply declare mapping in the domain:

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

此外,您也可以将 jsonb 映射到 JSONObject 或您现有的类或接口,而不是 String。在这种情况下,GORM将负责序列化/反序列化 JSON,您无需在应用程序中显式执行它。 这里是一个这样的 UserType 实现示例


1
你可以使用Grails Postgresql Extensions插件在你的域类中使用一些Postgresql本地类型。
目前该插件支持Json,但不支持Jsonb类型。你可以在插件文档中获得更多关于json支持的信息。 免责声明:我是该插件的开发人员之一。

由于不支持jsonb,严格来说,这并没有回答我的问题(无意冒犯)。我知道postgresql-extensions;但是grails不需要对该字段中的数据进行任何操作,它可以将其内容视为文本。只需数据库知道它是jsonb即可。仍然感谢您的输入,谢谢。 - CONTRACT SAYS I'M RIGHT
如果您能添加一个功能列表,列出您的插件不支持但在postgres-sql中实现了的功能,那将非常好。例如:查询json列的子元素。 - kiltek

1
尽管我回答得很晚,但是我成功地用一种非常简单的方法实现了这一点,而且非常顺畅 -
我创建了一个实现了UserType的自定义Hibernate类型:
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"
    }
}

你好!


0
有点晚了,但为了记录,这是我对@jasp提供的解决方案的版本。不同之处在于,此解决方案将一个Map对象作为JSON格式持久化到文本列中。它使用了Grails包含的Jackson库。
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 文档存储。


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