多表之间的关系和操作多表的操作步骤
表关系
- 一对多
- 一对多 >> 一:主表 多:从表
- 多对多 >> 中间表中最少应该由两个字段组成,这两个字段作为外键指向两张表的主键,又组成了联合主键
分析步骤
- 明确表关系
- 确定表关系( 描述: 外键 | 中间表 )
- 编写实体类,在实体类中描述表关系(包含关系)
- 配置映射关系
完成多表操作
一对多操作 案例:客户和联系人(一对多关系)
>> 客户:一家公司 联系人:这家公司的员工
一个客户可以具有多个联系人,一个联系人从属于一家公司
分析步骤
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 linkMans = new HashSet();
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 linkMans = new HashSet();对方配置关系的属性名称
@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 SetlinkMans = new HashSet ();
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 Specificationspec=new Specification () { 6 public Predicate toPredicate(Root root, CriteriaQuery> criteriaQuery, CriteriaBuilder criteriaBuilder) { 7 Path
多对多操作 案例:用户和角色
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 roles = new HashSet(); 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 Setusers = new HashSet (); 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 getUsers() { return users; } 37 public void setUsers(Set users) { this.users = users; } 38 }
保存一个用户,保存一个角色
Role
1 @ManyToMany(mappedBy = "roles") //配置多表关系,放弃维护权,被选择的一方放弃(因为角色被用户所选择)
2 private Set users = new HashSet();
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 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 }