框架學習之JPA(四)
JPA是Java Persistence API的簡稱,中文名Java持久層API,是JDK 5.0注解或XML描述對象-關系表的映射關系,並將運行期的實體對象持久化到數據庫中。
Sun引入新的JPA ORM規范出於兩個原因:其一,簡化現有Java EE和Java SE應用開發工作;其二,Sun希望整合ORM技術,實現天下歸一。
學習視頻:尚硅谷框架jpa學習(有興趣的同學留言郵箱)
使用軟件:eclipse
Java版本:jdk8
本節目錄
四、JPA_映射關聯關系
1.映射單向多對一的關聯關系
2.映射單向一對多的關聯關系
3.映射雙向多對一的關聯關系
4.映射雙向一對一的關聯關系
5.映射雙向多對多的關聯關系
四、JPA_映射關聯關系
1.映射單向多對一的關聯關系
創建Order類
package hue.edu.xiong.jpa; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.Table; @Entity @Table(name="JPA_ORDERS") public class Order { @Id @GeneratedValue(strategy=GenerationType.AUTO) private Integer id; @Column(name="ORDER_NAME") private String orderName; //映射單向n-1的關聯關系 //使用@ManyToIne來映射多對一的關聯關系 //使用@JoinColumn來映射外鍵 //上課學到的原則,對方永遠是一,即多個我對應一個對方,即多對一 @ManyToOne @JoinColumn(name="CUSTOMER_ID") private Customer customer; //get,set略,請自行補充 }
保存測試:
@Test public void testManyToOnePersist() { Customer customer = new Customer(); customer.setAge(12); customer.setEmail("937724308@qq.com"); customer.setLastName("xiong"); customer.setBirth(new Date()); customer.setCreatedTime(new Date()); Order order1 = new Order(); order1.setOrderName("O-FF-1"); Order order2 = new Order(); order2.setOrderName("O-FF-2"); //設置關聯關系 order1.setCustomer(customer); order2.setCustomer(customer); //執行保存操作 entityManager.persist(customer); entityManager.persist(order1); entityManager.persist(order2); }
//修改保存順序 entityManager.persist(order1); entityManager.persist(order2); entityManager.persist(customer);
l 第一個測試會發現數據庫中是三個insert操作
l 第二個測試修改順序之后發現是三個insert操作,再是兩個update操作
l 保存多對一時,建議先保存1的一端,然后保存n的一端這樣不會有額外的update語句
獲取測試:
//默認情況下使用左外連接方式來獲取N的一端的對象和其關聯的1的一端的對象 //可使用@ManyToOne的fetch屬性來修改默認的關聯屬性的加載策略 @Test public void testManyToOneFind() { Order order = entityManager.find(Order.class, 1); System.out.println(order.getOrderName()); System.out.println(order.getCustomer().getLastName()); } ------------------------------------------------------ //懶加載模式 //映射單向n-1的關聯關系 //使用@ManyToIne來映射多對一的關聯關系 //使用@JoinColumn來映射外鍵 //可使用@ManyToOne的fetch屬性來修改默認的關聯屬性的加載策略 @ManyToOne(fetch=FetchType.LAZY) @JoinColumn(name="CUSTOMER_ID") private Customer customer;
刪除測試:
//不能直接刪除 1 的一端, 因為有外鍵約束. @Test public void testManyToOneRemove(){ // Order order = entityManager.find(Order.class, 1); // entityManager.remove(order); Customer customer = entityManager.find(Customer.class, 5); entityManager.remove(customer); }
修改測試:
//可以修改外鍵對應的對象的值將"xiong"->"FFF" @Test public void testManyToOneUpdate(){ Order order = entityManager.find(Order.class, 2); order.getCustomer().setLastName("FFF"); }
2.映射單向一對多的關聯關系
修改Order類,把Customer全部注解掉
package hue.edu.xiong.jpa; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; @Entity @Table(name="JPA_ORDERS") public class Order { @Id @GeneratedValue(strategy=GenerationType.AUTO) private Integer id; @Column(name="ORDER_NAME") private String orderName; // //映射單向n-1的關聯關系 // //使用@ManyToIne來映射多對一的關聯關系 // //使用@JoinColumn來映射外鍵 // //可使用@ManyToOne的fetch屬性來修改默認的關聯屬性的加載策略 // @ManyToOne(fetch=FetchType.LAZY) // @JoinColumn(name="CUSTOMER_ID") // private Customer customer; //get,set略,請自行補充 }
修改Customer類,增加Set<Order> orders,並且設置一對多關系
package hue.edu.xiong.jpa; import java.util.Date; import java.util.Set; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.OneToMany; import javax.persistence.Table; import javax.persistence.Temporal; import javax.persistence.TemporalType; @Table(name = "JPA_CUSTOMERS") @Entity public class Customer { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; @Column(name = "LAST_NAME") private String lastName; private String email; private Integer age; @Temporal(TemporalType.DATE) private Date birth; @Temporal(TemporalType.TIMESTAMP) private Date createdTime; //設置單向1-n關聯關系 @OneToMany @JoinColumn(name="CUSTOMER_ID") private Set<Order> orders; }
保存測試:
//單向 1-n 關聯關系執行保存時, 一定會多出 UPDATE 語句. //因為 n 的一端在插入時不會同時插入外鍵列. @Test public void testOneToManyPersist(){ Customer customer = new Customer(); customer.setAge(18); customer.setBirth(new Date()); customer.setCreatedTime(new Date()); customer.setEmail("mm@163.com"); customer.setLastName("MM"); Order order1 = new Order(); order1.setOrderName("O-MM-1"); Order order2 = new Order(); order2.setOrderName("O-MM-2"); //建立關聯關系 customer.getOrders().add(order1); customer.getOrders().add(order2); //執行保存操作 entityManager.persist(customer); entityManager.persist(order1); entityManager.persist(order2); }
- 單向 1-n 關聯關系執行保存時, 一定會多出 UPDATE 語句.
- 因為 n 的一端在插入時不會同時插入外鍵列.
查找測試:
//默認對關聯的多的一方使用懶加載的加載策略. //可以使用 @OneToMany 的 fetch 屬性來修改默認的加載策略 @Test public void testOneToManyFind(){ Customer customer = entityManager.find(Customer.class, 9); System.out.println(customer.getLastName()); System.out.println(customer.getOrders().size()); }
- 默認是使用懶加載模式,可以修改為其他策略模式,怎樣修改看下面的刪除測試第二個表格內容
刪除測試:
//默認情況下, 若刪除 1 的一端, 則會先把關聯的 n 的一端的外鍵置空, 然后進行刪除. //可以通過 @OneToMany 的 cascade 屬性來修改默認的刪除策略. @Test public void testOneToManyRemove(){ Customer customer = entityManager.find(Customer.class, 8); entityManager.remove(customer); }
//設置單向1-n關聯關系 //使用 @OneToMany 來映射 1-n 的關聯關系 //使用 @JoinColumn 來映射外鍵列的名稱 //可以使用 @OneToMany 的 fetch 屬性來修改默認的加載策略 //可以通過 @OneToMany 的 cascade 屬性來修改默認的刪除策略. @OneToMany(fetch=FetchType.LAZY,cascade={CascadeType.REMOVE}) @JoinColumn(name="CUSTOMER_ID") private Set<Order> orders;
- 將外鍵修改為空之后再刪除
- 將外鍵全部刪除之后再刪除
修改測試:
@Test public void testUpdate(){ Customer customer = entityManager.find(Customer.class, 10); customer.getOrders().iterator().next().setOrderName("O-XXX-10"); }
3.映射雙向多對一的關聯關系
- 若是雙向 1-n 的關聯關系, 執行保存時
- 若先保存 n 的一端, 再保存 1 的一端, 默認情況下, 會多出 n 條 UPDATE 語句.
- 若先保存 1 的一端, 則會多出 n 條 UPDATE 語句
- 在進行雙向 1-n 關聯關系時, 建議使用 n 的一方來維護關聯關系, 而 1 的一方不維護關聯系, 這樣會有效的減少 SQL 語句.
- 注意: 若在 1 的一端的 @OneToMany 中使用 mappedBy 屬性, 則 @OneToMany 端就不能再使用 @JoinColumn屬性了.
- 放棄維護關聯關系,不使用JoinColumn標簽,在@OneToMany 中使用 mappedBy 屬性
// @JoinColumn(name="CUSTOMER_ID") @OneToMany(fetch=FetchType.LAZY,cascade={CascadeType.REMOVE},mappedBy="customer")
自我理解,就是把上面兩個關系組合起來,盡量使用多對一的關系,其他都一樣
4.映射雙向一對一的關聯關系
創建兩個類Manager,Department
package hue.edu.xiong.jpa; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.OneToOne; import javax.persistence.Table; @Table(name = "JPA_MANAGERS") @Entity public class Manager { @Id @GeneratedValue private Integer id; @Column(name = "MGR_NAME") private String mgrName; // 對於不維護關聯關系, 沒有外鍵的一方, 使用 @OneToOne 來進行映射, 建議設置 mappedBy=true @OneToOne(mappedBy = "mgr") private Department dept; //get,set略,請自行補充 }
package hue.edu.xiong.jpa; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.OneToOne; import javax.persistence.Table; @Table(name = "JPA_DEPARTMENTS") @Entity public class Department { @Id @GeneratedValue private Integer id; @Column(name = "DEPT_NAME") private String deptName; // 使用 @OneToOne 來映射 1-1 關聯關系。 // 若需要在當前數據表中添加主鍵則需要使用 @JoinColumn 來進行映射. 注意, 1-1 關聯關系, 所以需要添加 unique=true @JoinColumn(name = "MGR_ID", unique = true) @OneToOne(fetch = FetchType.LAZY) private Manager mgr; //get,set略,請自行補充 }
保存測試:
//雙向 1-1 的關聯關系, 建議先保存不維護關聯關系的一方, 即沒有外鍵的一方, 這樣不會多出 UPDATE 語句. @Test public void testOneToOnePersistence(){ Manager mgr = new Manager(); mgr.setMgrName("M-BB"); Department dept = new Department(); dept.setDeptName("D-BB"); //設置關聯關系 mgr.setDept(dept); dept.setMgr(mgr); //執行保存操作 entityManager.persist(mgr); entityManager.persist(dept); }
- l雙向 1-1 的關聯關系, 建議先保存不維護關聯關系的一方, 即沒有外鍵的一方, 這樣不會多出 UPDATE 語句.
查找測試:
//1.默認情況下, 若獲取維護關聯關系的一方, 則會通過左外連接獲取其關聯的對象. //但可以通過 @OntToOne 的 fetch 屬性來修改加載策略. @Test public void testOneToOneFind(){ Department dept = entityManager.find(Department.class, 1); System.out.println(dept.getDeptName()); System.out.println(dept.getMgr().getClass().getName()); }
//1. 默認情況下, 若獲取不維護關聯關系的一方, 則也會通過左外連接獲取其關聯的對象. //可以通過 @OneToOne 的 fetch 屬性來修改加載策略. 但依然會再發送 SQL 語句來初始化其關聯的對象 //這說明在不維護關聯關系的一方, 不建議修改 fetch 屬性. @Test public void testOneToOneFind2(){ Manager mgr = entityManager.find(Manager.class, 1); System.out.println(mgr.getMgrName()); System.out.println(mgr.getDept().getClass().getName()); }
5.映射雙向多對多的關聯關系
創建兩個類item和Category
package hue.edu.xiong.jpa; import java.util.HashSet; import java.util.Set; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToMany; import javax.persistence.Table; @Table(name="JPA_ITEMS") @Entity public class Item { @Id @GeneratedValue private Integer id; @Column(name="ITEM_NAME") private String itemName; //使用 @ManyToMany 注解來映射多對多關聯關系 //使用 @JoinTable 來映射中間表 //1. name 指向中間表的名字 //2. joinColumns 映射當前類所在的表在中間表中的外鍵 //2.1 name 指定外鍵列的列名 //2.2 referencedColumnName 指定外鍵列關聯當前表的哪一列 //3. inverseJoinColumns 映射關聯的類所在中間表的外鍵 @JoinTable(name="ITEM_CATEGORY", joinColumns={@JoinColumn(name="ITEM_ID", referencedColumnName="ID")}, inverseJoinColumns={@JoinColumn(name="CATEGORY_ID", referencedColumnName="ID")}) @ManyToMany private Set<Category> categories = new HashSet<>(); //get,set略,請自行補充 }
package hue.edu.xiong.jpa; import java.util.HashSet; import java.util.Set; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.ManyToMany; import javax.persistence.Table; @Table(name = "JPA_CATEGORIES") @Entity public class Category { @Id @GeneratedValue private Integer id; @Column(name = "CATEGORY_NAME") private String categoryName; @ManyToMany(mappedBy = "categories") private Set<Item> items = new HashSet<>(); //get,set略,請自行補充,過分了啊,這個顏色一直調整不好 }
必須指定中間表
- 使用 @ManyToMany 注解來映射多對多關聯關系
- 使用 @JoinTable 來映射中間表
- name 指向中間表的名字
- joinColumns 映射當前類所在的表在中間表中的外鍵
- name 指定外鍵列的列名
- referencedColumnName 指定外鍵列關聯當前表的哪一列
- inverseJoinColumns 映射關聯的類所在中間表的外鍵
典型多對多,一個學生對應多門課程,一門課程對應多個學生,學生加課程決定成績
保存案例:
//多對多的保存 @Test public void testManyToManyPersist(){ Item i1 = new Item(); i1.setItemName("i-1"); Item i2 = new Item(); i2.setItemName("i-2"); Category c1 = new Category(); c1.setCategoryName("C-1"); Category c2 = new Category(); c2.setCategoryName("C-2"); //設置關聯關系 i1.getCategories().add(c1); i1.getCategories().add(c2); i2.getCategories().add(c1); i2.getCategories().add(c2); c1.getItems().add(i1); c1.getItems().add(i2); c2.getItems().add(i1); c2.getItems().add(i2); //執行保存 entityManager.persist(i1); entityManager.persist(i2); entityManager.persist(c1); entityManager.persist(c2); }
查找案例:
//對於關聯的集合對象, 默認使用懶加載的策略. //使用維護關聯關系的一方獲取, 還是使用不維護關聯關系的一方獲取, SQL 語句相同. @Test public void testManyToManyFind(){ // Item item = entityManager.find(Item.class, 5); // System.out.println(item.getItemName()); // // System.out.println(item.getCategories().size()); Category category = entityManager.find(Category.class, 3); System.out.println(category.getCategoryName()); System.out.println(category.getItems().size()); }