Hibernate JPA 关联关系

Hibernate JPA 关联关系

关联关系从整体上分为单向关联和双向关联:

  1. 单向关联:只需从一端访问另一端,如教师Teacher可访问学生Student,则Teacher实体需要包含类型为Student的属性
  2. 双向关联:两端均可互相访问,如教师Teacher可访问学生Student,学生Student也可访问教师Teacher,两个实体均需要包含类型为对方的属性

1、一对一关联映射

1.1、单向一对一

单向1-1:需要在控制关系的一方实体中使用注解 @OneToOne 和 @JoinColumn 标注类型为对方的属性。如Person端为控制关系的一方,只需要在Person控制方加这两个注解即可。反端既不用配置属性字段也不用配置注解

单向一对一是关联关系映射中最简单的一种,简单地说就是可以从关联的一方去查询另一方,却不能反向查询。我们用下面的例子来举例说明,清单 1 中的 Person 实体类和清单 2 中的 Address 类就是这种单向的一对一关系,我们可以查询一个 Person 的对应的 Address 的内容,但是我们却不能由一个 Address 的值去查询这个值对应的 Person

  1. tb_person_address(外键表):address_id,street,city,country,person_id
  2. tb_person(主键表):person_id,username

清单 1:单向一对一关系的拥有端(正端)

@Data
@Entity
@Table(name = "tb_person")
public class Person implements Serializable {
   private static final long serialVersionUID = 1L;
   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   @Column(name = "person_id")
   private Long personId;
   private String username;
   /**
    * @JoinColumn
    *     name: person_address表外键字段名(数据库字段名)
    *     referencedColumnName: person表主键字段(数据库字段名)
    */
   @OneToOne
   @JoinColumn(name = "address_id",referencedColumnName = "address_id")
   private PersonAddress personAddress;
}

清单 2:单向一对一关系的被拥有端(反端)

@Data
@Entity
@Table(name = "tb_person_address")
public class PersonAddress implements Serializable {
   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   @Column(name = "address_id")
   private Long addressId;
   private String country;
   private String city;
}

执行代码测试:

    /**
     * 运行之前,修改hibernate.hbm2ddl.auto=create
     * 单向一对一查询
     */
    @Test
    public void testOneToOne(){
        EntityManager entityManager = JpaUtils.getEntityManager();
        // 开启事务
        entityManager.getTransaction().begin();

        // 保存地址数据
        PersonAddress address = new PersonAddress();
        address.setCountry("中国");
        address.setCity("广州");
        entityManager.persist(address);

        // 保存用户信息
        Person person = new Person();
        person.setUsername("Sam");
        person.setPersonAddress(address);
        entityManager.persist(person);

        // 提交更新事务
        entityManager.getTransaction().commit();

        // 查询拥有端(外键表端),先清理缓存
        entityManager.clear();
        System.out.println(entityManager.find(Person.class,person.getPersonId()));
        entityManager.close();
    }

查看日志1(日志分为两段,1为建表):

Hibernate: 
    
    create table tb_person (
       person_id bigint not null auto_increment,
        username varchar(255),
        address_id bigint,
        primary key (person_id)
    ) engine=InnoDB
Hibernate: 
    
    create table tb_person_address (
       address_id bigint not null auto_increment,
        city varchar(255),
        country varchar(255),
        primary key (address_id)
    ) engine=InnoDB
Hibernate: 
    
    alter table tb_person 
       add constraint FKcep21ttdy3yuo1f56giyatasf 
       foreign key (address_id) 
       references t_person_address (address_id)

查看日志2(日志分为两段,2为数据插入与查询):

Hibernate: // 数据插入
    insert 
    into
        t_person_address
        (city, country) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        t_person
        (address_id, username) 
    values
        (?, ?)
Hibernate: // 数据查询
    select
        person0_.person_id as person_i1_5_0_,
        person0_.address_id as address_3_5_0_,
        person0_.username as username2_5_0_,
        personaddr1_.address_id as address_1_6_1_,
        personaddr1_.city as city2_6_1_,
        personaddr1_.country as country3_6_1_ 
    from
        tb_person person0_ 
    left outer join
        tb_person_address personaddr1_ 
            on person0_.address_id=personaddr1_.address_id 
    where
        person0_.person_id=?
