0

0

使用JPA将对象列表作为单列JSON存储的教程

DDD

DDD

发布时间:2025-11-19 18:42:01

|

675人浏览过

|

来源于php中文网

原创

使用JPA将对象列表作为单列JSON存储的教程

本教程详细介绍了如何在spring boot和jpa应用中,将一个对象列表(json数组)高效地存储到数据库的单个列中,而非分散到多个列或单独的表中。核心解决方案是利用jpa的`attributeconverter`机制,结合jackson库实现对象列表与json字符串之间的双向转换,从而灵活地处理复杂数据结构,满足特定存储需求。

在现代应用开发中,经常需要将复杂的嵌套数据结构(例如对象列表)存储到关系型数据库中。虽然JPA提供了@Embeddable和@ElementCollection等注解来处理嵌入式对象或集合,但它们通常会将数据展开成多个列或存储到单独的表中。当需求是将整个对象列表作为一个JSON字符串存储在主表的单个列中时,这些标准JPA注解就不再适用。此时,JPA的AttributeConverter机制便成为理想的解决方案。

理解问题:为什么标准JPA注解不适用?

原始问题中尝试使用@Embeddable注解来处理Sensors[]数组,并期望它能以JSON形式存储在单个列中。然而,@Embeddable注解通常用于将一个类的属性嵌入到另一个实体类中,这些属性会直接映射到主表的相应列。如果@Embeddable类是一个集合类型(如数组或列表),JPA会尝试将其映射到多个独立的列(对于数组的每个元素),或者在结合@ElementCollection时映射到一个单独的关联表。这与将整个集合序列化为单个JSON字符串的需求相悖,从而导致类型转换错误。

解决方案:使用JPA AttributeConverter

AttributeConverter是JPA 2.1引入的一个强大特性,它允许开发者自定义实体属性类型与数据库列类型之间的转换逻辑。通过实现这个接口,我们可以将复杂的Java对象(如List)在存储到数据库时序列化为简单的字符串(JSON),并在从数据库读取时反序列化回Java对象。

1. 定义 Sensors 类

首先,我们需要确保Sensors类是一个普通的Java类,不再需要@Embeddable注解,因为它的序列化和反序列化将由我们自定义的转换器处理,而不是JPA的嵌入式对象机制。

public class Sensors {
    private String amplitudos;
    private Double displacement;
    private String frequencies;
    private Integer sensorId;

    public Sensors() {
    }

    public Sensors(String amplitudos, Double displacement, String frequencies, Integer sensorId) {
        this.amplitudos = amplitudos;
        this.displacement = displacement;
        this.frequencies = frequencies;
        this.sensorId = sensorId;
    }

    // Getters and Setters
    public String getAmplitudos() {
        return amplitudos;
    }

    public void setAmplitudos(String amplitudos) {
        this.amplitudos = amplitudos;
    }

    public Double getDisplacement() {
        return displacement;
    }

    public void setDisplacement(Double displacement) {
        return displacement;
    }

    public String getFrequencies() {
        return frequencies;
    }

    public void setFrequencies(String frequencies) {
        this.frequencies = frequencies;
    }

    public Integer getSensorId() {
        return sensorId;
    }

    public void setSensorId(Integer sensorId) {
        this.sensorId = sensorId;
    }
}

2. 实现自定义 AttributeConverter

接下来,创建一个实现AttributeConverter接口的类。在这个例子中,ENTITY_ATTRIBUTE_TYPE是List,DATABASE_COLUMN_TYPE是String。我们将使用Jackson库来处理JSON的序列化和反序列化。

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;

import java.io.IOException;
import java.util.List;
import java.util.Collections; // 导入 Collections

@Converter
public class SensorsConverter implements AttributeConverter<List<Sensors>, String> {

    private final ObjectMapper objectMapper = new ObjectMapper();

    /**
     * 将实体属性(List<Sensors>)转换为数据库列类型(String JSON)
     * @param sensors 要转换的Sensors列表
     * @return 转换后的JSON字符串
     */
    @Override
    public String convertToDatabaseColumn(List<Sensors> sensors) {
        if (sensors == null) {
            return null;
        }
        try {
            return objectMapper.writeValueAsString(sensors);
        } catch (JsonProcessingException e) {
            // 生产环境中应记录日志并抛出更具体的运行时异常
            throw new IllegalArgumentException("Error converting List<Sensors> to JSON string", e);
        }
    }

    /**
     * 将数据库列类型(String JSON)转换为实体属性(List<Sensors>)
     * @param sensorsJSON 数据库中的JSON字符串
     * @return 转换后的Sensors列表
     */
    @Override
    public List<Sensors> convertToEntityAttribute(String sensorsJSON) {
        if (sensorsJSON == null || sensorsJSON.trim().isEmpty()) {
            return Collections.emptyList(); // 返回空列表而不是null
        }
        try {
            return objectMapper.readValue(sensorsJSON, new TypeReference<List<Sensors>>() {});
        } catch (IOException e) {
            // 生产环境中应记录日志并抛出更具体的运行时异常
            throw new IllegalArgumentException("Error converting JSON string to List<Sensors>", e);
        }
    }
}

