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数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
25天前
|
监控 Java 数据库连接
详解Spring Batch:在Spring Boot中实现高效批处理
详解Spring Batch:在Spring Boot中实现高效批处理
132 12
|
25天前
|
安全 Java 测试技术
详解Spring Profiles:在Spring Boot中实现环境配置管理
详解Spring Profiles:在Spring Boot中实现环境配置管理
73 10
|
21天前
|
XML Java API
Spring Boot集成MinIO
本文介绍了如何在Spring Boot项目中集成MinIO,一个高性能的分布式对象存储服务。主要步骤包括:引入MinIO依赖、配置MinIO属性、创建MinIO配置类和服务类、使用服务类实现文件上传和下载功能,以及运行应用进行测试。通过这些步骤,可以轻松地在项目中使用MinIO的对象存储功能。
|
22天前
|
负载均衡 Java 开发者
深入探索Spring Cloud与Spring Boot:构建微服务架构的实践经验
深入探索Spring Cloud与Spring Boot:构建微服务架构的实践经验
68 5
|
23天前
|
消息中间件 Java Kafka
什么是Apache Kafka?如何将其与Spring Boot集成?
什么是Apache Kafka?如何将其与Spring Boot集成?
57 5
|
25天前
|
消息中间件 Java Kafka
Spring Boot 与 Apache Kafka 集成详解:构建高效消息驱动应用
Spring Boot 与 Apache Kafka 集成详解:构建高效消息驱动应用
37 1
|
26天前
|
消息中间件 监控 Java
您是否已集成 Spring Boot 与 ActiveMQ?
您是否已集成 Spring Boot 与 ActiveMQ?
49 0
|
2月前
|
Java Maven Docker
gitlab-ci 集成 k3s 部署spring boot 应用
gitlab-ci 集成 k3s 部署spring boot 应用
|
5月前
|
监控 druid Java
spring boot 集成配置阿里 Druid监控配置
spring boot 集成配置阿里 Druid监控配置
318 6
|
5月前
|
Java 关系型数据库 MySQL
如何实现Springboot+camunda+mysql的集成
【7月更文挑战第2天】集成Spring Boot、Camunda和MySQL的简要步骤: 1. 初始化Spring Boot项目,添加Camunda和MySQL驱动依赖。 2. 配置`application.properties`,包括数据库URL、用户名和密码。 3. 设置Camunda引擎属性,指定数据源。 4. 引入流程定义文件(如`.bpmn`)。 5. 创建服务处理流程操作,创建控制器接收请求。 6. Camunda自动在数据库创建表结构。 7. 启动应用,测试流程启动,如通过服务和控制器开始流程实例。 示例代码包括服务类启动流程实例及控制器接口。实际集成需按业务需求调整。
421 4

热门文章

最新文章