天天看點

Hibernate JPA 關聯關系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篇章中解決)