如何在Spring Boot中存储含对象列表的Map到PostgreSQL数据库
解决方案
方案一:使用PostgreSQL JSONB存储
如果坚持用JSONB格式存储,需要让JPA支持HashMap与JSONB的转换,步骤如下:
1. 添加依赖
引入Hibernate Types库处理JSONB类型(Spring Boot 2.x用hibernate-types-52,Spring Boot 3.x用hibernate-types-60):
<!-- Maven依赖 --> <dependency> <groupId>com.vladmihalcea</groupId> <artifactId>hibernate-types-52</artifactId> <version>2.20.0</version> </dependency>
2. 修改实体类
给mapRang字段添加注解,指定JSONB类型,建议只存储PeriodeProtocolaireEntity的ID而非完整实体,避免冗余:
@Entity @Table(name = "classements") @TypeDef(name = "jsonb", typeClass = JsonBinaryType.class) // 注册JSONB类型 public class ClassementEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id_classement") private Long id_classement; @ManyToOne @JoinColumn(name = "id_agent") private AgentEntity agent; @Column(name = "max_rank") private Integer max_rank; @Type(type = "jsonb") @Column(name = "map_rang", columnDefinition = "jsonb") private HashMap<Integer, List<Long>> mapRang; // 存储Periode的ID列表 // Getters and Setters }
3. 操作示例
保存时直接赋值HashMap:
HashMap<Integer, List<Long>> rankMap = new HashMap<>(); rankMap.put(1, Arrays.asList(1L, 2L)); rankMap.put(2, Collections.singletonList(3L)); ClassementEntity classement = new ClassementEntity(); classement.setMapRang(rankMap); classementRepository.save(classement);
查询后通过ID列表关联获取实体:
ClassementEntity classement = classementRepository.findById(1L).orElseThrow(); List<Long> periodeIds = classement.getMapRang().get(1); List<PeriodeProtocolaireEntity> periodes = periodeProtocolaireRepository.findAllById(periodeIds);
方案二:关系型数据库规范化设计(更优)
对于需要频繁查询、更新排名,或依赖排名做复杂业务逻辑的场景,推荐用规范化表结构,避免JSONB的局限性:
1. 创建中间表
新增classement_periode表存储排名与Periode的关联:
CREATE TABLE classement_periode ( id SERIAL PRIMARY KEY, id_classement BIGINT REFERENCES classements(id_classement), rank INTEGER NOT NULL, id_periode BIGINT REFERENCES periode_protocolaire(id_periode), UNIQUE(id_classement, rank, id_periode) -- 避免重复关联 );
2. 定义关联实体
创建ClassementPeriodeEntity作为中间实体:
@Entity @Table(name = "classement_periode") public class ClassementPeriodeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne @JoinColumn(name = "id_classement") private ClassementEntity classement; @Column(name = "rank") private Integer rank; @ManyToOne @JoinColumn(name = "id_periode") private PeriodeProtocolaireEntity periode; // Getters and Setters }
3. 修改ClassementEntity
移除mapRang,添加与中间实体的关联:
@Entity @Table(name = "classements") public class ClassementEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id_classement") private Long id_classement; @ManyToOne @JoinColumn(name = "id_agent") private AgentEntity agent; @Column(name = "max_rank") private Integer max_rank; @OneToMany(mappedBy = "classement", cascade = CascadeType.ALL, orphanRemoval = true) private List<ClassementPeriodeEntity> periodeRankings = new ArrayList<>(); // 便捷添加关联的方法 public void addPeriodeToRank(PeriodeProtocolaireEntity periode, Integer rank) { ClassementPeriodeEntity ranking = new ClassementPeriodeEntity(); ranking.setClassement(this); ranking.setPeriode(periode); ranking.setRank(rank); periodeRankings.add(ranking); } // Getters and Setters }
4. 操作示例
添加排名关联:
ClassementEntity classement = new ClassementEntity(); PeriodeProtocolaireEntity periode1 = periodeProtocolaireRepository.findById(1L).orElseThrow(); PeriodeProtocolaireEntity periode2 = periodeProtocolaireRepository.findById(2L).orElseThrow(); classement.addPeriodeToRank(periode1, 1); classement.addPeriodeToRank(periode2, 1); classementRepository.save(classement);
查询指定排名的Periode:
List<ClassementPeriodeEntity> rankings = classementPeriodeRepository.findByClassementIdAndRank(1L, 1); List<PeriodeProtocolaireEntity> periodes = rankings.stream() .map(ClassementPeriodeEntity::getPeriode) .collect(Collectors.toList());
方案对比
- JSONB方案:结构灵活,适合快速开发、排名结构不常变更的场景;但难以做复杂查询,更新单个关联关系时需修改整个JSON,性能较差。
- 规范化方案:符合关系型数据库设计,支持复杂查询、事务操作,维护性强;开发初期稍繁琐,但长期更适配业务扩展。
内容的提问来源于stack exchange,提问作者axxa
相关产品推荐
相关产品推荐