Person(personId=1, username=Sam, address=PersonAddress(addressId=1, country=中国, city=广州))

1.2、双向一对一

清单 3:单向一对一关系的拥有端

@Data
@Entity
@Table(name = "tb_person_address")
public class PersonAddress implements Serializable {
   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   @Column(name = "address_id")
   private Long addressId;
   private String country;
   private String city;

   /**
    * @OneToOne
    *    mappedBy:放弃外键维护。mappedBy 只有在双向关联的时候设置。值为对方类引用本类的属性名
    */
   @OneToOne(mappedBy = "personAddress")
   private Person person;
}

清单 4:双向一对一关系中的接受端

@Data
@Entity
@Table(name = "tb_person")
public class Person implements Serializable {
   private static final long serialVersionUID = 1L;
   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   @Column(name = "person_id")
   private Long personId;
   private String username;
   /**
    * @JoinColumn
    *     name: person_address表外键字段名(数据库字段名)
    *     referencedColumnName: person表主键字段(数据库字段名)
    */
   @OneToOne
   @JoinColumn(name = "address_id",referencedColumnName = "address_id")
   private PersonAddress personAddress;
}

执行代码测试:

    /**
     * 运行之前,修改hibernate.hbm2ddl.auto=create
     * 双向一对一查询
     */
    @Test
    public void testOneToOne2(){
        EntityManager entityManager = JpaUtils.getEntityManager();
        // 开启事务
        entityManager.getTransaction().begin();

        PersonAddress personAddress = new PersonAddress();
        personAddress.setCountry("中国");
        personAddress.setCity("广州");
        entityManager.persist(personAddress);

        Person person = new Person();
        person.setUsername("Sam");
        person.setPersonAddress(personAddress);
        entityManager.persist(person);

        // 提交事务
        entityManager.getTransaction().commit();

        // 查询拥有端(外键表端)先清理缓存
        entityManager.clear();
        System.out.println(entityManager.find(PersonAddress.class,personAddress.getAddressId()));
        // 查询被拥有端(主键表端)
        System.out.println(entityManager.find(Person.class,person.getPersonId()));
        entityManager.close();
    }

查看日志(建表语句就不重复打印了,因为是一摸一样的):

// ...省略插入语句和查询语句

java.lang.StackOverflowError
	at java.lang.Long.toString(Long.java:396)
	at java.lang.Long.toString(Long.java:1032)
	at java.lang.String.valueOf(String.java:2994)
	at java.lang.StringBuilder.append(StringBuilder.java:131)
	at OneToOne.Person.toString(Person.java:8)
	at java.lang.String.valueOf(String.java:2994)
	at java.lang.StringBuilder.append(StringBuilder.java:131)
	at OneToOne.PersonAddress.toString(PersonAddress.java:8)
	at java.lang.String.valueOf(String.java:2994)
	at java.lang.StringBuilder.append(StringBuilder.java:131)
	at OneToOne.Person.toString(Person.java:8)
	at java.lang.String.valueOf(String.java:2994)
...

现在在查询步骤中会出现死循环(后面解决)

2、一对多关联映射

2.1、单向一对多

单向1-N:需要在控制关系的一方实体中使用注解 @OneToMany 和 @JoinColumn 标注类型为对方的集合属性(有两种方式:一种是只加@OneToMany、另一种是 @OneToMany + @JoinColumn)

  1. tb_people(外键表):people_id,name,people_id
  2. tb_people_phone(主键表):phone_id,type,phone

清单 5:单向一对多关系的拥有端(一方、主键表方、主表)

