Hibernate不删除OneToMany中的孤儿记录

5

我有以下相对简单的一对多关系:

团队拥有一组球员:

@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;
    }       
}

每个玩家都有一个独特的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;
    }
}

我运行了一个非常简单的代码:

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;
}

我得到了以下预期的结果:
  • Hibernate:从TEAM team_中选择team_.id,team_.team_name作为team2_0_ where team_.id=?
  • Hibernate:从PLAYER player_中选择player_.id,player_.player_name作为player2_1_ where player_.id=?
  • Hibernate:从PLAYER player_中选择player_.id,player_.player_name作为player2_1_ where player_.id=?
  • Hibernate:从PLAYER player_中选择player_.id,player_.player_name作为player2_1_ where player_.id=?
  • Hibernate:将(team_name, id)值(?, ?)插入到TEAM中
  • Hibernate:将(player_name, id)值(?, ?)插入到PLAYER中
  • Hibernate:将(player_name, id)值(?, ?)插入到PLAYER中
  • Hibernate:将(player_name, id)值(?, ?)插入到PLAYER中
  • Hibernate:更新PLAYER set TEAM_ID=? where id=? Hibernate:更新PLAYER set TEAM_ID=? where id=? Hibernate:更新PLAYER set TEAM_ID=? where id=?
然后稍后我执行:
Team team = createTeam(2) // creates team with 2 player ids={1,2}
session.saveOrUpdate(team);

我期望孤儿球员被删除,但是我得到了以下内容:
Hibernate:从TEAM表中选择team_.id,team_.team_name作为team2_0_的值,条件为team_.id=?
Hibernate:从PLAYER表中选择player_.id,player_.player_name作为player2_1_的值,条件为player_.id=?
Hibernate:从PLAYER表中选择player_.id,player_.player_name作为player2_1_的值,条件为player_.id=?
Hibernate:将PLAYER表中的TEAM_ID设置为null,条件为TEAM_ID=?
Hibernate:将PLAYER表中的TEAM_ID设置为给定值,条件为id=?
Hibernate:将PLAYER表中的TEAM_ID设置为给定值,条件为id=?
这导致孤儿球员(id = 3)未被删除,而是被断开连接...
有什么想法我做错了什么吗?

1
createTeam() 方法是做什么的? - dcernahoschi
可能与此相关,也可能不相关,但我们在HSQL上进行了测试。 - Guy Korland
好的,但我们能看到createTeam()函数内部的代码吗? - dcernahoschi
刚刚更新了代码示例,添加了createTeam()函数。 - Guy Korland
3个回答

3
如果您希望将玩家作为孤儿删除,那么您需要让玩家失去与团队的关联并保存团队。你所做的是:
创建一个新的团队对象;
添加三名球员;
持久化。
此时每个球员行将包含对团队 (id=1) 的外键引用。
然后代码创建一个新的团队,具有相同的 id,并添加两名球员并持久化。
此时仍然存在一个参考团队 1 的球员。
在我看来,每个不同的业务对象都应该有自己的业务键。如果您想覆盖团队 1 的球员,您应该首先检索 id = 1 的团队,然后再添加球员。
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();
}

顺便提一下,另一个建议是不要直接修改您的播放器,这可能会导致HibernateErrors,例如 "不要更改对集合的引用..."。而是使用 addPlayer() removePlayer()方法来代替setPlayers()。

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

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

此外,集合是可变的,因此请确保getPlayers()返回一个不可修改的集合:
private Set<Player> getPlayers() {
   return Collections.unmodifiableSet(players);
}

希望这能给你一些启示 :)

1
你可以使用这个标签:@org.hibernate.annotations.Cascade(value = org.hibernate.annotations.CascadeType.DELETE_ORPHAN)。这样你就得到了:
@OneToMany(cascade = {CascadeType.ALL},orphanRemoval=true) @org.hibernate.annotations.Cascade(value = org.hibernate.annotations.CascadeType.DELETE_ORPHAN) @JoinColumn(name = "TEAM_ID")

0
在两个实体的关系中添加mappedBy属性。
在Player中添加Team。
// in Player.java
@ManyToOne(mappedBy="players")
private Team team;

和 Player 中的 MappeedBy。

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

当你有一对多的关系时,子表应该有一个指向其父表的引用。然后Hibernate会在子表中使用父表的ID作为外键。
你的子表应该有这三列:
id , player_name,team_id

2
没有理由使关联双向化。而且,在双向关联中,只有一侧必须具有mappedBy属性,因为它的意思是:“去查看另一侧的关联映射”。 - JB Nizet

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接