标签:问题: att 作用 不能 创建 双向一对多 ica over 描述
有时我们在查询某个实体的时候,给定的条件是不固定的,这时就需要动态构建相应的查询语句,在Spring Data JPA中可以通过JpaSpecificationExecutor接口查询。相比JPQL,其优势是类型安全,更加的面向对象。
1 package org.springframework.data.jpa.repository; 2 3 import java.util.List; 4 import org.springframework.data.domain.Page; 5 import org.springframework.data.domain.Pageable; 6 import org.springframework.data.domain.Sort; 7 import org.springframework.data.jpa.domain.Specification; 8 9 /** 10 * JpaSpecificationExecutor中定义的方法 11 **/ 12 public interface JpaSpecificationExecutor<T> { 13 T findOne(Specification<T> var1); 14 15 List<T> findAll(Specification<T> var1); 16 17 Page<T> findAll(Specification<T> var1, Pageable var2); 18 19 List<T> findAll(Specification<T> var1, Sort var2); 20 21 long count(Specification<T> var1); 22 }
对于JpaSpecificationExecutor,这个接口基本是围绕着Specification接口来定义的。我们可以简单的理解为,Specification构造的就是查询条件。
Specification接口中只定义了如下一个方法:
1 //构造查询条件 2 /** 3 * root :Root接口,代表查询的根对象,可以通过root获取实体中的属性 4 * query :代表一个顶层查询对象,用来自定义查询 5 * cb :用来构建查询,此对象里有很多条件方法 6 **/ 7 public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);
Customer.java
1 package cn.itcast.domain; 2 3 import lombok.Getter; 4 import lombok.Setter; 5 import lombok.ToString; 6 7 import javax.persistence.*; 8 9 /** 10 * 1.实体类和表的映射关系 11 * @Eitity 12 * @Table 13 * 2.类中属性和表中字段的映射关系 14 * @Id 15 * @GeneratedValue 16 * @Column 17 */ 18 @Entity 19 @Table(name="cst_customer") 20 @Getter 21 @Setter 22 @ToString 23 public class Customer { 24 25 @Id 26 @GeneratedValue(strategy = GenerationType.IDENTITY) 27 @Column(name = "cust_id") 28 private Long custId; 29 @Column(name = "cust_address") 30 private String custAddress; 31 @Column(name = "cust_industry") 32 private String custIndustry; 33 @Column(name = "cust_level") 34 private String custLevel; 35 @Column(name = "cust_name") 36 private String custName; 37 @Column(name = "cust_phone") 38 private String custPhone; 39 @Column(name = "cust_source") 40 private String custSource; 41 42 }
CustomerDao.java
1 package cn.itcast.dao; 2 3 4 import cn.itcast.domain.Customer; 5 import org.springframework.data.jpa.repository.JpaRepository; 6 import org.springframework.data.jpa.repository.JpaSpecificationExecutor; 7 8 public interface CustomerDao extends JpaRepository<Customer,Long>, JpaSpecificationExecutor<Customer> { 9 }
SpecTest.java
1 package cn.itcast.test; 2 3 import cn.itcast.dao.CustomerDao; 4 import cn.itcast.domain.Customer; 5 import org.junit.Test; 6 import org.junit.runner.RunWith; 7 import org.springframework.beans.factory.annotation.Autowired; 8 import org.springframework.data.domain.Page; 9 import org.springframework.data.domain.PageRequest; 10 import org.springframework.data.domain.Pageable; 11 import org.springframework.data.domain.Sort; 12 import org.springframework.data.jpa.domain.Specification; 13 import org.springframework.test.context.ContextConfiguration; 14 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 15 16 import javax.persistence.criteria.*; 17 import java.util.List; 18 19 @RunWith(SpringJUnit4ClassRunner.class) 20 @ContextConfiguration(locations = "classpath:applicationContext.xml") 21 public class SpecTest { 22 23 24 @Autowired 25 private CustomerDao customerDao; 26 27 /** 28 * 根据条件,查询单个对象 29 * 30 */ 31 @Test 32 public void testSpec() { 33 //匿名内部类 34 /** 35 * 自定义查询条件 36 * 1.实现Specification接口(提供泛型:查询的对象类型) 37 * 2.实现toPredicate方法(构造查询条件) 38 * 3.需要借助方法参数中的两个参数( 39 * root:获取需要查询的对象属性 40 * CriteriaBuilder:构造查询条件的,内部封装了很多的查询条件(模糊匹配,精准匹配) 41 * ) 42 * 案例:根据客户名称查询,查询客户名为传智播客的客户 43 * 查询条件 44 * 1.查询方式 45 * cb对象 46 * 2.比较的属性名称 47 * root对象 48 * 49 */ 50 Specification<Customer> spec = new Specification<Customer>() { 51 @Override 52 public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) { 53 //1.获取比较的属性 54 Path<Object> custName = root.get("custName"); 55 //2.构造查询条件 : select * from cst_customer where cust_name = ‘传智播客‘ 56 /** 57 * 第一个参数:需要比较的属性(path对象) 58 * 第二个参数:当前需要比较的取值 59 */ 60 Predicate predicate = cb.equal(custName, "传智播客");//进行精准的匹配 (比较的属性,比较的属性的取值) 61 return predicate; 62 } 63 }; 64 Customer customer = customerDao.findOne(spec); 65 System.out.println(customer); 66 } 67 68 /** 69 * 多条件查询 70 * 案例:根据客户名(传智播客)和客户所属行业查询(it教育) 71 * 72 */ 73 @Test 74 public void testSpec1() { 75 /** 76 * root:获取属性 77 * 客户名 78 * 所属行业 79 * cb:构造查询 80 * 1.构造客户名的精准匹配查询 81 * 2.构造所属行业的精准匹配查询 82 * 3.将以上两个查询联系起来 83 */ 84 Specification<Customer> spec = new Specification<Customer>() { 85 @Override 86 public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) { 87 Path<Object> custName = root.get("custName");//客户名 88 Path<Object> custIndustry = root.get("custIndustry");//所属行业 89 90 //构造查询 91 //1.构造客户名的精准匹配查询 92 Predicate p1 = cb.equal(custName, "传智播客");//第一个参数,path(属性),第二个参数,属性的取值 93 //2..构造所属行业的精准匹配查询 94 Predicate p2 = cb.equal(custIndustry, "it教育"); 95 //3.将多个查询条件组合到一起:组合(满足条件一并且满足条件二:与关系,满足条件一或满足条件二即可:或关系) 96 Predicate and = cb.and(p1, p2);//以与的形式拼接多个查询条件 97 // cb.or();//以或的形式拼接多个查询条件 98 return and; 99 } 100 }; 101 Customer customer = customerDao.findOne(spec); 102 System.out.println(customer); 103 } 104 105 /** 106 * 案例:完成根据客户名称的模糊匹配,返回客户列表 107 * 客户名称以 ’传智播客‘ 开头 108 * 109 * equal :直接的到path对象(属性),然后进行比较即可 110 * gt,lt,ge,le,like : 得到path对象,根据path指定比较的参数类型,再去进行比较 111 * 指定参数类型:path.as(类型的字节码对象) 112 */ 113 @Test 114 public void testSpec3() { 115 //构造查询条件 116 Specification<Customer> spec = new Specification<Customer>() { 117 @Override 118 public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) { 119 //查询属性:客户名 120 Path<Object> custName = root.get("custName"); 121 //查询方式:模糊匹配 122 Predicate like = cb.like(custName.as(String.class), "传智播客%"); 123 return like; 124 } 125 }; 126 // List<Customer> list = customerDao.findAll(spec); 127 // for (Customer customer : list) { 128 // System.out.println(customer); 129 // } 130 //添加排序 131 //创建排序对象,需要调用构造方法实例化sort对象 132 //第一个参数:排序的顺序(倒序,正序) 133 // Sort.Direction.DESC:倒序 134 // Sort.Direction.ASC : 升序 135 //第二个参数:排序的属性名称 136 Sort sort = new Sort(Sort.Direction.DESC,"custId"); 137 List<Customer> list = customerDao.findAll(spec, sort); 138 for (Customer customer : list) { 139 System.out.println(customer); 140 } 141 } 142 143 144 /** 145 * 分页查询 146 * Specification: 查询条件 147 * Pageable:分页参数 148 * 分页参数:查询的页码,每页查询的条数 149 * findAll(Specification,Pageable):带有条件的分页 150 * findAll(Pageable):没有条件的分页 151 * 返回:Page(springDataJpa为我们封装好的pageBean对象,数据列表,共条数) 152 */ 153 @Test 154 public void testSpec4() { 155 156 Specification spec = null; 157 //PageRequest对象是Pageable接口的实现类 158 /** 159 * 创建PageRequest的过程中,需要调用他的构造方法传入两个参数 160 * 第一个参数:当前查询的页数(从0开始) 161 * 第二个参数:每页查询的数量 162 */ 163 Pageable pageable = new PageRequest(0,2); 164 //分页查询 165 Page<Customer> page = customerDao.findAll(null, pageable); 166 System.out.println(page.getContent()); //得到数据集合列表 167 System.out.println(page.getTotalElements());//得到总条数 168 System.out.println(page.getTotalPages());//得到总页数 169 } 170 171 172 }
对于Spring Data JPA中的分页查询,是其内部自动实现的封装过程,返回的是一个Spring Data JPA提供的pageBean对象。其中的方法说明如下:
1 //获取总页数 2 int getTotalPages(); 3 //获取总记录数 4 long getTotalElements(); 5 //获取列表数据 6 List<T> getContent();
数据库中多表之间存在着三种关系,如图所示。
从图可以看出,系统设计的三种实体关系分别为:多对多、一对多和一对一关系。注意:一对多关系可以看为两种: 即一对多,多对一。所以说四种更精确。
明确: 我们只涉及实际开发中常用的关联关系,一对多和多对多。而一对一的情况,在实际开发中几乎不用。
在实际开发中,我们数据库的表难免会有相互的关联关系,在操作表的时候就有可能会涉及到多张表的操作。而在这种实现了ORM思想的框架中(如JPA),可以让我们通过操作实体类就实现对数据库表的操作。所以今天我们的学习重点是:掌握配置实体之间的关联关系。
第一步:首先确定两张表之间的关系。
如果关系确定错了,后面做的所有操作就都不可能正确。
第二步:在数据库中实现两张表的关系
第三步:在实体类中描述出两个实体的关系
第四步:配置出实体类和数据库表的关系映射(重点)
我们采用的示例为客户和联系人。
客户:指的是一家公司,我们记为A。
联系人:指的是A公司中的员工。
在不考虑兼职的情况下,公司和员工的关系即为一对多。
在一对多关系中,我们习惯把一的一方称之为主表,把多的一方称之为从表。在数据库中建立一对多的关系,需要使用数据库的外键约束。
什么是外键?
指的是从表中有一列,取值参照主表的主键,这一列就是外键。
一对多数据库关系的建立,如下图所示
在实体类中,由于客户是少的一方,它应该包含多个联系人,所以实体类要体现出客户中有多个联系人的信息,代码如下:
1 package cn.itcast.domain; 2 3 import lombok.Getter; 4 import lombok.Setter; 5 import lombok.ToString; 6 7 import javax.persistence.*; 8 import java.util.HashSet; 9 import java.util.Set; 10 11 /** 12 * 1.实体类和表的映射关系 13 * @Eitity 14 * @Table 15 * 2.类中属性和表中字段的映射关系 16 * @Id 17 * @GeneratedValue 18 * @Column 19 */ 20 @Entity 21 @Table(name="cst_customer") 22 @Getter 23 @Setter 24 @ToString 25 public class Customer { 26 27 @Id 28 @GeneratedValue(strategy = GenerationType.IDENTITY) 29 @Column(name = "cust_id") 30 private Long custId; 31 @Column(name = "cust_address") 32 private String custAddress; 33 @Column(name = "cust_industry") 34 private String custIndustry; 35 @Column(name = "cust_level") 36 private String custLevel; 37 @Column(name = "cust_name") 38 private String custName; 39 @Column(name = "cust_phone") 40 private String custPhone; 41 @Column(name = "cust_source") 42 private String custSource; 43 44 //配置客户和联系人之间的关系(一对多关系) 45 /** 46 * 使用注解的形式配置多表关系 47 * 1.声明关系 48 * @OneToMany : 配置一对多关系 49 * targetEntity :对方对象的字节码对象 50 * 2.配置外键(中间表) 51 * @JoinColumn : 配置外键 52 * name:外键字段名称 53 * referencedColumnName:参照的主表的主键字段名称 54 * 55 * * 在客户实体类上(一的一方)添加了外键了配置,所以对于客户而言,也具备了维护外键的作用 56 * 57 */ 58 59 //@OneToMany(targetEntity = LinkMan.class) 60 //@JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id") 61 /** 62 * 放弃外键维护权 63 * mappedBy:对方配置关系的属性名称64 * cascade : 配置级联(可以配置到设置多表的映射关系的注解上) 65 * CascadeType.all : 所有 66 * MERGE :更新 67 * PERSIST :保存 68 * REMOVE :删除 69 * 70 * fetch : 配置关联对象的加载方式 71 * EAGER :立即加载(有左外连接 left out join) 72 * LAZY :延迟加载 73 74 */ 75 //@OneToMany(mappedBy = "customer",cascade = CascadeType.ALL,fetch = FetchType.EAGER) 76 @OneToMany(mappedBy = "customer",cascade = CascadeType.ALL) 77 private Set<LinkMan> linkMans = new HashSet<>(); 78 79 80 }
由于联系人是多的一方,在实体类中要体现出,每个联系人只能对应一个客户,代码如下:
1 package cn.itcast.domain; 2 3 4 import lombok.Getter; 5 import lombok.Setter; 6 7 import javax.persistence.Entity; 8 import javax.persistence.*; 9 10 @Entity 11 @Table(name = "cst_linkman") 12 @Setter 13 @Getter 14 public class LinkMan { 15 16 @Id 17 @GeneratedValue(strategy = GenerationType.IDENTITY) 18 @Column(name = "lkm_id") 19 private Long lkmId; //联系人编号(主键) 20 @Column(name = "lkm_name") 21 private String lkmName;//联系人姓名 22 @Column(name = "lkm_gender") 23 private String lkmGender;//联系人性别 24 @Column(name = "lkm_phone") 25 private String lkmPhone;//联系人办公电话 26 @Column(name = "lkm_mobile") 27 private String lkmMobile;//联系人手机 28 @Column(name = "lkm_email") 29 private String lkmEmail;//联系人邮箱 30 @Column(name = "lkm_position") 31 private String lkmPosition;//联系人职位 32 @Column(name = "lkm_memo") 33 private String lkmMemo;//联系人备注 34 35 /** 36 * 配置联系人到客户的多对一关系 37 * 使用注解的形式配置多对一关系 38 * 1.配置表关系 39 * @ManyToOne : 配置多对一关系 40 * targetEntity:对方的实体类字节码 41 * 2.配置外键(中间表) 42 * 43 * * 配置外键的过程,配置到了多的一方,就会在多的一方维护外键 44 * 45 */ 46 @ManyToOne(targetEntity = Customer.class,fetch = FetchType.LAZY) 47 @JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id") 48 private Customer customer; 49 }
@OneToMany:
作用:建立一对多的关系映射
属性:
targetEntityClass:指定多的多方的类的字节码
mappedBy:指定从表实体类中引用主表对象的名称。
cascade:指定要使用的级联操作
fetch:指定是否采用延迟加载
orphanRemoval:是否使用孤儿删除
@ManyToOne
作用:建立多对一的关系
属性:
targetEntityClass:指定一的一方实体类字节码
cascade:指定要使用的级联操作
fetch:指定是否采用延迟加载
optional:关联是否可选。如果设置为false,则必须始终存在非空关系。
@JoinColumn
作用:用于定义主键字段和外键字段的对应关系。
属性:
name:指定外键字段的名称
referencedColumnName:指定引用主表的主键字段名称
unique:是否唯一。默认值不唯一
nullable:是否允许为空。默认值允许。
insertable:是否允许插入。默认值允许。
updatable:是否允许更新。默认值允许。
columnDefinition:列的定义信息。
1 @RunWith(SpringJUnit4ClassRunner.class) 2 @ContextConfiguration(locations="classpath:applicationContext.xml") 3 public class OneToManyTest { 4 5 @Autowired 6 private CustomerDao customerDao; 7 8 @Autowired 9 private LinkManDao linkManDao; 10 11 12 /** 13 * 保存操作 14 * 需求: 15 * 保存一个客户和一个联系人 16 * 要求: 17 * 创建一个客户对象和一个联系人对象 18 * 建立客户和联系人之间关联关系(双向一对多的关联关系) 19 * 先保存客户,再保存联系人 20 * 问题: 21 * 当我们建立了双向的关联关系之后,先保存主表,再保存从表时: 22 * 会产生2条insert和1条update. 23 * 而实际开发中我们只需要2条insert。 24 * 25 */ 26 @Test 27 @Transactional //开启事务 28 @Rollback(false)//设置为不回滚 29 public void testAdd() { 30 Customer c = new Customer(); 31 c.setCustName("TBD云集中心"); 32 c.setCustLevel("VIP客户"); 33 c.setCustSource("网络"); 34 c.setCustIndustry("商业办公"); 35 c.setCustAddress("昌平区北七家镇"); 36 c.setCustPhone("010-84389340"); 37 38 LinkMan l = new LinkMan(); 39 l.setLkmName("TBD联系人"); 40 l.setLkmGender("male"); 41 l.setLkmMobile("13811111111"); 42 l.setLkmPhone("010-34785348"); 43 l.setLkmEmail("98354834@qq.com"); 44 l.setLkmPosition("老师"); 45 l.setLkmMemo("还行吧"); 46 47 c.getLinkMans().add(l); 48 l.setCustomer(c); 49 customerDao.save(c); 50 linkManDao.save(l); 51 } 52 }
通过保存的案例,我们可以发现在设置了双向关系之后,会发送两条insert语句,一条多余的update语句,那我们的解决是思路很简单,就是一的一方放弃维护权
1 /** 2 *放弃外键维护权的配置将如下配置改为 3 */ 4 //@OneToMany(targetEntity=LinkMan.class) 5 //@JoinColumn(name="lkm_cust_id",referencedColumnName="cust_id") 6 //设置为 7 @OneToMany(mappedBy="customer")
1 @Autowired 2 private CustomerDao customerDao; 3 4 @Test 5 @Transactional 6 @Rollback(false)//设置为不回滚 7 public void testDelete() { 8 customerDao.delete(1l); 9 }
删除操作的说明如下:
删除从表数据:可以随时任意删除。
删除主表数据:
1、在默认情况下,它会把外键字段置为null,然后删除主表数据。如果在数据库的表 结构上,外键字段有非空约束,默认情况就会报错了。
2、如果配置了放弃维护关联关系的权利,则不能删除(与外键字段是否允许为null, 没有关系)因为在删除时,它根本不会去更新从表的外键字段了。
3、如果还想删除,使用级联删除引用
在实际开发中,级联删除请慎用!(在一对多的情况下)
级联操作:指操作一个对象同时操作它的关联对象
使用方法:只需要在操作主体的注解上配置cascade
1 /** 2 * cascade:配置级联操作 3 * CascadeType.MERGE 级联更新 4 * CascadeType.PERSIST 级联保存: 5 * CascadeType.REFRESH 级联刷新: 6 * CascadeType.REMOVE 级联删除: 7 * CascadeType.ALL 包含所有 8 */ 9 @OneToMany(mappedBy="customer",cascade=CascadeType.ALL)
综合测试实例:
1 package cn.itcast.test; 2 3 import cn.itcast.dao.CustomerDao; 4 import cn.itcast.dao.LinkManDao; 5 import cn.itcast.domain.Customer; 6 import cn.itcast.domain.LinkMan; 7 import org.junit.Test; 8 import org.junit.runner.RunWith; 9 import org.springframework.beans.factory.annotation.Autowired; 10 import org.springframework.test.annotation.Rollback; 11 import org.springframework.test.context.ContextConfiguration; 12 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 13 import org.springframework.transaction.annotation.Transactional; 14 15 @RunWith(SpringJUnit4ClassRunner.class) 16 @ContextConfiguration(locations = "classpath:applicationContext.xml") 17 public class OneToManyTest { 18 19 @Autowired 20 private CustomerDao customerDao; 21 22 @Autowired 23 private LinkManDao linkManDao; 24 25 /** 26 * 保存一个客户,保存一个联系人 27 * 效果:客户和联系人作为独立的数据保存到数据库中 28 * 联系人的外键为空 29 * 原因? 30 * 实体类中没有配置关系 31 */ 32 @Test 33 @Transactional //配置事务 34 @Rollback(false) //不自动回滚 35 public void testAdd() { 36 //创建一个客户,创建一个联系人 37 Customer customer = new Customer(); 38 customer.setCustName("百度"); 39 40 LinkMan linkMan = new LinkMan(); 41 linkMan.setLkmName("小李"); 42 43 /** 44 * 配置了客户到联系人的关系 45 * 从客户的角度上:发送两条insert语句,发送一条更新语句更新数据库(更新外键) 46 * 由于我们配置了客户到联系人的关系:客户可以对外键进行维护 47 */ 48 customer.getLinkMans().add(linkMan); 49 50 51 customerDao.save(customer); 52 linkManDao.save(linkMan); 53 } 54 55 56 57 @Test 58 @Transactional //配置事务 59 @Rollback(false) //不自动回滚 60 public void testAdd1() { 61 //创建一个客户,创建一个联系人 62 Customer customer = new Customer(); 63 customer.setCustName("百度"); 64 65 LinkMan linkMan = new LinkMan(); 66 linkMan.setLkmName("小李"); 67 68 /** 69 * 配置联系人到客户的关系(多对一) 70 * 只发送了两条insert语句 71 * 由于配置了联系人到客户的映射关系(多对一) 72 * 73 * 74 */ 75 linkMan.setCustomer(customer); 76 77 customerDao.save(customer); 78 linkManDao.save(linkMan); 79 } 80 81 /** 82 * 会有一条多余的update语句 83 * * 由于一的一方可以维护外键:会发送update语句 84 * * 解决此问题:只需要在一的一方放弃维护权即可 85 * 86 */ 87 @Test 88 @Transactional //配置事务 89 @Rollback(false) //不自动回滚 90 public void testAdd2() { 91 //创建一个客户,创建一个联系人 92 Customer customer = new Customer(); 93 customer.setCustName("百度"); 94 95 LinkMan linkMan = new LinkMan(); 96 linkMan.setLkmName("小李"); 97 98 99 linkMan.setCustomer(customer);//由于配置了多的一方到一的一方的关联关系(当保存的时候,就已经对外键赋值) 100 customer.getLinkMans().add(linkMan);//由于配置了一的一方到多的一方的关联关系(发送一条update语句) 101 102 customerDao.save(customer); 103 linkManDao.save(linkMan); 104 } 105 106 /** 107 * 级联添加:保存一个客户的同时,保存客户的所有联系人 108 * 需要在操作主体的实体类上,配置casacde属性 109 */ 110 @Test 111 @Transactional //配置事务 112 @Rollback(false) //不自动回滚 113 public void testCascadeAdd() { 114 Customer customer = new Customer(); 115 customer.setCustName("百度1"); 116 117 LinkMan linkMan = new LinkMan(); 118 linkMan.setLkmName("小李1"); 119 120 linkMan.setCustomer(customer); 121 customer.getLinkMans().add(linkMan); 122 123 customerDao.save(customer); 124 } 125 126 /** 127 * 级联删除: 128 * 删除1号客户的同时,删除1号客户的所有联系人 129 */ 130 @Test 131 @Transactional //配置事务 132 @Rollback(false) //不自动回滚 133 public void testCascadeRemove() { 134 //1.查询1号客户 135 Customer customer = customerDao.findOne(1l); 136 //2.删除1号客户 137 customerDao.delete(customer); 138 } 139 140 }
我们采用的示例为用户和角色。
用户:指的是咱们班的每一个同学。
角色:指的是咱们班同学的身份信息。
比如A同学,它是我的学生,其中有个身份就是学生,还是家里的孩子,那么他还有个身份是子女。
同时B同学,它也具有学生和子女的身份。
那么任何一个同学都可能具有多个身份。同时学生这个身份可以被多个同学所具有。
所以我们说,用户和角色之间的关系是多对多。
多对多的表关系建立靠的是中间表,其中用户表和中间表的关系是一对多,角色表和中间表的关系也是一对多,如下图所示:
一个用户可以具有多个角色,所以在用户实体类中应该包含多个角色的信息,代码如下:
1 /** 2 * 用户的数据模型 3 */ 4 @Entity 5 @Table(name="sys_user") 6 public class SysUser implements Serializable { 7 8 @Id 9 @GeneratedValue(strategy=GenerationType.IDENTITY) 10 @Column(name="user_id") 11 private Long userId; 12 @Column(name="user_code") 13 private String userCode; 14 @Column(name="user_name") 15 private String userName; 16 @Column(name="user_password") 17 private String userPassword; 18 @Column(name="user_state") 19 private String userState; 20 21 //多对多关系映射 22 @ManyToMany(mappedBy="users") 23 private Set<SysRole> roles = new HashSet<SysRole>(0); 24 25 public Long getUserId() { 26 return userId; 27 } 28 public void setUserId(Long userId) { 29 this.userId = userId; 30 } 31 public String getUserCode() { 32 return userCode; 33 } 34 public void setUserCode(String userCode) { 35 this.userCode = userCode; 36 } 37 public String getUserName() { 38 return userName; 39 } 40 public void setUserName(String userName) { 41 this.userName = userName; 42 } 43 public String getUserPassword() { 44 return userPassword; 45 } 46 public void setUserPassword(String userPassword) { 47 this.userPassword = userPassword; 48 } 49 public String getUserState() { 50 return userState; 51 } 52 public void setUserState(String userState) { 53 this.userState = userState; 54 } 55 public Set<SysRole> getRoles() { 56 return roles; 57 } 58 public void setRoles(Set<SysRole> roles) { 59 this.roles = roles; 60 } 61 @Override 62 public String toString() { 63 return "SysUser [userId=" + userId + ", userCode=" + userCode + ", userName=" + userName + ", userPassword=" 64 + userPassword + ", userState=" + userState + "]"; 65 } 66 }
一个角色可以赋予多个用户,所以在角色实体类中应该包含多个用户的信息,代码如下:
1 /** 2 * 角色的数据模型 3 */ 4 @Entity 5 @Table(name="sys_role") 6 public class SysRole implements Serializable { 7 8 @Id 9 @GeneratedValue(strategy=GenerationType.IDENTITY) 10 @Column(name="role_id") 11 private Long roleId; 12 @Column(name="role_name") 13 private String roleName; 14 @Column(name="role_memo") 15 private String roleMemo; 16 17 //多对多关系映射 18 @ManyToMany 19 @JoinTable(name="user_role_rel",//中间表的名称 20 //中间表user_role_rel字段关联sys_role表的主键字段role_id 21 joinColumns={@JoinColumn(name="role_id",referencedColumnName="role_id")}, 22 //中间表user_role_rel的字段关联sys_user表的主键user_id 23 inverseJoinColumns={@JoinColumn(name="user_id",referencedColumnName="user_id")} 24 ) 25 private Set<SysUser> users = new HashSet<SysUser>(0); 26 27 28 public Long getRoleId() { 29 return roleId; 30 } 31 public void setRoleId(Long roleId) { 32 this.roleId = roleId; 33 } 34 public String getRoleName() { 35 return roleName; 36 } 37 public void setRoleName(String roleName) { 38 this.roleName = roleName; 39 } 40 public String getRoleMemo() { 41 return roleMemo; 42 } 43 public void setRoleMemo(String roleMemo) { 44 this.roleMemo = roleMemo; 45 } 46 public Set<SysUser> getUsers() { 47 return users; 48 } 49 public void setUsers(Set<SysUser> users) { 50 this.users = users; 51 } 52 @Override 53 public String toString() { 54 return "SysRole [roleId=" + roleId + ", roleName=" + roleName + ", roleMemo=" + roleMemo + "]"; 55 } 56 }
@ManyToMany
作用:用于映射多对多关系
属性:
cascade:配置级联操作。
fetch:配置是否采用延迟加载。
targetEntity:配置目标的实体类。映射多对多的时候不用写。
@JoinTable
作用:针对中间表的配置
属性:
nam:配置中间表的名称
joinColumns:中间表的外键字段关联当前实体类所对应表的主键字段
inverseJoinColumn:中间表的外键字段关联对方表的主键字段
@JoinColumn
作用:用于定义主键字段和外键字段的对应关系。
属性:
name:指定外键字段的名称
referencedColumnName:指定引用主表的主键字段名称
unique:是否唯一。默认值不唯一
nullable:是否允许为空。默认值允许。
insertable:是否允许插入。默认值允许。
updatable:是否允许更新。默认值允许。
columnDefinition:列的定义信息。
1 @Autowired 2 private UserDao userDao; 3 4 @Autowired 5 private RoleDao roleDao; 6 /** 7 * 需求: 8 * 保存用户和角色 9 * 要求: 10 * 创建2个用户和3个角色 11 * 让1号用户具有1号和2号角色(双向的) 12 * 让2号用户具有2号和3号角色(双向的) 13 * 保存用户和角色 14 * 问题: 15 * 在保存时,会出现主键重复的错误,因为都是要往中间表中保存数据造成的。 16 * 解决办法: 17 * 让任意一方放弃维护关联关系的权利 18 */ 19 @Test 20 @Transactional //开启事务 21 @Rollback(false)//设置为不回滚 22 public void test1(){ 23 //创建对象 24 SysUser u1 = new SysUser(); 25 u1.setUserName("用户1"); 26 SysRole r1 = new SysRole(); 27 r1.setRoleName("角色1"); 28 //建立关联关系 29 u1.getRoles().add(r1); 30 r1.getUsers().add(u1); 31 //保存 32 roleDao.save(r1); 33 userDao.save(u1); 34 }
在多对多(保存)中,如果双向都设置关系,意味着双方都维护中间表,都会往中间表插入数据,中间表的2个字段又作为联合主键,所以报错,主键重复,解决保存失败的问题:只需要在任意一方放弃对中间表的维护权即可,推荐在被动的一方放弃,配置如下:
1 //放弃对中间表的维护权,解决保存中主键冲突的问题 2 @ManyToMany(mappedBy="roles") 3 private Set<SysUser> users = new HashSet<SysUser>(0);
1 @Autowired 2 private UserDao userDao; 3 /** 4 * 删除操作 5 * 在多对多的删除时,双向级联删除根本不能配置 6 * 禁用 7 * 如果配了的话,如果数据之间有相互引用关系,可能会清空所有数据 8 */ 9 @Test 10 @Transactional 11 @Rollback(false)//设置为不回滚 12 public void testDelete() { 13 userDao.delete(1l); 14 }
对象图导航检索方式是根据已经加载的对象,导航到他的关联对象。它利用类与类之间的关系来检索对象。例如:我们通过ID查询方式查出一个客户,可以调用Customer类中的getLinkMans()方法来获取该客户的所有联系人。对象导航查询的使用要求是:两个对象之间必须存在关联关系。
查询一个客户,获取该客户下的所有联系人
1 @Autowired 2 private CustomerDao customerDao; 3 4 @Test 5 //由于是在java代码中测试,为了解决no session问题,将操作配置到同一个事务中 6 @Transactional 7 public void testFind() { 8 Customer customer = customerDao.findOne(5l); 9 Set<LinkMan> linkMans = customer.getLinkMans();//对象导航查询 10 for(LinkMan linkMan : linkMans) { 11 System.out.println(linkMan); 12 } 13 }
查询一个联系人,获取该联系人的所有客户
1 @Autowired 2 private LinkManDao linkManDao; 3 4 5 @Test 6 public void testFind() { 7 LinkMan linkMan = linkManDao.findOne(4l); 8 Customer customer = linkMan.getCustomer(); //对象导航查询 9 System.out.println(customer); 10 }
对象导航查询的问题分析
问题1:我们查询客户时,要不要把联系人查询出来?
分析:如果我们不查的话,在用的时候还要自己写代码,调用方法去查询。如果我们查出来的,不使用时又会白白的浪费了服务器内存。
解决:采用延迟加载的思想。通过配置的方式来设定当我们在需要使用时,发起真正的查询。
配置方式:
1 /** 2 * 在客户对象的@OneToMany注解中添加fetch属性 3 * FetchType.EAGER :立即加载 4 * FetchType.LAZY :延迟加载 5 */ 6 @OneToMany(mappedBy="customer",fetch=FetchType.EAGER) 7 private Set<LinkMan> linkMans = new HashSet<>(0);
问题2:我们查询联系人时,要不要把客户查询出来?
分析:例如:查询联系人详情时,肯定会看看该联系人的所属客户。如果我们不查的话,在用的时候还要自己写代码,调用方法去查询。如果我们查出来的话,一个对象不会消耗太多的内存。而且多数情况下我们都是要使用的。
解决:采用立即加载的思想。通过配置的方式来设定,只要查询从表实体,就把主表实体对象同时查出来
配置方式:
1 /** 2 * 在联系人对象的@ManyToOne注解中添加fetch属性 3 * FetchType.EAGER :立即加载 4 * FetchType.LAZY :延迟加载 5 */ 6 @ManyToOne(targetEntity=Customer.class,fetch=FetchType.EAGER) 7 @JoinColumn(name="cst_lkm_id",referencedColumnName="cust_id") 8 private Customer customer;
查询代码示例:
1 package cn.itcast.test; 2 3 import cn.itcast.dao.CustomerDao; 4 import cn.itcast.dao.LinkManDao; 5 import cn.itcast.domain.Customer; 6 import cn.itcast.domain.LinkMan; 7 import org.junit.Test; 8 import org.junit.runner.RunWith; 9 import org.springframework.beans.factory.annotation.Autowired; 10 import org.springframework.test.context.ContextConfiguration; 11 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 12 import org.springframework.transaction.annotation.Transactional; 13 14 import java.util.Set; 15 16 @RunWith(SpringJUnit4ClassRunner.class) 17 @ContextConfiguration(locations = "classpath:applicationContext.xml") 18 public class ObjectQueryTest { 19 20 @Autowired 21 private CustomerDao customerDao; 22 23 @Autowired 24 private LinkManDao linkManDao; 25 26 //could not initialize proxy - no Session 27 //测试对象导航查询(查询一个对象的时候,通过此对象查询所有的关联对象) 28 @Test 29 @Transactional // 解决在java代码中的no session问题 30 public void testQuery1(){ 31 //查询id为1的客户 32 Customer customer = customerDao.getOne(1l); 33 //对象导航查询,此客户下的所有联系人 34 Set<LinkMan> linkMans = customer.getLinkMans(); 35 36 for (LinkMan linkMan:linkMans){ 37 System.out.println(linkMan); 38 } 39 40 } 41 42 /** 43 * 对象导航查询: 44 * 默认使用的是延迟加载的形式查询的 45 * 调用get方法并不会立即发送查询,而是在使用关联对象的时候才会查询 46 * 延迟加载! 47 * 修改配置,将延迟加载改为立即加载 48 * fetch,需要配置到多表映射关系的注解上 49 * 50 */ 51 52 @Test 53 @Transactional // 解决在java代码中的no session问题 54 public void testQuery2() { 55 //查询id为1的客户 56 Customer customer = customerDao.findOne(1l); 57 //对象导航查询,此客户下的所有联系人 58 Set<LinkMan> linkMans = customer.getLinkMans(); 59 60 System.out.println(linkMans.size()); 61 } 62 }
1 /** 2 * Specification的多表查询 3 */ 4 @Test 5 public void testFind() { 6 Specification<LinkMan> spec = new Specification<LinkMan>() { 7 public Predicate toPredicate(Root<LinkMan> root, CriteriaQuery<?> query, CriteriaBuilder cb) { 8 //Join代表链接查询,通过root对象获取 9 //创建的过程中,第一个参数为关联对象的属性名称,第二个参数为连接查询的方式(left,inner,right) 10 //JoinType.LEFT : 左外连接,JoinType.INNER:内连接,JoinType.RIGHT:右外连接 11 Join<LinkMan, Customer> join = root.join("customer",JoinType.INNER); 12 return cb.like(join.get("custName").as(String.class),"传智播客1"); 13 } 14 }; 15 List<LinkMan> list = linkManDao.findAll(spec); 16 for (LinkMan linkMan : list) { 17 System.out.println(linkMan); 18 } 19 }
说明:第一天笔记分散编写,没有指出为第一天讲义。
标签:问题: att 作用 不能 创建 双向一对多 ica over 描述
原文地址:https://www.cnblogs.com/116970u/p/11607628.html