Jpa之關聯(lián)對象(單向多對一)

一:單向多對一

多對一的關系:在多方表中添加一個外鍵列來維護雙方關系。

1. 表結(jié)構(gòu)設計

{3IY_$3PJ$MS3{`UYF487LQ.png

2. 對象設計

在多方關聯(lián)一方,外鍵所在的表是多方!
1.Employee

@Entity
@Setter
@Getter
public class Employee {
    @Id
    @GeneratedValue
    private Long id;
    private String name;//名稱
    @ManyToOne//告訴jpa,是多對一的關系
    @JoinColumn(name = "dept_id")//配置外鍵列的名字,默認是字段名_id
    private Department department;//關聯(lián)部門信息

    @Override
    public String toString() {
        return "Employee{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

2.Department

@Getter
@Setter
@ToString
@Entity
public class Department {
    @Id
    @GeneratedValue
    private Long id;
    private String name;
    }

3. 實現(xiàn)

向表中添加數(shù)據(jù):
(1):先保存部門,在保存員工

    @Test
    public void testSave(){
        EntityManager entityManager = JPAUtil.createEntityManager();
        Employee e1 = new Employee();
        e1.setName("Alice");
        Employee e2 = new Employee();
        e2.setName("cady");
        Department d = new Department();
        d.setName("人事部");
        e1.setDepartment(d);
        e2.setDepartment(d);
        entityManager.getTransaction().begin();//開啟事務
        //先保存部門,再保存員工
        entityManager.persist(d);//持久化操作
        entityManager.persist(e1);
        entityManager.persist(e2);
        entityManager.getTransaction().commit();//關閉事務
        entityManager.close();//釋放資源

(2):先保存員工,再保存部門

 @Test
    public void testSave(){
        EntityManager entityManager = JPAUtil.createEntityManager();
        Employee e1 = new Employee();
        e1.setName("Alice");
        Employee e2 = new Employee();
        e2.setName("cady");
        Department d = new Department();
        d.setName("人事部");
        e1.setDepartment(d);
        e2.setDepartment(d);
        entityManager.getTransaction().begin();//開啟事務
        //先保存員工,再保存部門
        entityManager.persist(e1);//持久化操作
        entityManager.persist(e2);
        entityManager.persist(d);
        entityManager.getTransaction().commit();//關閉事務
        entityManager.close();//釋放資源

4. 保存的SQL分析

(1):先保存部門,再保存員工

Hibernate: insert into Department (name) values (?)
Hibernate: insert into Employee (dept_id, name) values (?, ?)
Hibernate: insert into Employee (dept_id, name) values (?, ?)

執(zhí)行了三條SQL,這是很正常的!
(2):先保存部門,再保存員工

Hibernate: insert into Employee (dept_id, name) values (?, ?)
Hibernate: insert into Employee (dept_id, name) values (?, ?)
Hibernate: insert into Department (name) values (?)
Hibernate: update Employee set dept_id=?, name=? where id=?
Hibernate: update Employee set dept_id=?, name=? where id=?

執(zhí)行了5條SQL,三條插入,兩條更新!
原因:
1.執(zhí)行insert語句將Employee 保存到數(shù)據(jù)庫中時,此時Employee 對象由臨時狀態(tài)轉(zhuǎn)換成持久狀態(tài),并放到一級緩存中,Employee 對象所依賴的Department 的id為null。
2.保存Employee 所依賴的Department 對象,獲取到數(shù)據(jù)庫中自動生成的主鍵,此時EntityManager中一級緩存對象發(fā)生改變(依賴的Department的id屬性發(fā)生改變)
3.提交事務,檢查緩存區(qū)中的Employee 和快照區(qū)的Employee 對象是否一致,發(fā)現(xiàn)兩者所依賴的Department 的主鍵信息是不一致的。此時執(zhí)行對應的update語句將緩存中的臟數(shù)據(jù)持久化到數(shù)據(jù)庫中!

5.查詢的SQL分析

@Test
    public void testfind(){
        EntityManager entityManager = JPAUtil.createEntityManager();
        //查詢員工
        Employee employee = entityManager.find(Employee.class, 1l);
        System.out.println(employee);//Employee{id=1, name='Alice'}
        //查詢部門
        Department department = employee.getDepartment();
        System.out.println(department);//Department(id=1, name=人事部)
        entityManager.close();//釋放資源
    }

執(zhí)行的SQL

Hibernate : SELECT
    employee0_.id AS id1_1_0_,
    employee0_.dept_id AS dept_id3_1_0_,
    employee0_. NAME AS name2_1_0_,
    department1_.id AS id1_0_1_,
    department1_. NAME AS name2_0_1_
FROM
    Employee employee0_
LEFT OUTER JOIN Department department1_ ON employee0_.dept_id = department1_.id
WHERE
    employee0_.id =?

發(fā)現(xiàn)使用的是左外連接查詢,并且把所有的信息都查詢出來了!
有時候我們只需要獲取主對象,不需要獲取從對象,等到需要的時候再查詢,此時可以使用延遲加載的方式!

6.fetch抓取策略

告訴jpa怎樣發(fā)送SQL(立即還是延遲)來查詢關鍵對象!在建立關聯(lián)關系時(@ManyToOne)加上參數(shù)fetch = FetchType.LAZY,就是延遲加載,相對的立即加載就是fetch =FetchType.EAGER,jpa默認的參數(shù)就是立即加載。

@Entity
@Setter
@Getter
public class Employee {
    @Id
    @GeneratedValue
    private Long id;
    private String name;//名稱
    @ManyToOne(fetch = FetchType.LAZY)//告訴jpa,是多對一的關系,fetch抓取策略為延遲加載
    @JoinColumn(name = "dept_id")//配置外鍵列的名字,默認是字段名_id
    private Department department;//關聯(lián)部門信息

    @Override
    public String toString() {
        return "Employee{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

現(xiàn)在查詢員工信息,并獲取部門信息,但此時并沒有使用部門信息,只是獲取了個持久化對象!

@Test
    public void testfind(){
        EntityManager entityManager = JPAUtil.createEntityManager();
        //查詢員工
        Employee employee = entityManager.find(Employee.class, 1l);
        System.out.println(employee);//Employee{id=1, name='Alice'}
        //查詢部門
        Department department = employee.getDepartment();
        entityManager.close();//釋放資源
    }

發(fā)送的SQL:

Hibernate : SELECT
    employee0_.id AS id1_1_0_,
    employee0_.dept_id AS dept_id3_1_0_,
    employee0_. NAME AS name2_1_0_
FROM
    Employee employee0_
WHERE
    employee0_.id =?

發(fā)現(xiàn)并沒有去查詢部門的信息,這在一定情況下可以提高性能!
在關閉EntityManager后再去使用對象!會拋出異常:org.hibernate.LazyInitializationException: could not initialize proxy - no Session 延遲初始化異常,因為EntityManager中存放著連接對象,如果釋放了資源,丟失了連接,此時再去數(shù)據(jù)庫中查詢改數(shù)據(jù),顯然是不可能的!

@Test
    public void testfind(){
        EntityManager entityManager = JPAUtil.createEntityManager();
        //查詢員工
        Employee employee = entityManager.find(Employee.class, 1l);
        System.out.println(employee);//Employee{id=1, name='Alice'}
        //查詢部門
        Department department = employee.getDepartment();
        entityManager.close();//釋放資源
        System.out.println(department);//會拋出異常
    }

7.延遲加載實現(xiàn)原理

獲得運行時類型:

 @Test
    public void testClass(){
        EntityManager entityManager = JPAUtil.createEntityManager();
        Employee employee = entityManager.find(Employee.class, 1l);
        System.out.println(employee.getClass());//class hanfengyi.Employee
        Department department = employee.getDepartment();
        System.out.println(department.getClass());//class hanfengyi.Department_$$_jvst29a_0
        entityManager.close();
    }

結(jié)論:在使用關聯(lián)對象Department 時,此時獲取到的類型是class hanfengyi.Department_$$_jvst29a_0,這是jpa的一個代理對象,在運行時創(chuàng)建的類繼承自Department !重寫了getter方法,并且在我們需要使用的時候,在get方法中去發(fā)送sql查詢數(shù)據(jù),并封裝成對象,我們使用的就是代理對象。

Department_$$_jvst29a_0 extend Department{
//通過這個參數(shù)判斷,當前這個對象是否已經(jīng)加載了(已經(jīng)執(zhí)行了sql)
// 默認是false,創(chuàng)建的時候默認是沒有加載(沒有執(zhí)行sql)
    private boolean isLoad=false;
    
    public Long getId() {//直接從多方的外鍵里面獲取
        return id;
    }
  
    public String getName(){
       //第一次沒有加載,執(zhí)行里面的方法
       //里面的方法就去進行加載(執(zhí)行sql)
       
        if(!isLoad){
            load();
        }
        return this.name;
    }
 
    private void load(){
        isLoad=true;//控制發(fā)出一條sql,當前方法只會被調(diào)用一次
        //發(fā)出sql,獲取ResultSet
        //把ResultSet封裝到當前的屬性里面
    }
}

在這之前有個條件,如果我們的類是final修飾的不可繼承,此時即使配置了延遲加載,也會失效,他會去查詢到所有數(shù)據(jù)!

@Getter
@Setter
@ToString
@Entity
public final class Department {
    @Id
    @GeneratedValue
    private Long id;
    private String name;
    }
@Test
    public void testFetch(){
        EntityManager entityManager = JPAUtil.createEntityManager();
        Employee employee = entityManager.find(Employee.class, 1l);
        System.out.println(employee);//Employee{id=1, name='Alice'}
        Department department = employee.getDepartment();
        entityManager.close();
    }

Department 并沒有使用,發(fā)送的SQL:

SELECT
    employee0_.id AS id1_1_0_,
    employee0_.dept_id AS dept_id3_1_0_,
    employee0_. NAME AS name2_1_0_
FROM
    Employee employee0_
WHERE
    employee0_.id =? 
SELECT
        department0_.id AS id1_0_0_,
        department0_. NAME AS name2_0_0_
    FROM
        Department department0_
    WHERE
        department0_.id =?

發(fā)送了兩條SQL,先查詢Employee ,然后發(fā)現(xiàn)不能使用代理模式,就再發(fā)出一條SQL用來查詢關聯(lián)數(shù)據(jù)!

8:判斷是否有外鍵關聯(lián)

通過多方來獲取一方判斷是否為null!

@Test
   public void testFetch(){
       EntityManager entityManager = JPAUtil.createEntityManager();
       Employee employee = entityManager.find(Employee.class, 1l);
       System.out.println(employee);//Employee{id=1, name='Alice'}
       Department department = employee.getDepartment();
       if(department!=null){
           System.out.println("有數(shù)據(jù)");
       }
       entityManager.close();
   }

小結(jié):
1.單向多對一創(chuàng)建關聯(lián)方式在多方的實體類加注解@ManyToOne。
2.如果需要修改關聯(lián)列名(外鍵名)@JoinColumn(name = " 名字")。
3.保存數(shù)據(jù)時先保存一方再保存多方,性能更高點。(發(fā)送的SQL少)。
4.查詢數(shù)據(jù)時,如果只需要查詢主對象,不需要從對象,可以配置延遲加載, @ManyToOne(fetch = FetchType.LAZY)且從表不能用final修飾!
5.判斷有無外鍵關聯(lián),可以根據(jù)多方再獲取一方,判斷一方是否為null!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容