注意

纳米漫剧流水线
纳米漫剧流水线

360推出的国内首个工业级AI漫剧生产平台

下载
  • @Converter注解标记这个类是一个JPA转换器。
  • convertToDatabaseColumn方法负责将Java对象(List)序列化为JSON字符串。
  • convertToEntityAttribute方法负责将JSON字符串反序列化回Java对象(List)。
  • 在实际应用中,应添加更健壮的异常处理和日志记录。对于空值处理,convertToEntityAttribute返回Collections.emptyList()通常比返回null更安全,可以避免后续的NullPointerException。

3. 在实体类中使用转换器

最后,在Sensor实体类中,将sensors字段的类型从Sensors[]改为List,并使用@Convert注解指定我们刚刚创建的SensorsConverter。

import jakarta.persistence.*;
import java.io.Serializable;
import java.sql.Timestamp;
import java.util.List; // 使用 List 代替数组

@Entity
@Table(name = "SENSOR")
public class Sensor implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @Column(name = "TIMERECEIVED")
    private Timestamp timereceived;

    // 使用 @Convert 注解指定自定义转换器
    // 数据库列类型为 VARCHAR2/CLOB,Java实体属性类型为 List<Sensors>
    @Column(name = "SENSORS", columnDefinition = "CLOB") // 或 VARCHAR2(4000) 根据实际JSON大小调整
    @Convert(converter = SensorsConverter.class)
    private List<Sensors> sensors; // 类型改为 List

    @Column(name = "LOC")
    private String location;

    public Sensor() {
    }

    public Sensor(Timestamp timereceived, List<Sensors> sensors, String location) {
        this.timereceived = timereceived;
        this.sensors = sensors;
        this.location = location;
    }

    // Getters and Setters
    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public Timestamp getTimereceived() {
        return timereceived;
    }

    public void setTimereceived(Timestamp timereceived) {
        this.timereceived = timereceived;
    }

    public List<Sensors> getSensors() { // 返回类型也改为 List
        return sensors;
    }

    public void setSensors(List<Sensors> sensors) { // 参数类型也改为 List
        this.sensors = sensors;
    }

    public String getLocation() {
        return location;
    }

    public void setLocation(String location) {
        this.location = location;
    }
}

注意

  • @Column(name = "SENSORS", columnDefinition = "CLOB"):这里我们显式地指定了数据库列的类型为CLOB(字符大对象),以确保能够存储较长的JSON字符串。如果JSON字符串较短,也可以使用VARCHAR2(N),其中N是足够大的长度。一些现代数据库(如PostgreSQL、Oracle 12c+、MySQL 5.7+)也支持原生JSON数据类型,如果使用这些数据库,可以考虑将columnDefinition设置为相应的JSON类型,例如JSON。

4. 控制器和JSON体

控制器和服务层代码基本无需修改,因为转换器在JPA层自动处理了List与String之间的映射。

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class SensorController {

    private final SensorRepository sensorRepository; // 假设有一个 SensorRepository

    public SensorController(SensorRepository sensorRepository) {
        this.sensorRepository = sensorRepository;
    }

    @PostMapping("/sendData")
    public ResponseEntity<String> sendData(@RequestBody Sensor sensor) {
        Sensor newSensor = sensorRepository.save(sensor);
        System.out.println("Saved Sensor: " + newSensor.getId());
        // 可以打印出 newSensor.getSensors() 来验证是否正确反序列化
        System.out.println("Sensors data: " + newSensor.getSensors());
        return ResponseEntity.ok("Sensor received and saved successfully");
    }
}

客户端发送的JSON请求体保持不变:

{
    "timereceived": "2022-11-29T12:04:42.166",
    "sensors": [
        {
            "amplitudos": "a1#a2#a3#a4",
            "displacement": 0.002,
            "frequencies": "f1#f2#f3#f4",
            "sensorid": 1
        },
        {
            "amplitudos": "a1#a2#a3#a4",
            "displacement": 0.002,
            "frequencies": "f1#f2#f3#f4",
            "sensorid": 2
        }
    ],
    "location": "lokasi"
}

当这个JSON体到达Spring Boot控制器时,Jackson会自动将其中的sensors数组映射到Sensor实体中的List字段。然后,当JPA尝试将Sensor实体保存到数据库时,SensorsConverter会自动将List转换为JSON字符串,存储到数据库的SENSORS列。反之,从数据库读取时,也会自动将JSON字符串转换回List

