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

本文涉及的产品
云原生数据库 PolarDB PostgreSQL 版,标准版 2核4GB 50GB
云原生数据库 PolarDB MySQL 版,通用型 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数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
19天前
|
Java Maven Docker
gitlab-ci 集成 k3s 部署spring boot 应用
gitlab-ci 集成 k3s 部署spring boot 应用
|
9天前
|
前端开发 Java 程序员
springboot 学习十五:Spring Boot 优雅的集成Swagger2、Knife4j
这篇文章是关于如何在Spring Boot项目中集成Swagger2和Knife4j来生成和美化API接口文档的详细教程。
25 1
|
15天前
|
SQL Java 关系型数据库
Springboot引入jpa来管理数据库
Springboot引入jpa来管理数据库
17 0
Springboot引入jpa来管理数据库
|
26天前
|
存储 前端开发 Java
Spring Boot 集成 MinIO 与 KKFile 实现文件预览功能
本文详细介绍如何在Spring Boot项目中集成MinIO对象存储系统与KKFileView文件预览工具,实现文件上传及在线预览功能。首先搭建MinIO服务器,并在Spring Boot中配置MinIO SDK进行文件管理;接着通过KKFileView提供文件预览服务,最终实现文档管理系统的高效文件处理能力。
117 11
|
9天前
|
Java Spring
springboot 学习十一:Spring Boot 优雅的集成 Lombok
这篇文章是关于如何在Spring Boot项目中集成Lombok,以简化JavaBean的编写,避免冗余代码,并提供了相关的配置步骤和常用注解的介绍。
43 0
|
1月前
|
XML Java 关系型数据库
springboot 集成 mybatis-plus 代码生成器
本文介绍了如何在Spring Boot项目中集成MyBatis-Plus代码生成器,包括导入相关依赖坐标、配置快速代码生成器以及自定义代码生成器模板的步骤和代码示例,旨在提高开发效率,快速生成Entity、Mapper、Mapper XML、Service、Controller等代码。
springboot 集成 mybatis-plus 代码生成器
|
1月前
|
Java Spring
springboot 集成 swagger 2.x 和 3.0 以及 Failed to start bean ‘documentationPluginsBootstrapper‘问题的解决
本文介绍了如何在Spring Boot项目中集成Swagger 2.x和3.0版本,并提供了解决Swagger在Spring Boot中启动失败问题“Failed to start bean ‘documentationPluginsBootstrapper’; nested exception is java.lang.NullPointerEx”的方法,包括配置yml文件和Spring Boot版本的降级。
springboot 集成 swagger 2.x 和 3.0 以及 Failed to start bean ‘documentationPluginsBootstrapper‘问题的解决
|
15天前
|
SQL Java 数据库连接
springBoot+Jpa(hibernate)数据库基本操作
springBoot+Jpa(hibernate)数据库基本操作
23 0
|
2月前
|
NoSQL 关系型数据库 MySQL
SpringBoot 集成 SpringSecurity + MySQL + JWT 附源码,废话不多直接盘
SpringBoot 集成 SpringSecurity + MySQL + JWT 附源码,废话不多直接盘
120 2
|
2月前
|
机器学习/深度学习 存储 搜索推荐
Elasticsearch与深度学习框架的集成案例研究
Elasticsearch 是一个强大的搜索引擎和分析引擎,广泛应用于实时数据处理和全文搜索。深度学习框架如 TensorFlow 和 PyTorch 则被用来构建复杂的机器学习模型。本文将探讨如何将 Elasticsearch 与这些深度学习框架集成,以实现高级的数据分析和预测任务。
35 0

热门文章

最新文章