如何将 grails 域 class 属性配置为存储为 (postgres 9.4) jsonb?
How do I configure a grails domain class attribute to be stored as (postgres 9.4) jsonb?
我试过像这样配置域 class:
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'
,它创建了一个名为 data
.
的 text
列
如何正确地告诉 grails 使用 jsonb
作为 sql 列类型?有可能吗?
(postgresql jdbc 驱动程序在版本 9.4-1200 中使用。jdbc4 with hibernate 4。)
您可以使用 Grails Postgresql Extensions 插件在您的域中使用一些 Postgresql 本机类型 类。
目前插件支持Json但不支持Jsonb类型。您可以在 plugin documentation
中获得有关 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
或您现有的 class 或界面。在这种情况下,GORM 将负责 serializing/deserializing json 并且您不再需要在应用程序中明确地这样做。 Here is an example of such UserType
implementation.
尽管我很晚才回答这个问题,但我设法通过一种非常简单的方式实现了这一点,而且工作非常顺利-
我创建了一个实现 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-
class User {
String email
String name
JSONObject settings
static mapping = {
settings type: JSONObjectFooType, sqlType: "text"
}
}
合十礼!
游戏有点晚了,但对于后代来说,这里是我对@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 服务器,nvarchar(4000)
用于高效 JSON 文档查询或 nvarchar(MAX)
用于大型 JSON 文档存储。
我试过像这样配置域 class:
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'
,它创建了一个名为 data
.
text
列
如何正确地告诉 grails 使用 jsonb
作为 sql 列类型?有可能吗?
(postgresql jdbc 驱动程序在版本 9.4-1200 中使用。jdbc4 with hibernate 4。)
您可以使用 Grails Postgresql Extensions 插件在您的域中使用一些 Postgresql 本机类型 类。
目前插件支持Json但不支持Jsonb类型。您可以在 plugin documentation
中获得有关 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
或您现有的 class 或界面。在这种情况下,GORM 将负责 serializing/deserializing json 并且您不再需要在应用程序中明确地这样做。 Here is an example of such UserType
implementation.
尽管我很晚才回答这个问题,但我设法通过一种非常简单的方式实现了这一点,而且工作非常顺利-
我创建了一个实现 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-
class User {
String email
String name
JSONObject settings
static mapping = {
settings type: JSONObjectFooType, sqlType: "text"
}
}
合十礼!
游戏有点晚了,但对于后代来说,这里是我对@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 服务器,nvarchar(4000)
用于高效 JSON 文档查询或 nvarchar(MAX)
用于大型 JSON 文档存储。