PostgreSQL支持json字段类型,json 数据类型可以用来存储 JSON(JavaScript Object Notation)数据, 这样的数据也可以存储为 text,但是 json 数据类型更有利于检查每个存储的数值是可用的 JSON 值。
Spring Data Jpa若想使用json类型字段,需要自定义数据解析,具体实现方式就是实现UserType,重新方法实现编写解析逻辑代码解析数据以达到目的。
下面将详细说明实现方式(已测试通过):
简单的Map格式实现:
- 定义自己的数据库方言
package com.cai.demo.config;
import org.hibernate.dialect.PostgreSQL95Dialect;
import java.sql.Types;
/**
* @Description: 自定义PostgreSQL方言
* @Author: caijun
*/
public class JsonbPostgresDialect extends PostgreSQL95Dialect {
public JsonbPostgresDialect() {
this.registerColumnType(Types.JAVA_OBJECT, "jsonb");
}
}
- 配置自定义方言
spring:
jpa:
hibernate:
database-platform: com.cai.demo.config.JsonbPostgresDialect
- 编写json类型转换代码
package com.cai.demo.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.type.SerializationException;
import org.hibernate.usertype.UserType;
import org.postgresql.util.PGobject;
import org.springframework.util.ObjectUtils;
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.HashMap;
import java.util.Map;
/**
* @Description: Jsonb类型转换
* @Author: caijun
*/
public class JsonbType implements UserType {
private final ObjectMapper mapper = new ObjectMapper();
@Override
public Object deepCopy(Object originalValue) throws HibernateException {
if (originalValue != null) {
try {
return mapper.readValue(mapper.writeValueAsString(originalValue), returnedClass());
} catch (IOException e) {
throw new HibernateException("Failed to deep copy object", e);
}
}
return null;
}
@Override
public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws HibernateException, SQLException {
PGobject o = (PGobject) rs.getObject(names[0]);
// 具体实现逻辑
if (o != null && o.getValue() != null) {
try {
return mapper.readValue(o.getValue(), Map.class);
} catch (IOException e) {
e.printStackTrace();
}
}
return new HashMap<String, Object>();
}
@Override
public Serializable disassemble(Object value) throws HibernateException {
Object copy = deepCopy(value);
if (copy instanceof Serializable) {
return (Serializable) copy;
}
throw new SerializationException(String.format("Cannot serialize '%s', %s is not Serializable.", value, value.getClass()), null);
}
@Override
public Object assemble(Serializable cached, Object owner) throws HibernateException {
return deepCopy(cached);
}
@Override
public Object replace(Object original, Object target, Object owner) throws HibernateException {
return deepCopy(original);
}
@Override
public boolean isMutable() {
return true;
}
@Override
public int hashCode(Object x) throws HibernateException {
if (x == null) {
return 0;
}
return x.hashCode();
}
@Override
public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws HibernateException, SQLException {
if (value == null) {
st.setNull(index, Types.OTHER);
} else {
try {
st.setObject(index, mapper.writeValueAsString(value), Types.OTHER);
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public boolean equals(Object x, Object y) throws HibernateException {
return ObjectUtils.nullSafeEquals(x, y);
}
@Override
public Class<?> returnedClass() {
return Map.class;
}
@Override
public int[] sqlTypes() {
return new int[]{Types.JAVA_OBJECT};
}
}
- 实体类添加注解
@Getter
@Setter
@ToString
@Entity
@Table(name = "test_tb")
@TypeDef(name = "JsonbType", typeClass = JsonbType.class)
public class TestEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键id
*/
@Id
@Column(name = "pri_id")
private Integer priId;
@Column(name = "test_json_field", columnDefinition = "jsonb")
@Type(type = "JsonbType")
private Map<String, List<String>> testJsonField;
}
'test_json_field’字段需要存储如以下格式的数据:
{“2019-11-16”: [“0-24”], “2019-11-17”: [“0-24”], “2019-11-18”: [“0-24”]}
- 测试
/**
* 测试'jsonb'字段类型映射(测试通过)
*/
@Test
public void testJsonField() {
// jpa查询实体对象
TestEntity testEntity = testRepository.findById(1).get();
log.info(testEntity.toString());
// 增加新的值
Map<String, List<String>> map = testEntity.getTestJsonField();
List<String> list = ImmutableList.of("8-9");
map.put("2019-11-19", list);
testEntity.setTestJsonField(map);
// 保存
testRepository.save(testEntity);
}
对象直接映射:
- 定义自己的数据库方言
package com.cai.demo.config;
import org.hibernate.dialect.PostgreSQL95Dialect;
import java.sql.Types;
/**
* @Description: 自定义PostgreSQL方言
* @Author: caijun
*/
public class JsonbPostgresDialect extends PostgreSQL95Dialect {
public JsonbPostgresDialect() {
super();
this.registerColumnType(Types.JAVA_OBJECT, "jsonb");
registerHibernateType(Types.ARRAY, StringType.class.getName());
}
}
- 配置自定义方言
spring:
jpa:
hibernate:
database-platform: com.cai.demo.config.JsonbPostgresDialect
- 编写json类型转换代码
package com.cai.demo.config;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.hibernate.HibernateException;
import org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.type.SerializationException;
import org.hibernate.usertype.ParameterizedType;
import org.hibernate.usertype.UserType;
import org.postgresql.util.PGobject;
import org.springframework.util.ObjectUtils;
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.Properties;
/**
* @Description: Jsonb类型转换
* @Author: caijun
*/
public class JsonbType implements UserType, ParameterizedType {
private final ObjectMapper mapper = new ObjectMapper();
private static final ClassLoaderService classLoaderService = new ClassLoaderServiceImpl();
public static final String CLASS = "CLASS";
private Class<?> jsonClassType;
@Override
public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws HibernateException, SQLException {
if (value == null) {
st.setNull(index, Types.OTHER);
} else {
try {
st.setObject(index, mapper.writeValueAsString(value), Types.OTHER);
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public Object deepCopy(Object originalValue) throws HibernateException {
if (originalValue != null) {
try {
return mapper.readValue(mapper.writeValueAsString(originalValue),
returnedClass());
} catch (IOException e) {
throw new HibernateException("Failed to deep copy object", e);
}
}
return null;
}
@Override
public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws HibernateException, SQLException {
PGobject o = (PGobject) rs.getObject(names[0]);
// 具体实现逻辑
if (o != null && o.getValue() != null) {
return JSON.parseObject(o.getValue(), jsonClassType);
}
return null;
}
@Override
public Serializable disassemble(Object value) throws HibernateException {
Object copy = deepCopy(value);
if (copy instanceof Serializable) {
return (Serializable) copy;
}
throw new SerializationException(String.format("Cannot serialize '%s', %s is not Serializable.", value, value.getClass()), null);
}
@Override
public Object assemble(Serializable cached, Object owner) throws HibernateException {
return deepCopy(cached);
}
@Override
public Object replace(Object original, Object target, Object owner) throws HibernateException {
return deepCopy(original);
}
@Override
public boolean isMutable() {
return true;
}
@Override
public int hashCode(Object x) throws HibernateException {
if (x == null) {
return 0;
}
return x.hashCode();
}
@Override
public boolean equals(Object x, Object y) throws HibernateException {
return ObjectUtils.nullSafeEquals(x, y);
}
@Override
public Class<?> returnedClass() {
return jsonClassType;
}
@Override
public int[] sqlTypes() {
return new int[]{Types.JAVA_OBJECT};
}
@Override
public void setParameterValues(Properties properties) {
final String clazz = (String) properties.get(CLASS);
if (clazz != null) {
jsonClassType = classLoaderService.classForName(clazz);
}
}
}
- 实体类添加注解
@Data
@Entity
@Table(name = "test_tb")
@TypeDef(name = "entityJsonbType1", typeClass = JsonbType.class, parameters = {
@Parameter(name = JsonbType.CLASS, value = "com.cai.demo.entity.TestEntity2")
})
public class TestEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键id
*/
@Id
@Column(name = "pri_id")
private Integer priId;
@Column(name = "entity_json_field", columnDefinition = "jsonb")
@Type(type = "entityJsonbType1")
private TestEntity2 testEntityJson;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "test_tb_2")
public class TestEntity2 implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键id
*/
@Id
@Column(name = "pri_id")
private Integer priId;
@Column(name = "name")
private String name;
}
- 测试
@Test
public void testJsonbEntity() {
TestEntity testEntity = testRepository.findById(1).get();
TestEntity2 testEntity2 = new TestEntity2(11, "testEntity2");
// 添加
testEntity.setTestEntityJson(testEntity2);
testRepository.save(testEntity);
// 修改保存
TestEntity testEntity1_1 = testRepository.findById(1).get();
TestEntity2 testEntity2_1 = testEntity1_1.getTestEntityJson();
testEntity2_1.setName("testEntity2_1");
testEntity1_1.setTestEntityJson(testEntity2_1);
testRepository.save(testEntity1_1);
// 删除
TestEntity testEntity1_2 = testRepository.findById(1).get();
testEntity1_2.setTestEntityJson(null);
testRepository.save(testEntity1_2);
}