@Data
@Entity
@Table(name = "tb_people")
public class People {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "people_id")
    private Long peopleId;
    private String name;

    /**
     *  @OneToMany
     *    cascade = CascadeType.ALL:级联保存、更新、删除、刷新
     *    fetch = FetchType.LAZY   :延迟加载
     *  @JoinColumn
     *    name 指定外键列,这里注意指定的是people_id,实际上是为了外键表定义的字段。该字段在PeoplePhone类必须定义
     */
    @OneToMany
    @JoinColumn(name="people_id")
    private List<PeoplePhone> peoplePhones = new ArrayList<>();
}

清单 6:单向一对多关系的接收端(多方、外键表方、从表)

@Data
@Entity
@Table(name = "tb_people_phone")
public class PeoplePhone {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "phone_id")
    private Long phoneId;
    private String type;
    private String phone;
}

测试代码:

    /**
     * 单向一对多查询
     */
    @Test
    public void testOneToMany(){
        EntityManager entityManager = JpaUtils.getEntityManager();
        entityManager.getTransaction().begin(); // 开启事务

        PeoplePhone peoplePhoneA = new PeoplePhone();
        peoplePhoneA.setType("date_time");
        peoplePhoneA.setPhone("13011113333");
        PeoplePhone peoplePhoneB = new PeoplePhone();
        peoplePhoneB.setType("mobile");
        peoplePhoneB.setPhone("0208514851");
        entityManager.persist(peoplePhoneA);
        entityManager.persist(peoplePhoneB);

        People people = new People();
        people.setName("Sam");
        people.getPeoplePhones().add(peoplePhoneA);
        people.getPeoplePhones().add(peoplePhoneB);
        entityManager.persist(people);
        entityManager.getTransaction().commit(); // 提交更新事务

        // 查询拥有端(主键表端,注意:单向一对多是配置在 一方/拥有端)
        System.out.println(entityManager.find(People.class,people.getPeopleId()));
        entityManager.close();
    }

查看日志1:(建表语句)

Hibernate: 
    
    create table tb_people (
       people_id bigint not null auto_increment,
        name varchar(255),
        primary key (people_id)
    ) engine=InnoDB
Hibernate: 
    
    create table tb_people_phone (
       phone_id bigint not null auto_increment,
        phone varchar(255),
        type varchar(255),
        people_id bigint,
        primary key (phone_id)
    ) engine=InnoDB
Hibernate: 
    
    alter table tb_people_phone 
       add constraint FKnied6axrmqsyl5olnjywa7set 
       foreign key (people_id) 
       references t_people (people_id)

查看日志2:

Hibernate: 
    insert 
    into
        tb_people_phone
        (phone, type) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        tb_people_phone
        (phone, type) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        tb_people
        (name) 
    values
        (?)
Hibernate: 
    update
        tb_people_phone 
    set
        people_id=? 
    where
        phone_id=?
Hibernate: 
    update
        tb_people_phone 
    set
        people_id=? 
    where
        phone_id=?
People(peopleId=1, name=Sam, peoplePhones=[PeoplePhone(phoneId=1, type=date_time, phone=13011113333), PeoplePhone(phoneId=2, type=mobile, phone=0208514851)])
mysql> select * from tb_people;
+-----------+------+
| people_id | name |
+-----------+------+
|         1 | Sam  |
+-----------+------+
1 row in set (0.01 sec)

mysql> select * from tb_people_phone;
+----------+-------------+-----------+-----------+
| phone_id | phone       | type      | people_id |
+----------+-------------+-----------+-----------+
|        1 | 13011113333 | date_time |         1 |
|        2 | 0208514851  | mobile    |         1 |
+----------+-------------+-----------+-----------+
2 rows in set (0.02 sec)

2.2、单向多对一

单向N-1:需要在控制关系的一方实体中使用注解 @ManyToOne 和 @JoinColumn 标注类型为对方的属性。

清单 7:单向多对一关系的拥有端(多方、外键表方、从表)

@Data
@Entity
@Table(name = "tb_people_phone")
public class PeoplePhone {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "phone_id")
    private Long phoneId;
    private String type;
    private String phone;
    /**
     * @JoinColumn
     *     name 指定外键列
     *     referencedColumnName: people 表主键字段(数据库字段名)
     */
    @ManyToOne
    @JoinColumn(name="people_id", referencedColumnName = "people_id")
    private People people;
}

