目录
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实现:
- 精确查询
- 模糊查询
- 多条件查询
- 排序查询
- 分页查询
代码如下
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动态查询
- 一对多操作,在实体类里面配置一对多的关系映射
- 多对多操作,在实体类里面配置多对多的关系映射
- 对象导航查询测试
主体对象查询所有的关联对象
关联对象查询所属的主体对象 - 多表查询的级联操作