Spring Boot 学习研究笔记(十三) Spring Data JPA与PostgreSQL的jsonb类型集成

本文涉及的产品
云原生数据库 PolarDB MySQL 版,通用型 2核4GB 50GB
云原生数据库 PolarDB PostgreSQL 版,标准版 2核4GB 50GB
简介: Spring Boot 学习研究笔记(十三) Spring Data JPA与PostgreSQL的jsonb类型集成

Spring Data JPA与PostgreSQL的jsonb类型集成与支持

在我们项目中经常会遇到数据结构不定的情况,这时普通的关系型数据库不能满足我们的要求。Postgres为我们提供了jsonb数据类型,我们可在此类型的字段存储json数据,并可对此数据进行查询。本例将结合hibernate,Spring Data JPA,Spring Boot来实现。

1. 自定义SQLDialect

package com.call.show.common.jpa;
import org.hibernate.dialect.PostgreSQL95Dialect;
import java.sql.Types;
public class StringToTextPostgreSQLDialect extends PostgreSQL95Dialect {
    public StringToTextPostgreSQLDialect() {
        this.registerColumnType(Types.VARCHAR, "TEXT");
    }
}

指定SQLDialect

spring.jpa.database-platform=com.call.show.common.utils.StringToTextPostgreSQLDialect

2、自定义jsonb数据类型

这里主要实现了Map映射PGObject(postgres对象类型),通过ObjectMapper来实现两个数据类型的转换。