清单 8:单向多对一关系的被拥有端(一方、主键表方、主表)

@Data
@Entity
@Table(name = "tb_people")
public class People {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "people_id")
    private Long peopleId;
    private String name;
}

测试代码:

    /**
     * 单向多对一查询
     */
    @Test
    public void testManyToOne(){
        EntityManager entityManager = JpaUtils.getEntityManager();
        entityManager.getTransaction().begin();// 开启事务

        // 先保存主键(被维护端)数据
        People people = new People();
        people.setName("Sam");
        entityManager.persist(people);

        // 然后保存外键(维护端)数据
        PeoplePhone peoplePhoneA = new PeoplePhone();
        peoplePhoneA.setType("date_time");
        peoplePhoneA.setPhone("13011113333");
        peoplePhoneA.setPeople(people);
        PeoplePhone peoplePhoneB = new PeoplePhone();
        peoplePhoneB.setType("mobile");
        peoplePhoneB.setPhone("0208514851");
        peoplePhoneB.setPeople(people);
        entityManager.persist(peoplePhoneA);
        entityManager.persist(peoplePhoneB);

        entityManager.getTransaction().commit();// 提交更新事务

        // 查询拥有端(外键表端,注意:单向多对一是配置在多方外键拥有端)
        entityManager.clear();
        System.out.println(entityManager.find(PeoplePhone.class,peoplePhoneA.getPhoneId()));
        System.out.println(entityManager.find(PeoplePhone.class,peoplePhoneB.getPhoneId()));
        entityManager.close();
    }

查看日志:

Hibernate: // 插入数据
    insert 
    into
        tb_people
        (name) 
    values
        (?)
Hibernate: // 插入数据
    insert 
    into
        tb_people_phone
        (people_id, phone, type) 
    values
        (?, ?, ?)
Hibernate: // 插入数据
    insert 
    into
        tb_people_phone
        (people_id, phone, type) 
    values
        (?, ?, ?)
Hibernate: // 查询拥有端
    select
        peoplephon0_.phone_id as phone_id1_4_0_,
        peoplephon0_.people_id as people_i4_4_0_,
        peoplephon0_.phone as phone2_4_0_,
        peoplephon0_.type as type3_4_0_,
        people1_.people_id as people_i1_3_1_,
        people1_.name as name2_3_1_ 
    from
        tb_people_phone peoplephon0_ 
    left outer join
        tb_people people1_ 
            on peoplephon0_.people_id=people1_.people_id 
    where
        peoplephon0_.phone_id=?
PeoplePhone(phoneId=1, type=date_time, phone=13011113333, people=People(peopleId=1, name=Sam))
Hibernate: // 查询拥有端
    select
        peoplephon0_.phone_id as phone_id1_4_0_,
        peoplephon0_.people_id as people_i4_4_0_,
        peoplephon0_.phone as phone2_4_0_,
        peoplephon0_.type as type3_4_0_,
        people1_.people_id as people_i1_3_1_,
        people1_.name as name2_3_1_ 
    from
        tb_people_phone peoplephon0_ 
    left outer join
        tb_people people1_ 
            on peoplephon0_.people_id=people1_.people_id 
    where
        peoplephon0_.phone_id=?
PeoplePhone(phoneId=2, type=mobile, phone=0208514851, people=People(peopleId=1, name=Sam))
mysql> select * from tb_people;
+-----------+------+
| people_id | name |
+-----------+------+
|         1 | Sam  |
+-----------+------+
1 row in set (0.02 sec)

mysql> select * from tb_people_phone;
+----------+-------------+-----------+-----------+
| phone_id | phone       | type      | people_id |
+----------+-------------+-----------+-----------+
|        1 | 13011113333 | date_time |         1 |
|        2 | 0208514851  | mobile    |         1 |
+----------+-------------+-----------+-----------+

2.3、双向一对多

