Specifications动态查询
有时我们在查询某个实体的时候,给定的条件是不固定的,这时就需要动态构建相应的查询语句,在Spring Data JPA中可以通过JpaSpecificationExecutor接口查询。相比JPQL,其优势是类型安全,更加的面向对象。
JpaSpecificationExecutor 方法列表
T findOne(Specification<T> spec); //查询单个对象 List<T> findAll(Specification<T> spec); //查询列表 //查询全部,分页 //pageable:分页参数 //返回值:分页pageBean(page:是springdatajpa提供的) Page<T> findAll(Specification<T> spec, Pageable pageable); //查询列表 //Sort:排序参数 List<T> findAll(Specification<T> spec, Sort sort); long count(Specification<T> spec);//统计查询 Specification :查询条件
自定义我们自己的Specification实现类
实现 //root:查询的根对象(查询的任何属性都可以从根对象中获取) //CriteriaQuery:顶层查询对象,自定义查询方式(了解:一般不用) //CriteriaBuilder:查询的构造器,封装了很多的查询条件 Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb); //封装查询条件
分别用Specification实现:
1. 精确查询
2. 模糊查询
3. 多条件查询
4. 排序查询
5. 分页查询
代码如下
package cn.kt.test;/* *Created by tao on 2020-05-05. */ import cn.kt.dao.CustomerDao; import cn.kt.domain.Customer; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.jpa.domain.Specification; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import javax.persistence.criteria.*; import java.util.List; @RunWith(SpringJUnit4ClassRunner.class) //声明单元测试 @ContextConfiguration(locations = "classpath:applicationContext.xml") public class SpecTest { @Autowired private CustomerDao customerDao; /* * 根据条件查询单个对象 * 自定义查询条件 1.实现Specification接口(提供泛型:查询的对象类型) 2.实现toPredicate方法(构造查询条件) 3.需要借助方法参数中的两个参数( root:获取需要查询的对象属性 CriteriaBuilder:构造查询条件的,内部封装了很多的查询条件(模糊匹配,精准匹配) 查询条件: 1. 查询方式: cb对象 2. 比较属性的名称 oot对象 ) * */ @Test public void testSepc1() { Specification<Customer> sprec = new Specification<Customer>() { public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) { //1. 获取比较的属性 Path<Object> custName = root.get("custName"); //2. 构造查询的条件 第一个参数,path (属性),第二个参数,属性的取值 Predicate equal = criteriaBuilder.equal(custName, "左眼会陪右眼哭"); return equal; } }; Customer customer = customerDao.findOne(sprec); System.out.println(customer); } /* * 多条件查询 * root:获取属性 客户名 所属行业 cb:构造查询 1.构造客户名的精准匹配查询 2.构造所属行业的精准匹配查询 3.将以上两个查询联系起来 * */ @Test public void testSepc2() { Specification<Customer> sprec = new Specification<Customer>() { public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) { //1. 获取比较的属性 Path<Object> custName = root.get("custName");//客户名 Path<Object> custAddress = root.get("custAddress");//地址 //2. 构造查询的条件 第一个参数,path (属性),第二个参数,属性的取值 Predicate p1 = criteriaBuilder.equal(custName, "左眼会陪右眼哭"); Predicate p2 = criteriaBuilder.equal(custAddress, "江西赣州"); //3. 将多个条件组合到一起(满足条件一并且满足条件二,满足条件一或者满足条件二) Predicate and = criteriaBuilder.and(p1, p2);//以与的形式拼接多个条件 //Predicate or = criteriaBuilder.or();//以或的形式拼接多个条件 return and; } }; Customer customer = customerDao.findOne(sprec); System.out.println(customer); } /*完成根据客户名称的模糊匹配,返回客户列表 equal :直接的到path对象(属性),然后进行比较即可 gt, lt,ge,le,like 得到path对象, 根据path指定比较的参数类型,再去进行比较 指定参数类型: path.as (类型的字节码对象) */ @Test public void testSepc3() { Specification<Customer> sprec = new Specification<Customer>() { public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) { //1. 获取比较的属性 Path<Object> custName = root.get("custName");//客户名 //2. 构造查询的条件:模糊查询 Predicate like = criteriaBuilder.like(custName.as(String.class), "%吉%"); return like; } }; List<Customer> all = customerDao.findAll(sprec); for (Customer customer : all) { System.out.println(customer); } //添加排序 //创建排序对象,需要调用构造方法实例化sort对象 //第一个参数:排序的顺序(倒序,正序) // Sort .Direction. DESC:倒序 // Sort . Direction.ASC :升序 //第二个参数:排序的属性名称 System.out.println("****************排序实现的***************"); Sort sort = new Sort(Sort.Direction.DESC,"custId"); List<Customer> list = customerDao.findAll(sprec, sort); for (Customer customer : list) { System.out.println(customer); } } /*分页查询 Specification:查询条件 Pageable:分页参数 分页参数:查询的页码,每页查询的条数 findAll(Specification, Pageable):带有条件的分页 findA1l(Pageable):没有条件的分页 返回:Page (sptingDataJpa为我们封装好的pageBean对象,数据列表,共条数) */ @Test public void testPage() { //构造查询条件 Specification<Customer> spec = new Specification<Customer>() { public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) { return cb.like(root.get("custName").as(String.class), "%吉%"); } }; /** * 构造分页参数 * Pageable : 接口 * PageRequest实现了Pageable接口,调用构造方法的形式构造 * 第一个参数:页码(从0开始) * 第二个参数:每页查询条数 */ Pageable pageable = new PageRequest(0, 5); /** * 分页查询,封装为Spring Data Jpa 内部的page bean * 此重载的findAll方法为分页方法需要两个参数 * 第一个参数:查询条件Specification * 第二个参数:分页参数 */ Page<Customer> page = customerDao.findAll(spec,pageable); System.out.println(pageable); System.out.println(page.getContent());//得到数据集合列表 System.out.println(page.getTotalElements());//得到总条数 System.out.println(page.getTotalPages());//得到总页数 } }
多表之间的关系和操作
多表之间的关系和操作多表的操作步骤
- 表关系
一对一
一对多:
一的一方:主表
多的一方:从表
外键:需要再从表上新建一列作为外键,他的取值来源于主表的主键
多对多:
中间表:中间表中最少应该由两个字段组成,这两个字段做为外键指向两张表的主键,又组成了联合主键 -
讲师对学员:一对多关系
-
实体类中的关系
包含关系:可以通过实体类中的包含关系描述表关系
继承关系 -
分析步骤
1.明确表关系
2.确定表关系(描述 外键|中间表)
3.编写实体类,再实体类中描述表关系(包含关系)
4.配置映射关系
完成多表操作
映射的注解说明
-
@OneToMany:
作用:建立一对多的关系映射
属性:
targetEntityClass:指定多的多方的类的字节码
mappedBy:指定从表实体类中引用主表对象的名称。
cascade:指定要使用的级联操作
fetch:指定是否采用延迟加载
orphanRemoval:是否使用孤儿删除 -
@ManyToOne
作用:建立多对一的关系
属性:
targetEntityClass:指定一的一方实体类字节码
cascade:指定要使用的级联操作
fetch:指定是否采用延迟加载
optional:关联是否可选。如果设置为false,则必须始终存在非空关系。 -
@JoinColumn
作用:用于定义主键字段和外键字段的对应关系。
属性:
name:指定外键字段的名称
referencedColumnName:指定引用主表的主键字段名称
unique:是否唯一。默认值不唯一
nullable:是否允许为空。默认值允许。
insertable:是否允许插入。默认值允许。
updatable:是否允许更新。默认值允许。
columnDefinition:列的定义信息。
i.一对多操作
案例:客户和联系人的案例(一对多关系) 客户:一家公司 联系人:这家公司的员工 一个客户可以具有多个联系人 一个联系人从属于一家公司 分析步骤 1.明确表关系 一对多关系 2.确定表关系(描述 外键|中间表) 主表:客户表 从表:联系人表 * 再从表上添加外键 3.编写实体类,再实体类中描述表关系(包含关系) 客户:再客户的实体类中包含一个联系人的集合 联系人:在联系人的实体类中包含一个客户的对象 4.配置映射关系 * 使用jpa注解配置一对多映射关系 级联: 操作一个对象的同时操作他的关联对象 级联操作: 1.需要区分操作主体 2.需要在操作主体的实体类上,添加级联属性(需要添加到多表映射关系的注解上) 3.cascade(配置级联) 级联添加, 案例:当我保存一个客户的同时保存联系人 级联删除 案例:当我删除一个客户的同时删除此客户的所有联系人
保存操作
@Test @Transactional //开启事务 @Rollback(false)//设置为不回滚 public void testAdd() { Customer c = new Customer(); c.setCustName("TBD云集中心"); c.setCustLevel("VIP客户"); c.setCustSource("网络"); c.setCustIndustry("商业办公"); c.setCustAddress("昌平区北七家镇"); c.setCustPhone("010-84389340"); LinkMan l = new LinkMan(); l.setLkmName("TBD联系人"); l.setLkmGender("male"); l.setLkmMobile("13811111111"); l.setLkmPhone("010-34785348"); l.setLkmEmail("98354834@qq.com"); l.setLkmPosition("老师"); l.setLkmMemo("还行吧"); c.getLinkmans().add(l); l.setCustomer(c); customerDao.save(c); linkManDao.save(l); }
通过保存的案例,我们可以发现在设置了双向关系之后,会发送两条insert语句,一条多余的update语句,那我们的解决是思路很简单,就是一的一方放弃维护权
/** *放弃外键维护权的配置将如下配置改为 */ //@OneToMany(targetEntity=LinkMan.class) //@JoinColumn(name="lkm_cust_id",referencedColumnName="cust_id") //设置为 @OneToMany(mappedBy="customer")
级联
首先要配置级联属性
在配置一对多关系是添加一下注解
@OneToMany(mappedBy = "customer",cascade = CascadeType.ALL) private Set<LinkMan> linkmans = new HashSet<LinkMan>(0);
级联添加,
案例:当我保存一个客户的同时保存联系人
/* * 级联添加:保存一个客户的同时,保存客户的所有联系人 需要在操作主体的实体类上, 配置casacde属性 */ @Test @Transactional //开启事务 @Rollback(false)//设置为不回滚 public void testCascadeAdd() { Customer c = new Customer(); c.setCustName("TBD云集中心"); c.setCustLevel("VIP客户"); c.setCustSource("网络"); c.setCustIndustry("商业办公"); c.setCustAddress("昌平区北七家镇"); c.setCustPhone("010-84389340"); LinkMan l = new LinkMan(); l.setLkmName("TBD联系人"); l.setLkmGender("male"); l.setLkmMobile("13811111111"); l.setLkmPhone("010-34785348"); l.setLkmEmail("98354834@qq.com"); l.setLkmPosition("老师"); l.setLkmMemo("还行吧"); c.getLinkmans().add(l); l.setCustomer(c); customerDao.save(c); }
级联删除
案例:当我删除一个客户的同时删除此客户的所有联系人
/* * 级联删除: * 删除一号客户的同时,删除一号客户的所有联系人 */ @Test @Transactional //开启事务 @Rollback(false)//设置为不回滚 public void testCascadeRemove() { //1. 查询1号客户 Customer one = customerDao.findOne(2l); //2. 删除1号客户 customerDao.delete(one); }
ii.多对多操作
案例:用户和角色(多对多关系) 用户: 角色: 分析步骤 1.明确表关系 多对多关系 2.确定表关系(描述 外键|中间表) 中间间表 3.编写实体类,再实体类中描述表关系(包含关系) 用户:包含角色的集合 角色:包含用户的集合 4.配置映射关系
多对多操作案例
- 多对多保存操作(放弃维护权)
- 级联添加操作
- 级联删除操作
映射的注解说明
* @ManyToMany
作用:用于映射多对多关系
属性:
cascade:配置级联操作。
fetch:配置是否采用延迟加载。
targetEntity:配置目标的实体类。映射多对多的时候不用写。
- @JoinTable
作用:针对中间表的配置
属性:
nam:配置中间表的名称
joinColumns:中间表的外键字段关联当前实体类所对应表的主键字段
inverseJoinColumn:中间表的外键字段关联对方表的主键字段 -
@JoinColumn
作用:用于定义主键字段和外键字段的对应关系。
属性:
name:指定外键字段的名称
referencedColumnName:指定引用主表的主键字段名称
unique:是否唯一。默认值不唯一
代码如下
1.SysUser.java(用户的数据模型)
package cn.kt.domain;/* *Created by tao on 2020-05-06. */ import javax.persistence.*; import java.io.Serializable; import java.util.HashSet; import java.util.Set; /** * 用户的数据模型 */ @Entity @Table(name = "sys_user") public class SysUser implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "user_id") private Long userId; @Column(name = "user_code") private String userCode; @Column(name = "user_name") private String userName; @Column(name = "user_password") private String userPassword; @Column(name = "user_state") private String userState; /* * 配置用户到角色的多对多关系 配置多对多的映射关系 1.声明表关系的配置 @ManyToMany(mappedBy = SysRole.class) //多对多 targetEntity 对方实体类字节码 2.配置中间表(包含两个外键) JoinTable name:中间表的名称 joinColumns配置当前对象在中间表中的外键 inverseJoinColumns配置对方对象在中间表中的外键 * */ @ManyToMany(targetEntity = SysRole.class,cascade = CascadeType.ALL) @JoinTable(name = "sys_user_role", //joinColumns配置当前对象在中间表中的外键 joinColumns = {@JoinColumn(name = "sys_user_id",referencedColumnName = "user_id")}, //inverseJoinColumns配置对方对象在中间表中的外键 inverseJoinColumns = {@JoinColumn(name = "sys_role_id",referencedColumnName = "role_id")} ) private Set<SysRole> roles = new HashSet<SysRole>(0); public Long getUserId() { return userId; } public void setUserId(Long userId) { this.userId = userId; } public String getUserCode() { return userCode; } public void setUserCode(String userCode) { this.userCode = userCode; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getUserPassword() { return userPassword; } public void setUserPassword(String userPassword) { this.userPassword = userPassword; } public String getUserState() { return userState; } public void setUserState(String userState) { this.userState = userState; } public Set<SysRole> getRoles() { return roles; } public void setRoles(Set<SysRole> roles) { this.roles = roles; } @Override public String toString() { return "SysUser [userId=" + userId + ", userCode=" + userCode + ", userName=" + userName + ", userPassword=" + userPassword + ", userState=" + userState + "]"; } }
- SysRole.java(角色的数据模型)
package cn.kt.domain;/* *Created by tao on 2020-05-06. */ import javax.persistence.*; import java.io.Serializable; import java.util.HashSet; import java.util.Set; /** * 角色的数据模型 */ @Entity @Table(name="sys_role") public class SysRole implements Serializable { @Id @GeneratedValue(strategy= GenerationType.IDENTITY) @Column(name="role_id") private Long roleId; @Column(name="role_name") private String roleName; @Column(name="role_memo") private String roleMemo; //多对多关系映射 @ManyToMany(mappedBy = "roles") private Set<SysUser> users = new HashSet<SysUser>(0); public Long getRoleId() { return roleId; } public void setRoleId(Long roleId) { this.roleId = roleId; } public String getRoleName() { return roleName; } public void setRoleName(String roleName) { this.roleName = roleName; } public String getRoleMemo() { return roleMemo; } public void setRoleMemo(String roleMemo) { this.roleMemo = roleMemo; } public Set<SysUser> getUsers() { return users; } public void setUsers(Set<SysUser> users) { this.users = users; } @Override public String toString() { return "SysRoleDao [roleId=" + roleId + ", roleName=" + roleName + ", roleMemo=" + roleMemo + "]"; } }
- ManyToManyTest.java(测试)
package cn.kt.test;/* *Created by tao on 2020-05-06. */ import cn.kt.dao.SysRoleDao; import cn.kt.dao.SysUserDao; import cn.kt.domain.SysRole; import cn.kt.domain.SysUser; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.annotation.Rollback; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.transaction.annotation.Transactional; @RunWith(SpringJUnit4ClassRunner.class) //声明单元测试 @ContextConfiguration(locations = "classpath:applicationContext.xml") public class ManyToManyTest { @Autowired private SysUserDao userDao; @Autowired private SysRoleDao roleDao; /** * 需求: * 保存用户和角色 * 要求: * 创建2个用户和3个角色 * 让1号用户具有1号和2号角色(双向的) * 让2号用户具有2号和3号角色(双向的) * 保存用户和角色 * 问题: * 在保存时,会出现主键重复的错误,因为都是要往中间表中保存数据造成的。 * 解决办法: * 让任意一方放弃维护关联关系的权利 * 一般让被动的一方放弃维护权 */ @Test @Transactional //开启事务 @Rollback(false)//设置为不回滚 public void testAdd() { //创建对象 SysUser u1 = new SysUser(); u1.setUserName("用户1"); SysRole r1 = new SysRole(); r1.setRoleName("角色1"); //建立关联关系 //配置用户到角色关系,可以对中间表中的数据进行维护 1- 1 u1.getRoles().add(r1); //配置角色到用户关系,可以对中间表中的数据进行维护 1- 1 r1.getUsers().add(u1); //保存 roleDao.save(r1); userDao.save(u1); } /*测试级联添加:保存一个用户的同时,保存用户的关联角色 * * */ @Test @Transactional //开启事务 @Rollback(false)//设置为不回滚 public void testCasCadeAdd() { //创建对象 SysUser u1 = new SysUser(); u1.setUserName("用户2"); SysRole r1 = new SysRole(); r1.setRoleName("角色3"); //建立关联关系 //配置用户到角色关系,可以对中间表中的数据进行维护 1- 1 u1.getRoles().add(r1); //配置角色到用户关系,可以对中间表中的数据进行维护 1- 1 r1.getUsers().add(u1); //保存 userDao.save(u1); } /*级联删除:删除id为2的用户同时删除他的关联对象*/ @Test @Transactional //开启事务 @Rollback(false)//设置为不回滚 public void testCasCadeRemove() { //1. 查询2号用户 SysUser user = userDao.findOne(2l); //2. 删除用户 userDao.delete(user); } }
iii.多表的查询
1.对象导航查询 查询一个对象的同时,通过此对象查询他的关联对象 案例:客户和联系人 从一方查询多方 * 默认:使用延迟加载(****) 从多方查询一方 * 默认:使用立即加载
对象导航查询测试
ObjectQueryTest.java
package cn.kt.test;/* *Created by tao on 2020-05-06. */ import cn.kt.dao.CustomerDao; import cn.kt.dao.LinkManDao; import cn.kt.domain.Customer; import cn.kt.domain.LinkMan; import org.hibernate.event.spi.SaveOrUpdateEvent; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.transaction.annotation.Transactional; import java.util.Set; @RunWith(SpringJUnit4ClassRunner.class) //声明单元测试 @ContextConfiguration(locations = "classpath:applicationContext.xml") public class ObjectQueryTest { @Autowired private CustomerDao customerDao; @Autowired private LinkManDao linkManDao; /*测试对象导航查询:查询一个对象的时候,通过此对象查询所有的关联对象 * 默认使用的是延迟加载的形式查询的 调用get方法并不会立即发送查询,而是在使用关联对象的时候才会差和讯 延迟加载! 修改配置,将延迟加载改为立即加载 fetch,需要配置到多表映射关系的注解上 * */ @Test @Transactional //解决java代码当中的 np session的问题 public void testQuery1() { //查询id为1的对象 Customer customer = customerDao.findOne(1l); //对象导航查询,此客户下的所有联系人 Set<LinkMan> linkmans = customer.getLinkmans(); for (LinkMan linkman : linkmans) { System.out.println(linkman); } } /* * 从联系人导航查询所属客户 * 默认:立即加载 * */ @Test @Transactional //解决java代码当中的 np session的问题 public void testQuery2() { //查询id为1的对象 LinkMan linkMan = linkManDao.findOne((long) 1); //对象导航查询所属客户 Customer customer = linkMan.getCustomer(); System.out.println(customer); } }
总结:SpringDataJpa的使用方法
- 根据主键单表的CRUD
- 在接口使用@Query注解配置Jpql的灵活CRUD
- 在接口使用@Query注解配置Sql,nativeQuery = true的灵活CRUD
- 使用方法名的约定的方法进行查询
findBy +属性名+ "查询方式"+ "多条件的连接符(and|or)" +属性名+"查询方式" - 给定条件不固定的时候,使用Specifications动态查询
- 一对多操作,在实体类里面配置一对多的关系映射
- 多对多操作,在实体类里面配置多对多的关系映射
- 对象导航查询测试
主体对象查询所有的关联对象
关联对象查询所属的主体对象 - 多表查询的级联操作