package com.call.show.common.jpa;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
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.StringUtils;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.math.BigInteger;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class JsonType implements UserType {
    private final static ObjectMapper OBJECT_MAPPER = new ObjectMapper();
    @Override
    public Object assemble(Serializable cached, Object owner) throws HibernateException {
        return deepCopy(cached);
    }
    @Override
    @SneakyThrows
    public Object deepCopy(Object originalValue) throws HibernateException {
        //将对象写到流里
        ByteArrayOutputStream bo = new ByteArrayOutputStream();
        ObjectOutputStream oo = new ObjectOutputStream(bo);
        oo.writeObject(originalValue);
        //从流里读出来
        ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
        ObjectInputStream oi = new ObjectInputStream(bi);
        return oi.readObject();
    }
    @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 boolean equals(Object x, Object y) throws HibernateException {
        if (x instanceof List && y instanceof List) {
            List xList = ((List) x);
            List yList = ((List) y);
            if (xList.size() > 0 && yList.size() > 0 && xList.size() == yList.size()) {
                if (xList.get(0).getClass().isEnum() || yList.get(0).getClass().isEnum()) {
                    List<String> a = (List<String>) xList.stream().map(o -> o.toString()).collect(Collectors.toList());
                    Collections.sort(a);
                    List<String> b = (List<String>) yList.stream().map(o -> o.toString()).collect(Collectors.toList());
                    Collections.sort(b);
                    return a.equals(b);
                }
            }
        } else if (x instanceof Map && y instanceof Map) {
            Map xMap = ((Map) x);
            Map yMap = ((Map) y);
            if (xMap.size() > 0 && yMap.size() > 0 && xMap.size() == yMap.size()) {
                boolean keyN = xMap.keySet().stream().allMatch(o -> o instanceof Integer || o instanceof Long);
                boolean valueN = xMap.values().stream().allMatch(o -> o instanceof Integer || o instanceof Long);
                if (keyN || valueN) {
                    Map newX = new HashMap();
                    Map newY = new HashMap();
                    xMap.entrySet().stream().forEach((Consumer<Map.Entry>) entry -> {
                        Object key = entry.getKey();
                        Object value = entry.getValue();
                        if (keyN) {
                            key = key.toString();
                        }
                        if (valueN) {
                            value = value.toString();
                        }
                        newX.put(key, value);
                    });
                    yMap.entrySet().stream().forEach((Consumer<Map.Entry>) entry -> {
                        Object key = entry.getKey();
                        Object value = entry.getValue();
                        if (keyN) {
                            key = key.toString();
                        }
                        if (valueN) {
                            value = value.toString();
                        }
                        newY.put(key, value);
                    });
                    return newX.entrySet().stream().allMatch((Predicate<Map.Entry>) xme ->
                            xme.getValue() == newY.get(xme.getKey()) || xme.getValue().equals(newY.get(xme.getKey())));
                }
            }
        }
        if (x == null && y == null) {
            return true;
        } else if (x != null && y == null) {
            return false;
        } else if (x == null && y != null) {
            return false;
        } else {
            return x.toString().equals(y.toString());
        }
    }
    @Override
    public int hashCode(Object x) throws HibernateException {
        if (x == null) {
            return 0;
        }
        return x.hashCode();
    }
    @Override
    public boolean isMutable() {
        return true;
    }
    @Override
    @SneakyThrows
    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) {
            Object data = OBJECT_MAPPER.readValue(o.getValue().toString(), Map.class).get("data");
            if (data instanceof List) {
                String dbFiledName = names[0];
                String[] dbFiledNames = dbFiledName.split("_");
                StringBuffer filedNamePrefixBuffer = new StringBuffer();
                for (int i = 0; i < dbFiledNames.length; i++) {
                    if (i == 0) {
                        filedNamePrefixBuffer.append(dbFiledNames[0]);
                    } else if (dbFiledNames[i].matches("^[0-9]$")) {
                        break;
                    } else {
                        filedNamePrefixBuffer.append(StringUtils.capitalize(dbFiledNames[i]));
                    }
                }
                String fileNamePrefix = filedNamePrefixBuffer.toString().replaceAll("[0-9]", "");
                List<Field> fields = new ArrayList<>();
                fields.addAll(Arrays.asList(owner.getClass().getSuperclass().getDeclaredFields()));
                if (owner.getClass().getSuperclass().equals(Object.class)) {//针对实体类上级类是Object情况,直接从实体类中获取声明的字段
                    fields.addAll(Arrays.asList(owner.getClass().getDeclaredFields()));
                } else if (!owner.getClass().getSuperclass().getSuperclass().equals(Object.class)) {//针对实体类是继承View类情况,直接从View类的上级模型类中获取声明字段
                    fields.addAll(Arrays.asList(owner.getClass().getSuperclass().getSuperclass().getDeclaredFields()));
                }
                for (Field field : fields) {
                    if (field.getName().startsWith(fileNamePrefix)) {
                        if (field.getGenericType() instanceof ParameterizedType) {
                            String typeName = ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0].getTypeName();
                            if (!typeName.contains("<")) {
                                Class type = Class.forName(typeName);
                                if (type.isEnum()) {
                                    return ((List) data).stream().map(o1 -> Enum.valueOf(type, o1.toString())).collect(Collectors.toList());
                                }
                            }
                            break;
                        }
                    }
                }
                return filterList((List) data);
            } else if (data instanceof Map) {
                return filterMap((Map) data);
            } else {
                return data;
            }
        }
        return null;
    }
    @Override
    @SneakyThrows
    public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws HibernateException, SQLException {
        if (value == null) {
            st.setNull(index, Types.OTHER);
        } else {
            Map<String, Object> map = new HashMap<>();
            map.put("data", value);
            value = OBJECT_MAPPER.writeValueAsString(map);
            st.setObject(index, value, Types.OTHER);
        }
    }
    @Override
    public Object replace(Object original, Object target, Object owner) throws HibernateException {
        return deepCopy(original);
    }
    @Override
    public Class<?> returnedClass() {
        return Object.class;
    }
    @Override
    public int[] sqlTypes() {
        return new int[] { Types.OTHER };
    }
    private List filterList(List list) {
        return (List) list.stream().map(o -> {
            if (o instanceof Integer || o instanceof BigInteger) {
                return Long.valueOf(o.toString());
            }
            return o;
        }).collect(Collectors.toList());
    }
    private Map filterMap(Map map) {
        if (map.keySet().stream().allMatch(o -> o instanceof Integer) || map.values().stream().allMatch(o -> o instanceof Integer)) {
            map.entrySet().forEach(o -> {
                Map.Entry e = (Map.Entry) o;
                Object key = e.getKey();
                Object value = e.getValue();
                if (key instanceof Integer) {
                    key = Long.valueOf(key.toString());
                }
                if (value instanceof Integer) {
                    value = Long.valueOf(value.toString());
                }
                map.put(key, value);
            });
        }
        return map;
    }
}