双向1-N(N-1):1的一端需要使用注解@OneToMany标注类型为对方的集合属性,同时指定mappedBy属性表示1的一端不控制关系,N的一端则需要使用注解@ManyToOne 和 @JoinColumn 标注类型为对方的属性。

清单 9:双向一对多关系的接受端(一方、主键表方、主表)

@Data
@Entity
@Table(name = "tb_people")
public class People {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "people_id")
    private Long peopleId;
    private String name;
    /**
     * mappedBy:指定从表实体类中引用主表对象的名称。指明这端不控制关系
     * targetEntity:指定多的一方的类的字节码
     * cascade :指定要使用的级联操作
     * fetch   :指定是否采用延迟加载
     * orphanRemoval:是否使用孤儿删除
     */
    @OneToMany(
            mappedBy = "people",
            targetEntity = PeoplePhone.class,
            cascade = CascadeType.ALL,
            fetch = FetchType.LAZY)
    private List<PeoplePhone> peoplePhones = new ArrayList<>();
}

清单 10:双向一对多关系的发出端(多方、外键表方、从表)

@Data
@Entity
@Table(name = "tb_people_phone")
public class PeoplePhone {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "phone_id")
    private Long phoneId;
    private String type;
    private String phone;
    /**
     * @JoinColumn
     *     name 指定外键列
     *     referencedColumnName: people 表主键字段(数据库字段名)
     */
    @ManyToOne // cascade | fetch | targetEntity 都是可选
    @JoinColumn(name="people_id", referencedColumnName = "people_id")
    private People people;
}

测试代码:

    /**
     * 双向一对多查询
     */
    @Test
    public void testManyToOne(){
        EntityManager entityManager = JpaUtils.getEntityManager();
        entityManager.getTransaction().begin();// 开启事务

        // 先保存主键(被维护端)数据
        People people = new People();
        people.setName("Sam");
        entityManager.persist(people);

        // 然后保存外键(维护端)数据
        PeoplePhone peoplePhoneA = new PeoplePhone();
        peoplePhoneA.setType("date_time");
        peoplePhoneA.setPhone("13011113333");
        peoplePhoneA.setPeople(people);
        PeoplePhone peoplePhoneB = new PeoplePhone();
        peoplePhoneB.setType("mobile");
        peoplePhoneB.setPhone("0208514851");
        peoplePhoneB.setPeople(people);
        entityManager.persist(peoplePhoneA);
        entityManager.persist(peoplePhoneB);

        entityManager.getTransaction().commit();// 提交更新事务

        entityManager.clear();
        // 查询被拥有端(主键表端)
        System.out.println(entityManager.find(People.class,people.getPeopleId()));
        // 查询拥有端(外键表端,注意:单向多对一是配置在多方外键拥有端)
        System.out.println(entityManager.find(PeoplePhone.class,peoplePhoneA.getPhoneId()));
        System.out.println(entityManager.find(PeoplePhone.class,peoplePhoneB.getPhoneId()));
        entityManager.close();
    }

查看日志(建表语句就不重复打印了,因为是一摸一样的):

// ...省略插入语句和查询语句

java.lang.StackOverflowError
	at java.lang.StringBuilder.append(StringBuilder.java:136)
	at OneToMany.PeoplePhone.toString(PeoplePhone.java:7)
	at java.lang.String.valueOf(String.java:2994)
	at java.lang.StringBuilder.append(StringBuilder.java:131)
	at java.util.AbstractCollection.toString(AbstractCollection.java:462)
	at org.hibernate.collection.internal.PersistentBag.toString(PersistentBag.java:538)
	at java.lang.String.valueOf(String.java:2994)
	at java.lang.StringBuilder.append(StringBuilder.java:131)
	at OneToMany.People.toString(People.java:9)
	at java.lang.String.valueOf(String.java:2994)
	at java.lang.StringBuilder.append(StringBuilder.java:131)
	at OneToMany.PeoplePhone.toString(PeoplePhone.java:7)
...

现在在查询步骤中会出现死循环(后面解决)

3、多对多关联映射

