Contents

Hibernate 中@wherejointable注解简介

1. 概述

使用对象关系映射工具(如 Hibernate)可以很容易地将我们的数据读入对象,但会使复杂数据模型的查询变得困难。

多对多关系总是具有挑战性,但当我们希望 基于关系本身的某些属性获取相关实体时,它可能更具挑战性。

在本教程中,我们将研究如何使用 Hibernate 的*@WhereJoinTable*注解来解决这个问题。

2.基本*@ManyToMany*关系

让我们从一个简单的@ManyToMany 关系开始。我们需要领域模型实体、关系实体和一些示例测试数据。

2.1. 领域模型

假设我们有两个简单的实体,UserGroup,它们关联为*@ManyToMany*:

@Entity(name = "users")
public class User {
    @Id
    @GeneratedValue
    private Long id;
    private String name;
    @ManyToMany
    private List<Group> groups = new ArrayList<>();
    // standard getters and setters
}
@Entity
public class Group {
    @Id
    @GeneratedValue
    private Long id;
    private String name;
    @ManyToMany(mappedBy = "groups")
    private List<User> users = new ArrayList<>();
    // standard getters and setters
}

正如我们所见,我们的User实体可以是多个Group实体的成员。同样,一个Group实体可以包含多个User实体。

2.2. 关系实体

*对于@ManyToMany关联,我们需要一个单独的数据库表,称为关系表。**关系表需要至少包含两列:相关的UserGroup*实体的主键。

只有两个主键列,我们的 Hibernate 映射 就可以表示这个关系表。

但是,如果我们需要在关系表中放入额外的数据,我们还应该为多对多关系本身定义一个关系实体。 让我们创建UserGroupRelation类来做到这一点:

@Entity(name = "r_user_group")
public class UserGroupRelation implements Serializable {
    @Id
    @Column(name = "user_id", insertable = false, updatable = false)
    private Long userId;
    @Id
    @Column(name = "group_id", insertable = false, updatable = false)
    private Long groupId;
}

在这里,我们将实体命名为r_user_group,以便稍后引用它。

对于我们的额外数据,假设我们要为每个Group存储每个User的角色。因此,我们将创建UserGroupRole枚举:

public enum UserGroupRole {
    MEMBER, MODERATOR
}

接下来,我们将向UserGroupRelation添加一个role属性:

@Enumerated(EnumType.STRING)
private UserGroupRole role;

最后,为了正确配置它,我们需要在Usergroups集合上添加*@JoinTable注解。在这里,我们将使用r_user_group指定连接表名称,即UserGroupRelation*的实体名称:

@ManyToMany
@JoinTable(
    name = "r_user_group",
    joinColumns = @JoinColumn(name = "user_id"),
    inverseJoinColumns = @JoinColumn(name = "group_id")
)
private List<Group> groups = new ArrayList<>();

2.3. 样本数据

对于我们的集成测试,让我们定义一些示例数据:

public void setUp() {
    session = sessionFactory.openSession();
    session.beginTransaction();
    
    user1 = new User("user1");
    user2 = new User("user2");
    user3 = new User("user3");
    group1 = new Group("group1");
    group2 = new Group("group2");
    session.save(group1);
    session.save(group2);
    session.save(user1);
    session.save(user2);
    session.save(user3);
    saveRelation(user1, group1, UserGroupRole.MODERATOR);
    saveRelation(user2, group1, UserGroupRole.MODERATOR);
    saveRelation(user3, group1, UserGroupRole.MEMBER);
    saveRelation(user1, group2, UserGroupRole.MEMBER);
    saveRelation(user2, group2, UserGroupRole.MODERATOR);
}
private void saveRelation(User user, Group group, UserGroupRole role) {
    UserGroupRelation relation = new UserGroupRelation(user.getId(), group.getId(), role);
    
    session.save(relation);
    session.flush();
    session.refresh(user);
    session.refresh(group);
}

正如我们所见,user1user2在两个组中。此外,我们应该注意到,虽然user1group1上是MODERATOR,但同时在group2上具有MEMBER角色。

3. 获取*@ManyToMany*关系

现在我们已经正确配置了实体,让我们获取User实体的组。

3.1.简单获取

为了获取组,我们可以简单地在活动的 Hibernate 会话中调用User的*getGroups()*方法:

List<Group> groups = user1.getGroups();

我们的输出将是:

[Group [name=group1], Group [name=group2]]

但是我们如何才能获得组角色仅为MODERATOR 的用户的组呢?

3.2. 关系实体上的自定义过滤器

我们可以使用*@WhereJoinTable*注解直接获取过滤后的组。

让我们定义一个新属性为moderatorGroups并在其上添加 @WhereJoinTable注解。当我们通过这个属性访问相关实体时,它只会包含我们的用户是MODERATOR 的组。

我们需要添加一个 SQL where 子句来按MODERATOR角色过滤组:

@WhereJoinTable(clause = "role='MODERATOR'")
@ManyToMany
@JoinTable(
    name = "r_user_group",
    joinColumns = @JoinColumn(name = "user_id"),
    inverseJoinColumns = @JoinColumn(name = "group_id")
)
private List<Group> moderatorGroups = new ArrayList<>();

因此,我们可以轻松获取应用了指定 SQL where 子句的组:

List<Group> groups = user1.getModeratorGroups();

我们的输出将是用户仅具有MODERATOR 角色的组:

[Group [name=group1]]