You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot中嵌套List<List<Entity>>存储问题及优化方案咨询

问题描述

需要将如下嵌套结构的JSON数据存储到Spring Boot的TripItinerary实体类中:

{
    "1": [
        {
           "id":"1"
        },
        {
           "id":"2"
        }
    ],
    "2": [
        {
           "id":"2"
        },
        {
           "id":"3"
        },
        {
           "id":"4"
        }
    ]
}

(注:原JSON格式有误,外层应为对象{}而非数组[],因为包含键值对结构)

当前TripItinerary实体定义如下:

public class TripItinerary {
    @Id
    private Long id;

    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinColumn(name = "trip_itinerary_id")
    private List<List<Place>> itinerary;
}

其中Place是对应place表的实体类。

遇到的错误:'One To Many'属性值类型不应为'List'——因为@OneToMany只能关联实体类,无法直接绑定嵌套集合。曾尝试创建包含List<Place>的新实体,但该实体无单独使用场景,觉得冗余,希望找到更优处理方式。


解决方案

方案1:用@ElementCollection+自定义转换器(贴合JSON结构)

如果不需要对内层List<Place>做数据库级查询,可直接用Map<Integer, List<Place>>匹配原JSON的键值结构,通过转换器将内层列表序列化为JSON字符串存储:

public class TripItinerary {
    @Id
    private Long id;

    @ElementCollection
    @CollectionTable(name = "trip_itinerary_day", joinColumns = @JoinColumn(name = "trip_itinerary_id"))
    @MapKeyColumn(name = "day_order") // 对应JSON中的1、2等数字键
    @Column(name = "places")
    @Convert(converter = PlacesListConverter.class)
    private Map<Integer, List<Place>> itinerary;
}

实现转换器类:

import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.List;

@Converter(autoApply = true)
public class PlacesListConverter implements AttributeConverter<List<Place>, String> {
    private final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public String convertToDatabaseColumn(List<Place> places) {
        try {
            return objectMapper.writeValueAsString(places);
        } catch (JsonProcessingException e) {
            throw new RuntimeException("序列化Place列表失败", e);
        }
    }

    @Override
    public List<Place> convertToEntityAttribute(String dbData) {
        try {
            return objectMapper.readValue(dbData, objectMapper.getTypeFactory().constructCollectionType(List.class, Place.class));
        } catch (JsonProcessingException e) {
            throw new RuntimeException("反序列化Place列表失败", e);
        }
    }
}

这种方式无需额外实体类,结构贴合原JSON,适合仅需整体读写的场景。

方案2:直接序列化整个嵌套集合(最简方案)

如果完全不需要对内层集合做数据库查询,可直接把List<List<Place>>序列化为JSON字符串存在单个字段:

public class TripItinerary {
    @Id
    private Long id;

    @Column(columnDefinition = "TEXT")
    @Convert(converter = NestedPlacesListConverter.class)
    private List<List<Place>> itinerary;
}

实现对应转换器:

import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.List;

@Converter(autoApply = true)
public class NestedPlacesListConverter implements AttributeConverter<List<List<Place>>, String> {
    private final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public String convertToDatabaseColumn(List<List<Place>> itinerary) {
        try {
            return objectMapper.writeValueAsString(itinerary);
        } catch (JsonProcessingException e) {
            throw new RuntimeException("序列化行程失败", e);
        }
    }

    @Override
    public List<List<Place>> convertToEntityAttribute(String dbData) {
        try {
            return objectMapper.readValue(dbData, 
                objectMapper.getTypeFactory().constructCollectionType(List.class, 
                    objectMapper.getTypeFactory().constructCollectionType(List.class, Place.class)));
        } catch (JsonProcessingException e) {
            throw new RuntimeException("反序列化行程失败", e);
        }
    }
}

这是最简洁的方式,无需额外表结构,缺点是无法通过数据库查询内层的Place数据。

方案3:关联中间实体(规范JPA方式,适合需扩展场景)

如果后续可能需要对内层列表做单独查询,建议创建中间实体(即使暂时无单独使用场景,也能保证结构规范性和扩展性):

// 中间实体:每日行程
@Entity
@Table(name = "itinerary_day")
public class ItineraryDay {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private Integer dayOrder; // 对应JSON中的1、2

    @ManyToOne
    @JoinColumn(name = "trip_itinerary_id")
    private TripItinerary tripItinerary;

    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinColumn(name = "itinerary_day_id")
    private List<Place> places;

    // getter、setter
}

修改TripItinerary实体:

public class TripItinerary {
    @Id
    private Long id;

    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "tripItinerary")
    private List<ItineraryDay> itinerary;
}

这种方式符合JPA规范,支持对每日的Place列表做单独查询,长期维护更合理。


内容的提问来源于stack exchange,提问作者hnbm

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 16:13:20