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

本文涉及的产品
云原生数据库 PolarDB MySQL 版,通用型 2核8GB 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数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
2月前
|
安全 Java Apache
微服务——SpringBoot使用归纳——Spring Boot中集成 Shiro——Shiro 身份和权限认证
本文介绍了 Apache Shiro 的身份认证与权限认证机制。在身份认证部分,分析了 Shiro 的认证流程,包括应用程序调用 `Subject.login(token)` 方法、SecurityManager 接管认证以及通过 Realm 进行具体的安全验证。权限认证部分阐述了权限(permission)、角色(role)和用户(user)三者的关系,其中用户可拥有多个角色,角色则对应不同的权限组合,例如普通用户仅能查看或添加信息,而管理员可执行所有操作。
110 0
|
2月前
|
安全 Java 数据安全/隐私保护
微服务——SpringBoot使用归纳——Spring Boot中集成 Shiro——Shiro 三大核心组件
本课程介绍如何在Spring Boot中集成Shiro框架,主要讲解Shiro的认证与授权功能。Shiro是一个简单易用的Java安全框架,用于认证、授权、加密和会话管理等。其核心组件包括Subject(认证主体)、SecurityManager(安全管理员)和Realm(域)。Subject负责身份认证,包含Principals(身份)和Credentials(凭证);SecurityManager是架构核心,协调内部组件运作;Realm则是连接Shiro与应用数据的桥梁,用于访问用户账户及权限信息。通过学习,您将掌握Shiro的基本原理及其在项目中的应用。
117 0
|
2月前
|
NoSQL Java 关系型数据库
微服务——SpringBoot使用归纳——Spring Boot 中集成Redis——Redis 介绍
本文介绍在 Spring Boot 中集成 Redis 的方法。Redis 是一种支持多种数据结构的非关系型数据库(NoSQL),具备高并发、高性能和灵活扩展的特点,适用于缓存、实时数据分析等场景。其数据以键值对形式存储,支持字符串、哈希、列表、集合等类型。通过将 Redis 与 Mysql 集群结合使用,可实现数据同步,提升系统稳定性。例如,在网站架构中优先从 Redis 获取数据,故障时回退至 Mysql,确保服务不中断。
123 0
微服务——SpringBoot使用归纳——Spring Boot 中集成Redis——Redis 介绍
|
24天前
|
Java 开发工具 Spring
【Azure Application Insights】为Spring Boot应用集成Application Insight SDK
本文以Java Spring Boot项目为例,详细说明如何集成Azure Application Insights SDK以收集和展示日志。内容包括三步配置:1) 在`pom.xml`中添加依赖项`applicationinsights-runtime-attach`和`applicationinsights-core`;2) 在main函数中调用`ApplicationInsights.attach()`;3) 配置`applicationinsights.json`文件。同时提供问题排查建议及自定义日志方法示例,帮助用户顺利集成并使用Application Insights服务。
|
2月前
|
消息中间件 存储 Java
微服务——SpringBoot使用归纳——Spring Boot中集成ActiveMQ——ActiveMQ安装
本教程介绍ActiveMQ的安装与基本使用。首先从官网下载apache-activemq-5.15.3版本,解压后即可完成安装,非常便捷。启动时进入解压目录下的bin文件夹,根据系统选择win32或win64,运行activemq.bat启动服务。通过浏览器访问`http://127.0.0.1:8161/admin/`可进入管理界面,默认用户名密码为admin/admin。ActiveMQ支持两种消息模式:点对点(Queue)和发布/订阅(Topic)。前者确保每条消息仅被一个消费者消费,后者允许多个消费者同时接收相同消息。
74 0
微服务——SpringBoot使用归纳——Spring Boot中集成ActiveMQ——ActiveMQ安装
|
2月前
|
消息中间件 Java 微服务
微服务——SpringBoot使用归纳——Spring Boot中集成ActiveMQ——发布/订阅消息的生产和消费
本文详细讲解了Spring Boot中ActiveMQ的发布/订阅消息机制,包括消息生产和消费的具体实现方式。生产端通过`sendMessage`方法发送订阅消息,消费端则需配置`application.yml`或自定义工厂以支持topic消息监听。为解决点对点与发布/订阅消息兼容问题,可通过设置`containerFactory`实现两者共存。最后,文章还提供了测试方法及总结,帮助读者掌握ActiveMQ在异步消息处理中的应用。
108 0
|
2月前
|
消息中间件 网络协议 Java
微服务——SpringBoot使用归纳——Spring Boot中集成ActiveMQ——ActiveMQ集成
本文介绍了在 Spring Boot 中集成 ActiveMQ 的详细步骤。首先通过引入 `spring-boot-starter-activemq` 依赖并配置 `application.yml` 文件实现基本设置。接着,创建 Queue 和 Topic 消息类型,分别使用 `ActiveMQQueue` 和 `ActiveMQTopic` 类完成配置。随后,利用 `JmsMessagingTemplate` 实现消息发送功能,并通过 Controller 和监听器实现点对点消息的生产和消费。最后,通过浏览器访问测试接口验证消息传递的成功性。
67 0
|
2月前
|
消息中间件 Java API
微服务——SpringBoot使用归纳——Spring Boot中集成ActiveMQ—— JMS 和 ActiveMQ 介绍
本文介绍如何在Spring Boot中集成ActiveMQ,首先阐述了JMS(Java消息服务)的概念及其作为与具体平台无关的API在异步通信中的作用。接着说明了JMS的主要对象模型,如连接工厂、会话、生产者和消费者等,并指出JMS支持点对点和发布/订阅两种消息类型。随后重点讲解了ActiveMQ,作为Apache开源的消息总线,它完全支持JMS规范,适用于异步消息处理。最后,文章探讨了在Spring Boot中使用队列(Queue)和主题(Topic)这两种消息通信形式的方法。
62 0
|
2月前
|
NoSQL Java API
微服务——SpringBoot使用归纳——Spring Boot 中集成Redis——Spring Boot 集成 Redis
本文介绍了在Spring Boot中集成Redis的方法,包括依赖导入、Redis配置及常用API的使用。通过导入`spring-boot-starter-data-redis`依赖和配置`application.yml`文件,可轻松实现Redis集成。文中详细讲解了StringRedisTemplate的使用,适用于字符串操作,并结合FastJSON将实体类转换为JSON存储。还展示了Redis的string、hash和list类型的操作示例。最后总结了Redis在缓存和高并发场景中的应用价值,并提供课程源代码下载链接。
125 0
|
2月前
|
NoSQL Java Redis
微服务——SpringBoot使用归纳——Spring Boot 中集成Redis——Redis 安装
本教程介绍在 VMware 虚拟机(CentOS 7)或阿里云服务器中安装 Redis 的过程,包括安装 gcc 编译环境、下载 Redis(官网或 wget)、解压安装、修改配置文件(如 bind、daemonize、requirepass 等设置)、启动 Redis 服务及测试客户端连接。通过 set 和 get 命令验证安装是否成功。适用于初学者快速上手 Redis 部署。
50 0