一:單向多對一
多對一的關系:在多方表中添加一個外鍵列來維護雙方關系。
1. 表結(jié)構(gòu)設計

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!