简体   繁体   中英

spring+hibernate why is many-to-many relationship not persisted?

I have an entity E1 defined as

@Entity
public class E1 {
    @Id
    @GeneratedValue
    public long id;
    @ManyToMany(cascade={CascadeType.PERSIST, CascadeType.MERGE})
    @JoinTable(
            name="e1_e2",
            joinColumns = @JoinColumn(name = "e2_id"),
            inverseJoinColumns = @JoinColumn(name = "e1_id")
    )
    public Set<E2> e2s = new HashSet<>();
}

an entity E2 defined as

@Entity
public class E2 {
    @Id
    @GeneratedValue
    public long id;
    @ManyToMany(mappedBy = "e2s")
    public Set<E1> e1s = new HashSet<>();
}

and controller defined as

@RestController
@RequestMapping("/")
public class C1 {
    private final E1Repository e1Repository;
    private final E2Repository e2Repository;

    @PersistenceContext
    EntityManager em;

    @Autowired
    public C1(E1Repository e1Repository, E2Repository e2Repository) {
        this.e1Repository = e1Repository;
        this.e2Repository = e2Repository;
    }

    @Transactional
    @RequestMapping(method = POST)
    public void c(){
        E1 e1 = new E1();
        E2 e2 = new E2();

        e1Repository.save(e1);
        e2Repository.save(e2);

        em.refresh(e1);
        em.refresh(e2);

        e1.e2s.add(e2);
        e2.e1s.add(e1);

        e1Repository.save(e1);
        e2Repository.save(e2);

        em.refresh(e1);
        em.refresh(e2);
    }

}

( E1Repository and E2Repository are @Repository annotated interfaces that extend JpaRepository and have empty bodies).

when I step through the c method in my debugger, I see that after the last two em.refresh lines, both e1 and e2 have their sets cleared.

based on other questions I found on Stack Overflow, I tried defining E2 as

@Entity
public class E2 {
    @Id
    @GeneratedValue
    public long id;
    @ManyToMany(cascade = CascadeType.PERSIST)
    @JoinTable(
            name="e1_e2",
            inverseJoinColumns = @JoinColumn(name = "e2_id"),
            joinColumns = @JoinColumn(name = "e1_id")
    )
    public Set<E1> e1s = new HashSet<>();
}

but this did not help.

Original question (from before I tried testing the simplified case above) I have a class Robot defined similar to the following:

@Entity
class Robot{
    @Id
    @GeneratedValue
    private long id;

    @ManyToMany(mappedBy="robots")
    private Set<Match> matches = new HashSet<>();
}

and a class Match similar to

@Entity
class Robot{
    @Id
    @GeneratedValue
    private long id;
    @ManyToMany(cascade={CascadeType.Persist,CascadeType.Merge})
    @JoinTable(
        name = "match_robot",
        joinColumns = {@JoinColumn(name = "Match_id")},
        inverseJoinColumns = {@JoinColumn(name = "Robot_id")}
)
private Set<Robot> robots = new HashSet<>();

and a class Result defined similar to

@Entity
public class Result {
    @Id
    @GeneratedValue
    private long id;

    @ManyToOne(optional = false)
    private Match match;

    @ManyToOne(optional = false)
    @NotNull(groups = {Default.class, Creating.class})
    private Robot robot;
}

and attempt to save the relationship as follows:

resultRepository.save(result);
match.getRobots().add(result.getRobot());
result.getRobot().getMatches().add(match);
robotRepository.save(result.getRobot());
matchRepository.save(match);
entityManager.refresh(result);
entityManager.refresh(result.getRobot());
entityManager.refresh(match);

where *Repository are beans created by spring that implement JpaRepository. When this code is ran, an insert statement is ran by hibernate for the result object (hibernate outputs all of its sql commands to the console), but none for the "match_robot" table. I should not that, before this snippet, result is in the transient state of the hibernate entity lifecycle, match is in the persisted state, robot is in the persisted state, and result 's match and robot properties have been set to match and robot respectively.

Based on other questions on Stack Overflow and other sites, I have also tried defining the matches variable as

@ManyToMany(cascade = CascadeType.PERSIST)
@JoinTable(
        name = "match_robot",
        inverseJoinColumns = {@JoinColumn(name = "Match_id")},
        joinColumns = {@JoinColumn(name = "Robot_id")}
)
private Set<Match> matches = new HashSet<>();

which did not help. Other than that, the only recommendation I was seeing was to make sure both entities in the many-to-many relationship were consistant with each other before persisting, but, as far as I can tell, I am doing so here.

How can I persist this relationship?

Edit: the full method for context:

@Transactional
@RequestMapping(value = "/{match:[0-9]+}/results", method = RequestMethod.POST)
public ResultResource createResult(@PathVariable Match match,
                                   @Validated(Result.Creating.class)
                                   @RequestBody Result result) {
    if (match == null) throw new ResourceNotFoundException();

    result.setScorecard(scorecardRepository
            .findById(result.getScorecard().getId()));
    if (result.getScorecard() == null) {
        throw new ScorecardDoesNotExistException();
    }

    result.setMatch(match);

    //remove null scores
    result.getScores().removeIf(
            fieldResult -> fieldResult.getScore() == null
    );

    //replace transient robot with entity from database
    Robot existingRobot = robotRepository
            .findByNumberAndGame(result.getRobot().getNumber(),result.getRobot().getGame());
     if (existingRobot == null) { //create new robot
        //find team for this robot
        Team existingTeam = teamRepository
                .findByNumberAndGameType(
                        result.getRobot().getNumber(),
                        result.getScorecard().getGame().getType());
        if (existingTeam == null) {
            Team team = new Team();
            team.setNumber(result.getRobot().getNumber());
            team.setGameType(result.getMatch().getEvent().getGame().getType());
            team.setDistrict(result.getMatch().getEvent().getDistrict());
            teamRepository.save(team);
            result.getRobot().setTeam(team);
        }
    else result.getRobot().setTeam(existingTeam);
             result.getRobot().setGame(result.getMatch().getEvent().getGame());

        robotRepository.save(result.getRobot());
        entityManager.refresh(result.getRobot());
    } else result.setRobot(existingRobot);
    List<Robot> all = robotRepository.findAll();

    //replace transient FieldSections with entities from database
    //todo: reduce database hits
    //noinspection ResultOfMethodCallIgnored
    result.getScores().stream()
          .peek(fieldResult -> fieldResult.setField(
                  fieldSectionRepository.findByIdAndScorecard(
                          fieldResult.getField().getId(),
                          result.getScorecard())))
              .peek(fieldResult -> {
              if (fieldResult.getField() == null)
                  throw new ScoresDoNotExistException();
          })
          .forEach(fieldResult->fieldResult.setResult(result));


    if (!result.scoresMatchScorecardSections()) {
        throw new ScoresDoNotMatchScorecardException();
    }

    if (!result.allMissingScoresAreOptional()) {
        throw new RequiredScoresAbsentException();
    }

    if (!result.gameMatchesScorecard()) {
        throw new GameDoesNotMatchScorecardException();
    }

    resultRepository.save(result);
    match.getRobots().add(result.getRobot());
    result.getRobot().getMatches().add(match);
    robotRepository.save(result.getRobot());
    matchRepository.save(match);
    entityManager.refresh(result);
    entityManager.refresh(result.getRobot());
    entityManager.refresh(match);
    return new ResultResourceAssembler().toResource(result);
}

JpaRepository.save() does not flush the changes to the database. It is necessary to flush the changes with JpaRepository.flush() or JpaRepository.saveAndFlush() .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM