Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hibernate does not delete orphans on OneToMany

I have the following pretty simple one to many relations:

Team has a Set of players:

@Entity(name = "TEAM")
@Access(AccessType.PROPERTY)
public class Team{
    private Integer id;
    private String name;
    private Set<Player> players ;

    @Id
    @Column(name = "id")
    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    @Column(name = "team_name")
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @OneToMany(cascade = {CascadeType.ALL},orphanRemoval=true)
    @JoinColumn(name = "TEAM_ID")
    public Set<Player> getPlayers() {
        return players;
    }

    public void setPlayers(Set<Player> players) {
        this.players = players;
    }       
}

And each player has a unique id & name.

@Entity(name = "PLAYER")
@Access(AccessType.PROPERTY)
public class Player implements Serializable{

    private int id;
    private String name;

    @Id
    @Column(name = "id")
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }

    @Column(name = "player_name")
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public boolean equals(Object obj) {
    return id == ((Player)obj).id;
    }
    @Override
    public int hashCode() {
        return id;
    }
}

I run a very simple code:

Team team = createTeam(3) // creates team with 3 players ids={1,2,3}
session.saveOrUpdate(team);
...

private Team createTeam(int players) {
    Team team = new Team();
    team.setName("Bears");
    team.setId(1);
    for(int i=1 ; i<=players; ++ i){
        Player player = new Player();
        player.setId(i);
        player.setName("Player"+i);
        team.addPlayer(player);
    }
    return team;
}

And I get the following as expected:

  • Hibernate: select team_.id, team_.team_name as team2_0_ from TEAM team_ where team_.id=?
  • Hibernate: select player_.id, player_.player_name as player2_1_ from PLAYER player_ where player_.id=?
  • Hibernate: select player_.id, player_.player_name as player2_1_ from PLAYER player_ where player_.id=?
  • Hibernate: select player_.id, player_.player_name as player2_1_ from PLAYER player_ where player_.id=?
  • Hibernate: insert into TEAM (team_name, id) values (?, ?)
  • Hibernate: insert into PLAYER (player_name, id) values (?, ?)
  • Hibernate: insert into PLAYER (player_name, id) values (?, ?)
  • Hibernate: insert into PLAYER (player_name, id) values (?, ?)
  • Hibernate: update PLAYER set TEAM_ID=? where id=? Hibernate: update PLAYER set TEAM_ID=? where id=? Hibernate: update PLAYER set TEAM_ID=? where id=?

Then later I do:

Team team = createTeam(2) // creates team with 2 player ids={1,2}
session.saveOrUpdate(team);

And expect the orphan players to be deleted but I get:

  • Hibernate: select team_.id, team_.team_name as team2_0_ from TEAM team_ where team_.id=?
  • Hibernate: select player_.id, player_.player_name as player2_1_ from PLAYER player_ where player_.id=?
  • Hibernate: select player_.id, player_.player_name as player2_1_ from PLAYER player_ where player_.id=?
  • Hibernate: update PLAYER set TEAM_ID=null where TEAM_ID=?
  • Hibernate: update PLAYER set TEAM_ID=? where id=?
  • Hibernate: update PLAYER set TEAM_ID=? where id=?

Which leaves the orphan player (id=3) disconnected but not deleted... Any ideas what I do wrong?

like image 859
Guy Korland Avatar asked Oct 06 '22 13:10

Guy Korland


2 Answers

If you want that the players are removed as delete-orphan, you need that the players loose the reference to the team and the save the team.

What you are doing is the following:

  • Create a new object team.
  • Feed the team with 3 players
  • Persist

After that, each player row will contain a FK to team (id=1).

Then the code creates a new team, with the same id, and feed 2 players and persist.

At that point there still will be a player in DB that references to team 1.

From my POV every different business object should have their own business key. If you want to overwrite the players of team 1, you should first retrieve team where id = 1, and then feed the players.

private Team createTeam(int players) {
    Team team = session.get(Team.class, 1);
    if (team == null) {
       team = new Team();
       team.setName("Bears");
       team.setId(1);
    }
    team.clearPlayers();

    for(int i=1 ; i<=players; ++ i){
        Player player = new Player();
        player.setId(i);
        player.setName("Player"+i);
        team.addPlayer(player);
    }
    return team;
}

// Team.java
private void clearPlayers() {
   players.clear();
}

BTW, another advice. Don't allow to modify your players directly, that can lead to HibernateErrors such us "Don't change the reference to a collection...". Instead of setPlayers(), add methods for addPlayer() and removePlayer()

private void adddPlayer(Player player) {
   player.setTeam(this);
   players.add(player);
}

private void removePlayer(Player player) {
   player.setTeam(null);
   players.remove(player);
}

Also, a collection is mutable, so make getPlayers() to return a non-modifiable collection:

private Set<Player> getPlayers() {
   return Collections.unmodifiableSet(players);
}

Hope this sheds some light :)

like image 90
Diego Pino Avatar answered Oct 10 '22 02:10

Diego Pino


You could use this tag: @org.hibernate.annotations.Cascade(value = org.hibernate.annotations.CascadeType.DELETE_ORPHAN). So you get:

@OneToMany(cascade = {CascadeType.ALL},orphanRemoval=true) @org.hibernate.annotations.Cascade(value = org.hibernate.annotations.CascadeType.DELETE_ORPHAN) @JoinColumn(name = "TEAM_ID")

like image 37
anfilbiblio Avatar answered Oct 10 '22 01:10

anfilbiblio