Spring Data JPA多对多关联含额外列问题:关联表无数据写入
你遇到的核心问题是双向关联关系未正确维护,以及缺少必要的级联配置,导致JPA无法自动将HeroDnDClasses实例持久化到关联表中。
问题根源
关联关系拥有方未设置关联对象:
在你的映射中,HeroDnDClasses是关联关系的拥有方(因为它带有@JoinColumn注解,而Hero的@OneToMany使用了mappedBy="hero",表示由HeroDnDClasses的hero字段维护关联)。你在创建HeroDnDClasses时只设置了dnDClass和level,但没有把newHero赋值给它的hero属性,JPA无法识别这两者的关联关系。缺少级联操作配置:
Hero的@OneToMany注解没有配置cascade属性,因此当你保存Hero时,JPA不会自动保存关联的HeroDnDClasses实例。
分步解决方案
1. 为Hero的关联添加级联配置
修改Hero类中的@OneToMany注解,添加cascade = CascadeType.PERSIST(或者CascadeType.ALL,根据你的业务需求选择):
@OneToMany(mappedBy = "hero", cascade = CascadeType.PERSIST) private List<HeroDnDClasses> heroDnDClasses = new ArrayList<>();
CascadeType.PERSIST:当保存Hero时,自动保存关联的HeroDnDClasses实例。- 如果需要支持更新、删除等操作,可以使用
CascadeType.ALL。
2. 正确维护双向关联关系
修改HeroService中的addHero方法,确保在创建HeroDnDClasses时设置对应的Hero实例,同时将HeroDnDClasses添加到Hero的关联列表中:
@Transactional public void addHero(Hero hero){ DnDClass newDnDClass = dnDClassService.findDnDClassById(hero.getHeroDnDClasses().get(0).getDnDClass().getId()); Race newRace = raceService.getRaceById(hero.getRace().getId()); // 先创建Hero基础实例 Hero newHero = Hero.builder() .setName(hero.getName()) .setStrength(hero.getStrength()) .setDexterity(hero.getDexterity()) .setConstitution(hero.getConstitution()) .setIntelligence(hero.getIntelligence()) .setWisdom(hero.getWisdom()) .setCharisma(hero.getCharisma()) .setRace(newRace) .build(); // 创建关联实体并设置双向关联 HeroDnDClasses newHeroDnDClasses = new HeroDnDClasses(); newHeroDnDClasses.setHero(newHero); // 关键:设置拥有方的关联对象 newHeroDnDClasses.setDnDClass(newDnDClass); newHeroDnDClasses.setLevel(hero.getHeroDnDClasses().get(0).getLevel()); // 将关联实体添加到Hero的列表中 newHero.getHeroDnDClasses().add(newHeroDnDClasses); // 保存Hero,级联保存关联的HeroDnDClasses heroRepository.save(newHero); }
3. 可选:为HeroDnDClasses实现equals和hashCode
由于HeroDnDClasses使用复合主键(hero和dnDClass),为了避免后续查询、缓存或集合操作出现问题,建议实现equals和hashCode方法:
import java.util.Objects; // ... 其他代码 ... @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; HeroDnDClasses that = (HeroDnDClasses) o; // 注意:这里要使用id而非对象本身,避免懒加载导致的NPE return Objects.equals(hero != null ? hero.getId() : null, that.hero != null ? that.hero.getId() : null) && Objects.equals(dnDClass != null ? dnDClass.getId() : null, that.dnDClass != null ? that.dnDClass.getId() : null); } @Override public int hashCode() { return Objects.hash(hero != null ? hero.getId() : null, dnDClass != null ? dnDClass.getId() : null); }
验证
完成以上修改后,重新运行代码,保存Hero时,HERO_DNDCLASSES表应该会生成对应的关联记录了。
内容的提问来源于stack exchange,提问作者Paris Karagiannopoulos

