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

本文涉及的产品
云原生数据库 PolarDB MySQL 版,Serverless 5000PCU 100GB
简介: 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数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
1天前
|
JavaScript Java 测试技术
基于SpringBoot+Vue+uniapp微信小程序的在线学习系统的详细设计和实现
基于SpringBoot+Vue+uniapp微信小程序的在线学习系统的详细设计和实现
6 0
|
2天前
|
Java Spring
Spring源码学习——(二)
第二讲——了解BeanFactory的功能
|
2天前
|
Java Spring 容器
Spring源码学习——(一)
第一讲——了解BeanFactory和ApplicationContext
|
2天前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp小程序的党员学习交流平台附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp小程序的党员学习交流平台附带文章源码部署视频讲解等
2 0
|
2天前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp小程序的在线互动学习网站附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp小程序的在线互动学习网站附带文章源码部署视频讲解等
7 1
|
2天前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp小程序的计算机学习系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp小程序的计算机学习系统附带文章源码部署视频讲解等
7 0
|
2天前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp小程序的语言课学习系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp小程序的语言课学习系统附带文章源码部署视频讲解等
5 0
|
3天前
|
消息中间件 监控 Java
Java一分钟之-Spring Integration:企业级集成
【6月更文挑战第11天】Spring Integration是Spring框架的一部分,用于简化企业应用的集成,基于EIP设计,采用消息传递连接不同服务。核心概念包括通道(Channel)、端点(Endpoint)和适配器(Adapter)。常见问题涉及过度设计、消息丢失与重复处理、性能瓶颈。解决策略包括遵循YAGNI原则、使用幂等性和事务管理、优化线程配置。通过添加依赖并创建简单消息处理链,可以开始使用Spring Integration。注意实践中要关注消息可靠性、系统性能,逐步探索高级特性以提升集成解决方案的质量和可维护性。
26 3
Java一分钟之-Spring Integration:企业级集成
|
3天前
|
NoSQL Java MongoDB
Java一分钟之-Spring Data MongoDB:MongoDB集成
【6月更文挑战第11天】Spring Data MongoDB简化Java应用与MongoDB集成,提供模板和Repository模型。本文介绍其基本用法、常见问题及解决策略。包括时间字段的UTC转换、异常处理和索引创建。通过添加相关依赖、配置MongoDB连接、定义Repository接口及使用示例,帮助开发者高效集成MongoDB到Spring Boot应用。注意避免时间差、异常处理不充分和忽视索引的问题。
22 0
|
3天前
|
SQL Java 数据库
Java一分钟之-Spring Data JPA:简化数据库访问
【6月更文挑战第10天】Spring Data JPA是Spring Data项目的一部分,简化了Java数据库访问。它基于JPA,提供Repository接口,使开发者能通过方法命名约定自动执行SQL,减少代码量。快速上手包括添加相关依赖,配置数据库连接,并定义实体与Repository接口。常见问题涉及主键生成策略、查询方法命名和事务管理。示例展示了分页查询的使用。掌握Spring Data JPA能提升开发效率和代码质量。
24 0