3. 声明使用

先定义数据类型,再在字段上使用

@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@TypeDef(name = "JsonbType", typeClass = JsonbType.class)
public class Person {
    @Id
    @GeneratedValue
    private Long id;
    @Column(columnDefinition = "jsonb")
    @Type(type = "JsonbType")
    private Map<String,Object> info;
}

4.Repository

通过postgres原生sql语句查询,本例含义为json数据info的一个keyname的值等于。具体的JSON的sql查询方式请参考:

public interface PersonRepository extends JpaRepository<Person,Long> {
    @Query(value = "select * from person where info ->> 'name' = :name" , nativeQuery = true)
    List<Person> findByName(@Param("name") String name);
}

5. 保存和读取测试

@Bean
CommandLineRunner saveAndReadJsonb(PersonRepository personRepository){
    return e -> {
        Person p = new Person();
        Map m = new HashMap();
        m.put("name","汪云飞");
        m.put("age",11);
        p.setInfo(m);
        Person returnPerson = personRepository.save(p);
        Map returnMap = returnPerson.getInfo();
        for(Object entry :returnMap.entrySet()){
                log.info(entry.toString());
        }
    };
}

6. 查询测试

@Bean
CommandLineRunner queryJsonb(PersonRepository personRepository){
    return e -> {
        List<Person> people = personRepository.findByName("test");
        for (Person person : people){
            Map info = person.getInfo();
            log.info(person.getId() + "/" + info.get("name") + "/" +info.get("age"));
        }
    };
}
相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
11天前
|
Java 开发者 Spring
Spring高手之路24——事务类型及传播行为实战指南
本篇文章深入探讨了Spring中的事务管理,特别是事务传播行为(如REQUIRES_NEW和NESTED)的应用与区别。通过详实的示例和优化的时序图,全面解析如何在实际项目中使用这些高级事务控制技巧,以提升开发者的Spring事务管理能力。
24 1
Spring高手之路24——事务类型及传播行为实战指南
|
9天前
|
分布式计算 关系型数据库 MySQL
SpringBoot项目中mysql字段映射使用JSONObject和JSONArray类型
SpringBoot项目中mysql字段映射使用JSONObject和JSONArray类型 图像处理 光通信 分布式计算 算法语言 信息技术 计算机应用
30 8
|
1月前
|
存储 Java API
如何使用 Java 记录简化 Spring Data 中的数据实体
如何使用 Java 记录简化 Spring Data 中的数据实体
35 9
|
1月前
|
Java 测试技术 开发者
springboot学习四:Spring Boot profile多环境配置、devtools热部署
这篇文章主要介绍了如何在Spring Boot中进行多环境配置以及如何整合DevTools实现热部署,以提高开发效率。
56 2
|
1月前
|
前端开发 Java 程序员
springboot 学习十五:Spring Boot 优雅的集成Swagger2、Knife4j
这篇文章是关于如何在Spring Boot项目中集成Swagger2和Knife4j来生成和美化API接口文档的详细教程。
84 1
|
1月前
|
Java API Spring
springboot学习七:Spring Boot2.x 拦截器基础入门&实战项目场景实现
这篇文章是关于Spring Boot 2.x中拦截器的入门教程和实战项目场景实现的详细指南。
25 0
springboot学习七:Spring Boot2.x 拦截器基础入门&实战项目场景实现
|
1月前
|
Java API Spring
springboot学习六:Spring Boot2.x 过滤器基础入门&实战项目场景实现
这篇文章是关于Spring Boot 2.x中过滤器的基础知识和实战项目应用的教程。
23 0
springboot学习六:Spring Boot2.x 过滤器基础入门&实战项目场景实现
|
18天前
|
关系型数据库 MySQL Java
SpringBoot项目中mysql字段映射使用JSONObject和JSONArray类型
SpringBoot项目中mysql字段映射使用JSONObject和JSONArray类型
22 0
|
1月前
|
XML Java 应用服务中间件
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
167 2
|
1月前
|
前端开发 安全 Java
【Spring】Spring Boot项目创建和目录介绍
【Spring】Spring Boot项目创建和目录介绍
84 2