注意事项与最佳实践

  1. 数据库列类型选择
    • 对于较小的JSON数据,VARCHAR2(N)(Oracle)、VARCHAR(N)(MySQL/PostgreSQL)可能足够。
    • 对于不确定长度或可能很大的JSON数据,推荐使用CLOB(Oracle)、TEXT(PostgreSQL)、LONGTEXT(MySQL)等大文本类型。
    • 如果数据库支持原生JSON数据类型(如PostgreSQL的JSONB,Oracle的JSON类型,MySQL的JSON),优先考虑使用这些类型,它们通常提供更好的查询和索引性能。
  2. 错误处理:在AttributeConverter中,序列化和反序列化过程可能会抛出JsonProcessingException或IOException。应捕获这些异常,并根据业务需求进行日志记录或抛出自定义的运行时异常,以便更好地诊断问题。
  3. 性能考虑
    • 频繁地序列化和反序列化大型JSON字符串可能会带来一定的性能开销。
    • 如果需要对JSON内部的数据进行复杂查询,直接将JSON存储为字符串可能不是最高效的方式。考虑使用数据库的原生JSON查询功能,或将关键字段抽取为独立列。
  4. Schema演进:JSON数据的灵活性意味着其内部结构可以随时变化。但这也给应用带来了挑战,特别是当旧的JSON数据与新的Java对象结构不兼容时。需要有策略来处理JSON结构的演进,例如版本控制或数据迁移脚本。
  5. 依赖管理:确保项目中包含了Jackson库的依赖,例如:
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.15.2</version> <!-- 根据你的Spring Boot版本选择兼容版本 -->
    </dependency>

总结

通过使用JPA的AttributeConverter,我们可以优雅地解决将对象列表作为JSON字符串存储到数据库单个列的需求。这种方法提供了极大的灵活性,允许开发者在不牺牲JPA便利性的前提下,处理复杂的非结构化或半结构化数据。理解何时以及如何使用AttributeConverter是构建健壮且灵活的Spring Boot数据持久化层的关键技能之一。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
mysql修改数据表名
mysql修改数据表名

MySQL修改数据表:1、首先查看数据库中所有的表,代码为:‘SHOW TABLES;’;2、修改表名,代码为:‘ALTER TABLE 旧表名 RENAME [TO] 新表名;’。php中文网还提供MySQL的相关下载、相关课程等内容,供大家免费下载使用。

686

2023.06.20

MySQL创建存储过程
MySQL创建存储过程

存储程序可以分为存储过程和函数,MySQL中创建存储过程和函数使用的语句分别为CREATE PROCEDURE和CREATE FUNCTION。使用CALL语句调用存储过程智能用输出变量返回值。函数可以从语句外调用(通过引用函数名),也能返回标量值。存储过程也可以调用其他存储过程。php中文网还提供MySQL创建存储过程的相关下载、相关课程等内容,供大家免费下载使用。

513

2023.06.21

mongodb和mysql的区别
mongodb和mysql的区别

mongodb和mysql的区别:1、数据模型;2、查询语言;3、扩展性和性能;4、可靠性。本专题为大家提供mongodb和mysql的区别的相关的文章、下载、课程内容,供大家免费下载体验。

287

2023.07.18

mysql密码忘了怎么查看
mysql密码忘了怎么查看

MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS 应用软件之一。那么mysql密码忘了怎么办呢?php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

519

2023.07.19

mysql创建数据库
mysql创建数据库

MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS 应用软件之一。那么mysql怎么创建数据库呢?php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

267

2023.07.25

mysql默认事务隔离级别
mysql默认事务隔离级别

MySQL是一种广泛使用的关系型数据库管理系统,它支持事务处理。事务是一组数据库操作,它们作为一个逻辑单元被一起执行。为了保证事务的一致性和隔离性,MySQL提供了不同的事务隔离级别。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

392

2023.08.08

sqlserver和mysql区别
sqlserver和mysql区别

SQL Server和MySQL是两种广泛使用的关系型数据库管理系统。它们具有相似的功能和用途,但在某些方面存在一些显著的区别。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

542

2023.08.11

mysql忘记密码
mysql忘记密码

MySQL是一种关系型数据库管理系统,关系数据库将数据保存在不同的表中,而不是将所有数据放在一个大仓库内,这样就增加了速度并提高了灵活性。那么忘记mysql密码我们该怎么解决呢?php中文网给大家带来了相关的教程以及其他关于mysql的文章,欢迎大家前来学习阅读。

666

2023.08.14

C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

3

2026.03.11

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
MySQL 教程
MySQL 教程

共48课时 | 2.5万人学习

MySQL 初学入门(mosh老师)
MySQL 初学入门(mosh老师)

共3课时 | 0.3万人学习

简单聊聊mysql8与网络通信
简单聊聊mysql8与网络通信

共1课时 | 846人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号