2

I'm using spring data JPA and have Player, Score entities. Player has @OneToMany relation to Score (and @ManyToOne from Score to Player). I save Player to the database without specifying related scores. After that, I save Score to the database, passing to the contructor a link to the Player. Then in the same function, I read Player entry, but it has no link to the Score entry yet.
I have FetchType.LAZY in my OneToMany relation, so I need to use annotation @Transactional in that function. I also tried to save player and score in the inner transaction(using propogation=Propogation.REQUIRES_NEW) and read the player entry in an external transaction, but it doesn't help. I also tried to add CascadeType.ALL - no effect. And i used flush() method from JpaRepository - doesn't help too.
But if I will save player and score in one controller method(using spring MVC), and read in another, the player will have a link to the score.
Edited: add code, Score in code is GamePlayerScore. All classes have lombok annotations @Getter and @ToString.

Player

@Temporal(TemporalType.DATE)
@NonNull
@Column(updatable = false)
private Date createdAt;

@Setter
@NonNull
@Column(unique = true)
private String nickname;

@OneToMany(mappedBy = "winner", cascade = CascadeType.REFRESH)
private Set<GameStatistic> gamesWon;

@OneToOne(cascade = {CascadeType.PERSIST, CascadeType.REFRESH}, optional = false)
@JoinColumn
private PlayerStatistic stat;

@OneToMany(mappedBy = "player", cascade = CascadeType.REFRESH)
private Set<GamePlayerScore> scores;

@ManyToOne
@JoinColumn
private Game currentGame;

public Player(String nickname, Date createdAt) {
    this.nickname = nickname;
    this.createdAt = createdAt;
    stat = new PlayerStatistic(0, 0, 0, this);
}

GamePlayerScore

@ManyToOne(optional = false)
@JoinColumn
@NonNull
@JsonIgnore
private GameStatistic game;

@ManyToOne(optional = false)
@JoinColumn
@NonNull
@JsonIgnore
private Player player;

@NonNull
private Integer score;


@PrePersist
private void updatePlayerStat() {
    player.getStat().incrementTotalGames();
    player.getStat().addTotalScore(score);
}

GameStatistic

@Column(updatable = false)
@NonNull
private Integer durationMinutes;

@Temporal(TemporalType.TIMESTAMP)
@NonNull
private Date startedAt;

@ManyToOne(optional = false)
@JoinColumn
@NonNull
@JsonIgnore
private Player winner;


@OneToMany(mappedBy = "game")
private Set<GamePlayerScore> scores;

@PrePersist
private void update() {
    winner.getStat().incrementTotalWins();
}

And how I store entries

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void prepossess() {
    Date date = new Date();

    Player john = new Player("john", date);
    GameStatistic game = new GameStatistic(35, new Date(), john);
    GamePlayerScore johnScore = new GamePlayerScore(game, john, 100);
    playerRepository.save(player);
    gameRepository.save(game);
    scoreRepository.save(johnScore);
}

And how I read entry

@Transactional
public void insertAndRead() {
    preprocess();
    Player player = playerRepository.findByNickname("john");
    System.out.println(player.getScores());
}
Dima Stoyanov
  • 121
  • 1
  • 11
  • Please post the code of the `Player` and `Score` classes plus the variations how you try to persist them. It is really hard to understand what is going on when reading it as prose. Also important details like the exact annotations needed easily get lost. – Jens Schauder Mar 20 '18 at 05:50
  • Ok, I added code – Dima Stoyanov Mar 20 '18 at 10:20
  • The key to the solutionis to read in the data again. The link is only filled by JPA when you load a `player` and not magically... – Uwe Plonus Mar 20 '18 at 10:45
  • What do you mean? I should read from repository again in the same function? Seems, it didn't work too – Dima Stoyanov Mar 20 '18 at 12:16

1 Answers1

2

Saving the score should update the relationship to the player in the database, once those changes are flushed.

But reloading a Player with a session that already contains that Player will bring up the old Player instance without updating it with data from the database.

So in order to see the changed Player you need to flush the session and remove the Player from the session and then reload it. Alternatively, you can just close the session.

You seem to try to achieve this by separate transactional methods. This should work in principle, but I'm almost 100% sure, that you have a transaction running across both the save and the load method. You can enable logging for transactions in order to investigate further: Spring hibernate Transaction Logging

What you really want to do though is probably to keep the two java objects in sync by writing custom setters.

Jens Schauder
  • 77,657
  • 34
  • 181
  • 348
  • Why transaction running across both operations does not allow to flush changes? I thought, that if transaction ended, all changes will be recorded. Also I tried to write transactional write method, transactional read method, and in test (non-transactional) method call both methods. But I got exception, that says "...can't lazily initialize a collection...". But I'm reading this collection in transactional method... – Dima Stoyanov Mar 20 '18 at 13:51
  • Changes get flushed, but as long as you are in a transaction/session objects don't get reloaded. Something seems to be wrong with your transaction setup because it should work when using two separate transactions. I'd recommend to activate logging to confirm that you really use one transaction and create a new question about how to fix that. – Jens Schauder Mar 20 '18 at 15:53
  • For some unknown for me reason, if I call from non-transactional method some transactional methods(from same class), actually spring will not create transactions at all – Dima Stoyanov Mar 20 '18 at 21:09
  • Ok, now I found that I can't do this because of limitation with Spring AOP – Dima Stoyanov Mar 20 '18 at 21:15