3.1、单向多对多

单向N-N:需要在控制关系的一方实体中使用注解@ManyToMany 和 @JoinTable标注类型为对方的属性,这里应该是一个集合属性

清单 11:单向多对多关系的发出端(拥有端)

@Data
@Entity
@Table(name = "tb_role")
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "role_id")
    private Long roleId;
    @Column(name = "role_name")
    private String roleName;

    /**
     * @JoinTable:
     *   name:中间表名称
     *   joinColumns          中间表对应本类的信息
     *     @JoinColumn;
     *       name:本类的外键字段(中间表的数据库字段)
     *       referencedColumnName:本类与外键(表)对应的主键(本类的主键字段)
     *   inverseJoinColumns    中间表对应对方类的信息
     *     @JoinColumn:
     *       name:对方类的外键(中间表的数据字段)
     *       referencedColumnName:对方类与外键(表)对应的主键(对方类的主键字段)
     */
    @ManyToMany
    @JoinTable(name="tb_role_permission", // 中间表明
            joinColumns=@JoinColumn(
                    name="role_id", // 本类的外键
                    referencedColumnName = "role_id"), // 本类与外键(表)对应的主键
            inverseJoinColumns=@JoinColumn(
                    name="permission_id", // 对方类的外键
                    referencedColumnName = "permission_id")) // 对方类与外键(表)对应的主键
    private Set<Permission> permissions = new HashSet<>();
}

清单 11:单向多对多关系的接收端(被拥有端)什么都不用配置

@Data
@Entity
@Table(name = "tb_permission")
public class Permission {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "permission_id")
    private Long permissionId;
    @Column(name = "permission_name")
    private String permissionName;
}

测试代码:

    /**
     * 单向多对多
     */
    @Test
    public void testManyToMany(){
        EntityManager entityManager = JpaUtils.getEntityManager();
        // 开启事务
        entityManager.getTransaction().begin();

        // 增加权限数据
        Permission permissionA = new Permission();
        permissionA.setPermissionName("增加");
        Permission permissionB = new Permission();
        permissionB.setPermissionName("查询");
        entityManager.persist(permissionA);
        entityManager.persist(permissionB);
        entityManager.persist(permissionB);

        // 增加角色数据
        Role role = new Role();
        role.setRoleName("网络管理员");
        role.getPermissions().add(permissionA);
        role.getPermissions().add(permissionB);
        entityManager.persist(role);

        // 提交更新事务
        entityManager.getTransaction().commit();

        // 查询角色信息
        entityManager.clear();
        System.out.println(entityManager.find(Role.class,role.getRoleId()));
        entityManager.close();
    }

查看日志1:(建表语句)

Hibernate: 
    
    create table tb_permission (
       permission_id bigint not null auto_increment,
        permission_name varchar(255),
        primary key (permission_id)
    ) engine=InnoDB
Hibernate: 
    
    create table tb_role (
       role_id bigint not null auto_increment,
        role_name varchar(255),
        primary key (role_id)
    ) engine=InnoDB
Hibernate: 
    
    create table tb_role_permission (
       role_id bigint not null,
        permission_id bigint not null,
        primary key (role_id, permission_id)
    ) engine=InnoDB
Hibernate: 
    
    alter table tb_role_permission 
       add constraint FKjobmrl6dorhlfite4u34hciik 
       foreign key (permission_id) 
       references tb_permission (permission_id)
Hibernate: 
    
    alter table tb_role_permission 
       add constraint FK90j038mnbnthgkc17mqnoilu9 
       foreign key (role_id) 
       references tb_role (role_id)

查看日志2:(数据插入与查询)

Hibernate: 
    insert 
    into
        tb_permission
        (permission_name) 
    values
        (?)
Hibernate: 
    insert 
    into
        tb_permission
        (permission_name) 
    values
        (?)
Hibernate: 
    insert 
    into
        tb_role
        (role_name) 
    values
        (?)
Hibernate: 
    insert 
    into
        tb_role_permission
        (role_id, permission_id) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        tb_role_permission
        (role_id, permission_id) 
    values
        (?, ?)
