Spring JPA ManyToMany with additional table persists with null id

Tags: , , ,



I have these entities:

@Entity
@Data
@NoArgsConstructor
public class Hero {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    @NotNull
    private String name;
    @OneToMany(mappedBy = "character", cascade = CascadeType.ALL, orphanRemoval = true)
    @NotEmpty
    private List<HeroSkill> heroSkills;
}

@Entity
@Data
@NoArgsConstructor
public class Skill {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    @Column(nullable = false)
    private String name;
}

@Entity
@Data
@NoArgsConstructor
public class HeroSkill {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    @ManyToOne
    @JoinColumn(name = "hero_id", referencedColumnName = "id")
    @JsonIgnore
    private Hero hero;
    @ManyToOne
    @JoinColumn(name = "skill_id", nullable = false)
    private Skill skill;
    private int ranks;
}

My HeroService is like this:

@Transactional
public void create(Hero hero) {
    heroRepository.save(hero);
}

I am using postman to create a new Hero and this is a sample POST request:

{
    "name": "Test",
    "heroSkills": [
        {
            "skill": {
                "id": 1
            },
            "ranks": 4
        },
        {
            "skill": {
                "id": 2
            },
            "ranks": 4
        }
    ]
}

Although a hero is created and two entities on HeroSkill table are also created, the HeroSkill hero_id column is null, so my new heroes are not associated with their skills as they should be.

It works if I modify my service like this:

@Transactional
public void save(Hero hero) {
    Hero result = heroRepository.save(hero);
    hero.getHeroSkills().stream()
            .forEach(it -> {
                it.setHero(result);
                heroSkillRepository.save(it);
            });
}

But I think that I shouldn’t have to pass the Hero manually to each HeroSkill. Isn’t that the point of adding CascateType.ALL (which includes CascadeType.PERSIST)?

What is the correct approach to that?

Thanks in advance.

Answer

There is no reference of Hero(Parent) in HeroSkill(child) that’s why JPA can’t resolve hero_id and set as null. Use this heroSkill.setHero(hero) to set parent in child.

To save child with parent in bidirectional relation you have to make sync both side.

 hero.getHeroSkills().stream()
            .forEach(it -> {
                it.setHero(hero);
            });
Hero result = heroRepository.save(hero);


Source: stackoverflow