多表之間的關系和操作多表的操作步驟
表關系
- 一對多
- 一對多 >> 一:主表 多:從表
- 多對多 >> 中間表中最少應該由兩個字段組成,這兩個字段作為外鍵指向兩張表的主鍵,又組成了聯合主鍵
分析步驟
- 明確表關系
- 確定表關系( 描述: 外鍵 | 中間表 )
- 編寫實體類,在實體類中描述表關系(包含關系)
- 配置映射關系
完成多表操作
一對多操作 案例:客戶和聯系人(一對多關系)
>> 客戶:一家公司 聯系人:這家公司的員工
一個客戶可以具有多個聯系人,一個聯系人從屬於一家公司
分析步驟
1. 明確表關系 >> 一對多關系
2. 確定表關系,再從表上添加外鍵(描述: 外鍵 I 中間表)
- 主表:客戶表
- 從表:聯系人表
3. 編寫實體類,在實體類中描述表關系(包含關系)
- 客戶:在客戶的實體類中包含一個聯系人的集合
- 聯系人:在聯系人的實體類中包含一個客戶的對象
4. 配置映射關系
使用JPA注解配置一對多映射關系
操作步驟
1.引入依賴坐標,導入實體類和xml文件
2. Customer >> 配置客戶和聯系人之間的一對多關系
1 @OneToMany(targetEntity = LinkMan.class) //對方實體類的字節碼對象 2 @JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id") //name:外鍵的名稱 referencedColumnName: 外鍵的取值來源 3 private Set<LinkMan> linkMans = new HashSet<LinkMan>();
LinkMan >> 配置客戶和聯系人之間的一對多關系
1 @ManyToOne(targetEntity = Customer.class) 2 @JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id") 3 private Customer customer;
配置外鍵的過程中,配置到多的一方,就會在多的一方維護外鍵
3. 保存一個客戶,保存一個聯系人
1 @RunWith(SpringJUnit4ClassRunner.class) 2 @ContextConfiguration(locations = "classpath:applicationContext.xml") 3 public class OneToManyTest { 4 @Autowired 5 private CustomerDao customerDao; 6 @Autowired 7 private LinkManDao linkManDao; 8 /** 9 * 保存一個客戶,保存一個聯系人 10 * 效果:客戶和聯系人作為獨立的數據保存到數據庫中 11 * 聯系人的外鍵為空 12 * 原因? >> 實體類中沒有配置關系!! 14 */ 15 @Test 16 @Transactional //配置事務 17 @Rollback(false) //不自動回滾 18 public void testAdd() { 19 //創建一個客戶,創建一個聯系人 20 Customer customer = new Customer(); 21 customer.setCustname("百度"); 22 23 LinkMan linkMan = new LinkMan(); 24 linkMan.setLkmName("小李"); 25 /** 26 * 配置了客戶到聯系人的關系 27 * 從客戶的角度上:發送兩條insert語句,發送一條更新語句更新數據庫(更新外鍵) 28 * 由於我們配置了客戶到聯系人的關系:客戶可以對外鍵進行維護 29 */ 30 customer.getLinkMans().add(linkMan); 31 32 customerDao.save(customer); 33 linkManDao.save(linkMan); 34 }
進行改進
1 /** 2 * 配置聯系人到客戶的關系(多對一) 3 * 只發送了兩條insert語句 4 * 由於配置了聯系人到客戶的映射關系(多對一),聯系人就能在保存的時候維護外鍵5 */ 6 linkMan.setCustomer(customer);
再次進行改進
1 linkMan.setCustomer(customer);//由於配置了多的一方到一的一方的關聯關系(當保存的時候,就已經對外鍵賦值)
2 customer.getLinkMans().add(linkMan);//由於配置了一的一方到多的一方的關聯關系(發送一條update語句)
結果:會有一條多余的update語句 ,是由於一的一方會維護外鍵,發送update語句
解決的辦法:只需要在一的一方放棄外鍵維護權即可
1 @OneToMany(mappedBy = "customer")// mappedBy:對方配置關系的屬性名稱,如下橙色條 2 private Set<LinkMan> linkMans = new HashSet<LinkMan>();對方配置關系的屬性名稱
@ManyToOne(targetEntity = Customer.class)
@JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
private Customer customer;
刪除說明:當在主表中設置了放棄外鍵維護權,導致從表中的外鍵無法修改,刪除時就會提示主鍵正在被占用不能刪除。 >> 真的想刪除,則使用級聯刪除 (實際開發中,慎用!! >> 一對多情況下)
級聯操作 >> 操作一個對象的同時操作他的關聯對象
- 需要區分操作主體
- 需要在操作主體的實體類上,添加級聯屬性(需要添加到多表映射關系的注解上)
-
cascade(配置級聯)
級聯添加
案例:當我保存一個客戶的同時保存聯系人
Customer
1 @OneToMany(mappedBy = "customer",cascade = CascadeType.ALL) //
- CascadeType.ALL:所有
- MERGE:更新
- PERSIST:保存
- REMOVE:刪除
2 private Set<LinkMan> linkMans = new HashSet<LinkMan>();
1 @Test 2 @Transactional //配置事務 3 @Rollback(false) //不自動回滾 4 public void testCascadeAdd() { 5 //創建一個客戶,創建一個聯系人 6 Customer customer = new Customer(); 7 customer.setCustname("百度級聯添加"); 8 9 LinkMan linkMan = new LinkMan(); 10 linkMan.setLkmName("小李級聯添加"); 11 12 linkMan.setCustomer(customer);//由於配置了多的一方到一的一方的關聯關系(當保存的時候,就已經對外鍵賦值) 13 customer.getLinkMans().add(linkMan);//由於配置了一的一方到多的一方的關聯關系(發送一條update語句) 14 15 customerDao.save(customer); 16 }
級聯刪除
案例:當我刪除一個客戶的同時刪除此客戶的所有聯系人
1 @Test 2 @Transactional //配置事務 3 @Rollback(false) //不自動回滾 4 public void testRemove() { 5 customerDao.delete(33l); 6 }
改進:根據客戶名查詢出id,再進行刪除操作
1 @Test 2 @Transactional //配置事務 3 @Rollback(false) //不自動回滾 4 public void testRemove1() { 5 Specification<Customer> spec=new Specification<Customer>() { 6 public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) { 7 Path<Object> custname = root.get("custname"); 8 Predicate predicate = criteriaBuilder.equal(custname, "百度級聯添加"); 9 return predicate; 10 }}; 11 Customer customerDaoOne = customerDao.findOne(spec); 12 Long custid = customerDaoOne.getCustid(); 13 customerDao.delete(custid); 14 }
多對多操作 案例:用戶和角色
1. 引入依賴坐標,導入實體類和xml文件,創建dao接口
2. User >> 配置用戶和聯系人之間的一對多關系
1 package cn.itcast.domain; 2 3 import lombok.Data; 4 5 import javax.persistence.*; 6 import java.util.HashSet; 7 import java.util.Set; 8 @Data 9 @Entity 10 @Table(name = "sys_user") 11 public class User { 12 @Id 13 @GeneratedValue(strategy = GenerationType.IDENTITY) 14 @Column(name="user_id") 15 private Long userId; 16 @Column(name="user_name") 17 private String userName; 18 @Column(name="age") 19 private Integer age; 20 /** 21 * 配置用戶到角色的多對多關系 22 * 配置多對多的映射關系 23 * 1.聲明表關系的配置 24 * @ManyToMany(targetEntity = Role.class) //多對多 25 * targetEntity:代表對方的實體類字節碼 26 * 2.配置中間表(包含兩個外鍵) 27 * @JoinTable 28 * name : 中間表的名稱 29 * joinColumns:配置當前對象在中間表的外鍵 30 * @JoinColumn的數組 31 * name:外鍵名 32 * referencedColumnName:參照的主表的主鍵名 33 * inverseJoinColumns:配置對方對象在中間表的外鍵 34 */ 35 @ManyToMany(targetEntity = Role.class,cascade = CascadeType.ALL) 36 @JoinTable(name = "sys_user_role", 37 //joinColumns,當前對象在中間表中的外鍵 38 joinColumns = {@JoinColumn(name = "sys_user_id",referencedColumnName = "user_id")}, name:外鍵名 referencedColumnName:參照的主表里的主鍵名 39 //inverseJoinColumns,對方對象在中間表的外鍵 40 inverseJoinColumns = {@JoinColumn(name = "sys_role_id",referencedColumnName = "role_id")} name:外鍵名 referencedColumnName:參照的主表里的主鍵名
41 ) 42 private Set<Role> roles = new HashSet<Role>(); 43 }
3. Role >> 配置用戶和聯系人之間的一對多關系
1 package cn.itcast.domain; 2 3 import lombok.Data; 4 5 import javax.persistence.*; 6 import java.util.HashSet; 7 import java.util.Set; 8 9 //@Data 10 @Entity 11 @Table(name = "sys_role") 12 public class Role { 13 14 @Id 15 @GeneratedValue(strategy = GenerationType.IDENTITY) 16 @Column(name = "role_id") 17 private Long roleId; 18 @Column(name = "role_name") 19 private String roleName; 20 21 /*@ManyToMany(targetEntity = User.class) 22 @JoinTable(name = "sys_user_role", 23 //joinColumns,當前對象在中間表中的外鍵 24 joinColumns = {@JoinColumn(name = "sys_role_id", referencedColumnName = "role_id")}, 25 //inverseJoinColumns,對方對象在中間表的外鍵 26 inverseJoinColumns = {@JoinColumn(name = "sys_user_id", referencedColumnName = "user_id")} 27 )*/ 28 29 @ManyToMany(mappedBy = "roles") //配置多表關系,被選擇的一方放棄 30 private Set<User> users = new HashSet<User>(); 31 32 public Long getRoleId() { return roleId; } 33 public void setRoleId(Long roleId) { this.roleId = roleId; } 34 public String getRoleName() { return roleName; } 35 public void setRoleName(String roleName) { this.roleName = roleName; } 36 public Set<User> getUsers() { return users; } 37 public void setUsers(Set<User> users) { this.users = users; } 38 }
保存一個用戶,保存一個角色
Role
1 @ManyToMany(mappedBy = "roles") //配置多表關系,放棄維護權,被選擇的一方放棄(因為角色被用戶所選擇)
2 private Set<User> users = new HashSet<User>();
1 @Test 2 @Transactional 3 @Rollback(false) 4 5 public void testAdd() { 6 User user = new User(); 7 user.setUserName("小馬"); 8 user.setAge(21); 9 10 Role role = new Role(); 11 role.setRoleName("tx程序員"); 12 13 //配置用戶到角色關系,可以對中間表中的數據進行維護 14 user.getRoles().add(role); 15 //配置角色到用戶關系 16 role.getUsers().add(user); 17 18 userDao.save(user); 19 roleDao.save(role); 20 }
級聯添加 / 級聯刪除(與一對多案例類似)
1 這里只給出 級聯刪除案例 2 @Transactional 3 @Rollback(false) 4 @Test 5 public void testCaseCadeRemove() {
6 User user = userDao.findOne(1l); 7 userDao.delete(user); 8 }
多表的查詢
對象導航查詢 :查詢一個對象的同時,通過此對象查詢他的關聯對象
使用 jpa-day03-onetomany 項目來演示
1 @Test 2 @Transactional no Session錯誤! >> 因為所有操作並沒有在一個事務中進行,所以需要添加事務注解
3 public void testQuery1(){ 4 //查詢id為6的客戶
5 Customer customer = customerDao.getOne(6l); 6 //對象導航查詢,此客戶下的所有聯系人
7 Set<LinkMan> linkMans = customer.getLinkMans(); 8 for (LinkMan linkMan : linkMans) { 9 System.out.println(linkMan); 10 } }
默認使用的是延遲加載的形式查詢的
調用get方法並不會立即發送查詢,而是在使用關聯對象的時候才會差和訊延遲加載!
修改配置,將延遲加載改為立即加載(GAGER)
fetch (延遲加載),需要配置到多表映射關系的注解上
LinkMan
1 @ManyToOne(targetEntity = Customer.class,fetch = FetchType.LAZY) 2 @JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id") 3 private Customer customer;
1 @Test 2 @Transactional 3 public void testQuery2(){ 4 LinkMan linkMan = linkManDao.findOne(1l); 5 //對象導航查詢所屬的語句 6 Customer customer = linkMan.getCustomer(); 7 System.out.println(customer); 8 }