Hibernate: 
    select
        role0_.role_id as role_id1_6_0_,
        role0_.role_name as role_nam2_6_0_ 
    from
        tb_role role0_ 
    where
        role0_.role_id=?
Hibernate: 
    select
        permission0_.role_id as role_id1_0_0_,
        permission0_.permission_id as permissi2_0_0_,
        permission1_.permission_id as permissi1_3_1_,
        permission1_.permission_name as permissi2_3_1_ 
    from
        tb_role_permission permission0_ 
    inner join
        tb_permission permission1_ 
            on permission0_.permission_id=permission1_.permission_id 
    where
        permission0_.role_id=?
Role(roleId=1, roleName=网络管理员, permissions=[Permission(permissionId=2, permissionName=查询), Permission(permissionId=1, permissionName=增加)])
mysql> select * from tb_role;
+---------+------------+
| role_id | role_name  |
+---------+------------+
|       1 | 网络管理员 |
+---------+------------+
1 row in set (0.05 sec)
mysql> select * from tb_permission;
+---------------+-----------------+
| permission_id | permission_name |
+---------------+-----------------+
|             1 | 增加            |
|             2 | 查询            |
+---------------+-----------------+
2 rows in set (0.06 sec)
mysql> select * from tb_role_permission;
+---------+---------------+
| role_id | permission_id |
+---------+---------------+
|       1 |             3 |
|       1 |             4 |
+---------+---------------+
2 rows in set (0.05 sec)

PS:单向多对多反过来配置到另一个类也是一样。只需要把本类的外键和对方类外键调换一下即可。

3.2、双向多对多

发出端(拥有端)代码不变

@Data
@Entity
@Table(name = "t_role")
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "role_id")
    private Long roleId;
    @Column(name = "role_name")
    private String roleName;

    /**
     * @JoinTable:
     *   name:中间表名称
     *   joinColumns          中间表对应本类的信息
     *     @JoinColumn;
     *       name:本类的外键字段(中间表的数据库字段)
     *       referencedColumnName:本类与外键(表)对应的主键(本类的主键字段)
     *   inverseJoinColumns    中间表对应对方类的信息
     *     @JoinColumn:
     *       name:对方类的外键(中间表的数据字段)
     *       referencedColumnName:对方类与外键(表)对应的主键(对方类的主键字段)
     */
    @ManyToMany
    @JoinTable(name="t_role_permission", // 中间表明
            joinColumns=@JoinColumn(
                    name="role_id", // 本类的外键
                    referencedColumnName = "role_id"), // 本类与外键(表)对应的主键
            inverseJoinColumns=@JoinColumn(
                    name="permission_id", // 对方类的外键
                    referencedColumnName = "permission_id")) // 对方类与外键(表)对应的主键
    private Set<Permission> permissions = new HashSet<>();
}

接收端(被拥有端)代码增加了@ManyToMany 注解和mappedBy属性

@Data
@Entity
@Table(name = "t_permission")
public class Permission {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "permission_id")
    private Long permissionId;
    @Column(name = "permission_name")
    private String permissionName;

    /**
     * @ManyToMany
     *   mappedBy:对方类应用该类的属性名,指明这端不控制关系
     *   cascade | fetch | targetEntity 为可选属性
     */
    @ManyToMany(mappedBy = "permissions")
    private Set<Role> role = new HashSet<>();
}

测试代码:

    /**
     * 单向/双向 多对多查询代码
     */
    @Test
    public void testManyToManyFind(){
        EntityManager entityManager = JpaUtils.getEntityManager();
        System.out.println(entityManager.find(Role.class,1L));
        System.err.println("--------------华丽的分割线-------------------");
        System.out.println(entityManager.find(Permission.class,1L));
        entityManager.close();
    }

可以发现单向查询依旧正常,而双向查询 依旧会有死循环问题(该问题在SpringDataJPA篇章中解决)

你可能感兴趣的:(从零开始学,Spring,Data,JPA,hibernate,数据库,java)