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
相关产品推荐
相关产品推荐

