5

我试图像这样配置一个域类:

class Test {

    String data

    static constraints = {
    }

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

这会引发异常(最终的原因是Invocation of init method failed; nested exception is org.hibernate.MappingException: Could not determine type for: jsonb, at table: test, for columns: [org.hibernate.mapping.Column(data)])。

我也试过column: 'data', sqlType: 'jsonb'了,它创建了一个text名为data.

如何正确告诉 grailsjsonb用作 sql 列类型?有可能吗?

(postgresql jdbc 驱动程序用于版本 9.4-1200.jdbc4 和 hibernate 4。)

4

4 回答 4

4

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

  1. 声明你自己的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);
        }
    }
    
  2. 之后,您可以简单地在域中声明映射:

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

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

于 2015-02-22T07:52:33.823 回答
1

您可以使用Grails Postgresql Extensions插件在您的域类中使用一些 Postgresql 本机类型。

目前该插件支持 Json 但不支持 Jsonb 类型。您在插件文档中有更多关于 json 支持的信息

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

于 2015-02-15T11:05:40.130 回答
0

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

我创建了一个自定义的 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"
    }
}

合十!

于 2020-05-23T18:51:50.310 回答
0

游戏有点晚了,但为了后代,这里是@jasp 提供的我的解决方案版本。不同之处在于此解决方案会将Map对象以 JSON 格式保存到文本列。它使用包含 Jackson 库的 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 文档存储。

于 2021-01-28T20:07:20.493 回答