当前位置:网站首页>Specifications、多表查询基础
Specifications、多表查询基础
2022-07-17 00:28:00 【你好y】
一、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);//统计查询
对于JpaSpecificationExecutor,这个接口基本是围绕着Specification接口来定义的。我们可以简单的理解为,Specification构造的就是查询条件。
Specification :查询条件
自定义我们自己的Specification实现类
实现
//root:查询的根对象(查询的任何属性都可以从根对象中获取)
//CriteriaQuery:顶层查询对象,自定义查询方式(了解:一般不用)
//CriteriaBuilder:查询的构造器,封装了很多的查询条件
Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb); //封装查询条件
动态查询单个对象
案例实现
@Test
/** * 自定义查询条件 * 1、实现Specification接口(提供泛型,查询的对象类型) * 2、实现toPredicate方法(构造查询条件) * 3、需要借助方法参数中的两个参数( * root:获取需要查询的对象属性 * CriteriaBuilder:构造查询条件,内部封装了很多查询条件(模糊匹配,精确匹配) * ) * 案例:根据客户名称,查询客户名为互联网的客户 * 查询条件 * 1、查询方式 criteriaBuilder对象 * 2、比较的属性名称 root对象 * */
public void testSpecifications(){
Specification<Customer> spec=new Specification<Customer>() {
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
//1.获得比较的属性
Path<Object> custName = root.get("custName");
//2,构造条件:select * from cst_customer where cust_name="xxx"
/** * 第一个参数:需要比较的属性(path对象) * 第二个参数:当前需要比较的取值 */
Predicate predicate = criteriaBuilder.equal(custName, "互联网");//进行精确的匹配(比较的属性,比较的属性的取值)
return predicate;
}
};
Customer customer = customerDao.findOne(spec);
System.out.println(customer);
}
动态查询完成多条件拼接
案例
/** * 动态多条件拼接查询 * 案例:查询客户名为黄金矿工,行业为教育的客户 */
@Test
public void testConditions(){
Specification<Customer> spec=new Specification<Customer>() {
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
//获得比较的属性
Path<Object> custName = root.get("custName");
Path<Object> custIndustry = root.get("custIndustry");
//构造条件
Predicate p1 = criteriaBuilder.equal(custName, "黄金矿工");
Predicate p2 = criteriaBuilder.equal(custIndustry, "教育");
//条件拼接
Predicate predicate = criteriaBuilder.and(p1, p2);
return predicate;
}
};
Customer customer = customerDao.findOne(spec);
System.out.println(customer);
}
模糊匹配查询列表
案例:
/** * 模糊查询匹配 * 案例:查询客户名为黄xx的客户 */
@Test
public void testLike(){
Specification<Customer> spec=new Specification<Customer>() {
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
//获得比较的属性
Path<Object> custName = root.get("custName");
//构造条件
Predicate predicate = criteriaBuilder.like(custName.as(String.class), "黄%");
return predicate;
}
};
List<Customer> list = customerDao.findAll(spec);
for (Customer customer : list) {
System.out.println(customer);
}
}
因为客户名称是字符串,所以path.as(String.class)
️注意:
1、equals:直接得到path对象属性,然后进行比较即可。
2、gt、lt、ge、le、like:得到path对象,根据path指定比较的参数类型,再去进行比较
指定参数类型,path.as(类型的字节码对象)
排序
创建排序对象,需要调用构造方法示例化sort对象。第一个参数指定排序的顺序(正倒序)、第二个参数排序的属性名称
示例:
/** * 模糊查询匹配 * 案例:查询客户名为黄xx的客户 */
@Test
public void testLike(){
Specification<Customer> spec=new Specification<Customer>() {
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
//获得比较的属性
Path<Object> custName = root.get("custName");
//构造条件
Predicate predicate = criteriaBuilder.like(custName.as(String.class), "黄%");
return predicate;
}
};
Sort sort=new Sort(Sort.Direction.DESC,"custId");
List<Customer> list = customerDao.findAll(spec,sort);
for (Customer customer : list) {
System.out.println(customer);
}
}
分页查询
案例
每页有两个信息,查找第0页
//分页查询
Pageable pageable=new PageRequest(0,2);
Page<Customer> customers = customerDao.findAll(spec, pageable);
System.out.println(customers.getContent());//结果集合
System.out.println(customers.getTotalElements());//总条数
System.out.println(customers.getTotalPages());//总页数
for (Customer customer : customers) {
System.out.println(customer);
}
总结
1、根据属性名称获取比较的属性
2、构造查询条件
二、多表之间的关系和操作多表的操作步骤
i.一对多操作
案例:客户和联系人的案例(一对多关系)
客户:一家公司
联系人:这家公司的员工
一个客户可以具有多个联系人
一个联系人从属于一家公司
分析步骤
1.明确表关系
一对多关系
2.确定表关系(描述 外键|中间表)
主表:客户表
从表:联系人表
* 再从表上添加外键
3.编写实体类,再实体类中描述表关系(包含关系)
客户:再客户的实体类中包含一个联系人的集合
联系人:在联系人的实体类中包含一个客户的对象
4.配置映射关系
* 使用jpa注解配置一对多映射关系
联系人实体类
package cn.itcast.domain;
import lombok.Data;
import javax.persistence.*;
@Entity
@Table(name = "cst_linkman")
@Data
public class LinkMan {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "lkm_id")
private Long lkmId; //联系人编号(主键)
@Column(name = "lkm_name")
private String lkmName;//联系人姓名
@Column(name = "lkm_gender")
private String lkmGender;//联系人性别
@Column(name = "lkm_phone")
private String lkmPhone;//联系人办公电话
@Column(name = "lkm_mobile")
private String lkmMobile;//联系人手机
@Column(name = "lkm_email")
private String lkmEmail;//联系人邮箱
@Column(name = "lkm_position")
private String lkmPosition;//联系人职位
@Column(name = "lkm_memo")
private String lkmMemo;//联系人备注
/** * 配置联系人到客户的多对一关系 * 使用注解的形式配置多对一关系 * 1.配置表关系 * @ManyToOne : 配置多对一关系 * targetEntity:对方的实体类字节码 * 2.配置外键(中间表) * name:外键的名字 * referencedColumnName:参考的主键的名称 * * * 配置外键的过程,配置到了多的一方,就会在多的一方维护外键 * */
@ManyToOne(targetEntity = Customer.class,fetch = FetchType.LAZY)
@JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
private Customer customer;
}
客户实体类
package cn.itcast.domain;
import lombok.Data;
import javax.persistence.*;
/** * 实体类和表的映射关系 * @Entity * @Table */
@Entity
@Table(name = "cst_customer")
@Data//提供类的get,set,equals,toString,hashCode方法
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "cust_id")
private Long custId;
@Column(name ="cust_address")
private String custAddress;
@Column(name ="cust_industry")
private String custIndustry;
@Column(name ="cust_level")
private String custLevel;
@Column(name ="cust_name")
private String custName;
@Column(name ="cust_phone")
private String custPhone;
@Column(name ="cust_source")
private String custSource;
//配置外键
@OneToMany(targetEntity = Customer.class )
@JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
private LinkMan linkMan;
}
配置文件的配置
<!--注入jpa的配置信息 加载jpa的基本配置信息和jpa的实现方式(hibernate)的配置信息 hibernate.hbm2ddl.auto:自动创建数据库表 create:每次都会重新创建数据库表 update:有表不会重新创建,没有表会重新创建 -->
<property name="jpaProperties">
<props>
<prop key="hibernate.hbm2ddl.auto">create</prop>
</props>
</property>
在springDataJPA中,添加事务会默认回滚。所以在进行测试添加事务注解的之后,还要添加@Rollback注解,设置value=false
三、完成多表查询
一对多操作
一对多的关系
实体之间的设置
package cn.itcast.domain;
import lombok.Data;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
/** * 实体类和表的映射关系 * @Entity * @Table */
@Entity
@Table(name = "cst_customer")
@Data//提供类的get,set,equals,toString,hashCode方法
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "cust_id")
private Long custId;
@Column(name ="cust_address")
private String custAddress;
@Column(name ="cust_industry")
private String custIndustry;
@Column(name ="cust_level")
private String custLevel;
@Column(name ="cust_name")
private String custName;
@Column(name ="cust_phone")
private String custPhone;
@Column(name ="cust_source")
private String custSource;
//配置客户和联系人之间的关系(一对多关系)
/** * 使用注解的形式配置多表关系 * 1.声明关系 * @OneToMany : 配置一对多关系 * targetEntity :对方对象的字节码对象 * 2.配置外键(中间表) * @JoinColumn : 配置外键 * name:外键字段名称 * referencedColumnName:参照的主表的主键字段名称 * * * 在客户实体类上(一的一方)添加了外键了配置,所以对于客户而言,也具备了维护外键的作用 * */
// @OneToMany(targetEntity = LinkMan.class)
// @JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
/** * 放弃外键维护权 * mappedBy:对方配置关系的属性名称\ * cascade : 配置级联(可以配置到设置多表的映射关系的注解上) * CascadeType.all : 所有 * MERGE :更新 * PERSIST :保存 * REMOVE :删除 * * fetch : 配置关联对象的加载方式 * EAGER :立即加载 * LAZY :延迟加载 */
@OneToMany(mappedBy = "customer",cascade = CascadeType.ALL)
private Set<LinkMan> linkMans = new HashSet<LinkMan>();
}
package cn.itcast.domain;
import lombok.Data;
import javax.persistence.*;
@Entity
@Table(name = "cst_linkman")
@Data
public class LinkMan {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "lkm_id")
private Long lkmId; //联系人编号(主键)
@Column(name = "lkm_name")
private String lkmName;//联系人姓名
@Column(name = "lkm_gender")
private String lkmGender;//联系人性别
@Column(name = "lkm_phone")
private String lkmPhone;//联系人办公电话
@Column(name = "lkm_mobile")
private String lkmMobile;//联系人手机
@Column(name = "lkm_email")
private String lkmEmail;//联系人邮箱
@Column(name = "lkm_position")
private String lkmPosition;//联系人职位
@Column(name = "lkm_memo")
private String lkmMemo;//联系人备注
/** * 配置联系人到客户的多对一关系 * 使用注解的形式配置多对一关系 * 1.配置表关系 * @ManyToOne : 配置多对一关系 * targetEntity:对方的实体类字节码 * 2.配置外键(中间表) * name:外键的名字 * referencedColumnName:参考的主键的名称 * * * 配置外键的过程,配置到了多的一方,就会在多的一方维护外键 * */
@ManyToOne(targetEntity = Customer.class,fetch = FetchType.LAZY)
@JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
private Customer customer;
}
删除从表数据:可以随时任意删除。
删除主表数据:
- 有从表数据
1、在默认情况下,它会把外键字段置为null,然后删除主表数据。如果在数据库的表结构上,外键字段有非空约束,默认情况就会报错了。
2、如果配置了放弃维护关联关系的权利,则不能删除(与外键字段是否允许为null,没有关系)因为在删除时,它根本不会去更新从表的外键字段了。
3、如果还想删除,使用级联删除引用
- 没有从表数据引用:随便删
级联:
操作一个对象的同时操作他的关联对象
级联操作:
1.需要区分操作主体
2.需要在操作主体的实体类上,添加级联属性(需要添加到多表映射关系的注解上)
3.cascade(配置级联)
级联添加,
案例:当我保存一个客户的同时保存联系人
级联删除
案例:当我删除一个客户的同时删除此客户的所有联系人
一定要区分出操作主体,只操作该主题即可,从的就会做出对应的改变
cascade : 配置级联(可以配置到设置多表的映射关系的注解上)
CascadeType.all : 所有
MERGE :更新
PERSIST :保存
REMOVE :删除
fetch : 配置关联对象的加载方式
EAGER :立即加载
LAZY :延迟加载
案例
//级联添加
@Test
@Transactional
@Rollback(false)
public void testAdd(){
Customer customer=new Customer();
customer.setCustName("阿斯顿2");
LinkMan linkMan=new LinkMan();
linkMan.setLkmName("赵敏1");
linkMan.setCustomer(customer);
customer.setLinkMans(Arrays.asList(linkMan));
customerDao.save(customer);
}
//级联删除
@Test
@Transactional
@Rollback(false)
public void testDelete(){
customerDao.delete(14l);
}
多对多操作
案例:用户和角色(多对多关系)
用户:
角色:
分析步骤
1.明确表关系
多对多关系
2.确定表关系(描述 外键|中间表)
中间间表
3.编写实体类,再实体类中描述表关系(包含关系)
用户:包含角色的集合
角色:包含用户的集合
4.配置映射关系
建立三张表
建立实体类
User
package cn.itcast.domain;
import lombok.Data;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
@Entity
@Table(name = "sys_user")
@Data
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="user_id")
private Long userId;
@Column(name="user_name")
private String userName;
@Column(name="age")
private Integer age;
/** * 配置用户到角色的多对多关系 * 配置多对多的映射关系 * 1.声明表关系的配置 * @ManyToMany(targetEntity = Role.class) //多对多 * targetEntity:代表对方的实体类字节码 * 2.配置中间表(包含两个外键) * @JoinTable * name : 中间表的名称 * joinColumns:配置当前对象在中间表的外键 * @JoinColumn的数组 * name:外键名 * referencedColumnName:参照的主表的主键名 * inverseJoinColumns:配置对方对象在中间表的外键 */
@ManyToMany(targetEntity = Role.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<Role> roles = new HashSet<Role>();
}
Role
package cn.itcast.domain;
import lombok.Data;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
@Entity
@Table(name = "sys_role")
@Data
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "role_id")
private Long roleId;
@Column(name = "role_name")
private String roleName;
//配置多对多
@ManyToMany(mappedBy = "roles") //配置多表关系
private Set<User> users = new HashSet<User>();
}
创建对应的DAO
RoleDao和UserDao
public interface RoleDao extends JpaRepository<Role,Long >,JpaSpecificationExecutor<Role> {
}
将配置文件的update改为create,每次都去创建,测试中间表中是否有数据
案例
@Test
public void testAdd(){
User user=new User();
user.setUserName("小李");
Role role=new Role();
role.setRoleName("老师");
//配置用户到角色关系
user.getRoles().add(role);
userDao.save(user);
}
Hibernate: insert into sys_user (age, user_name) values (?, ?)
Hibernate: insert into sys_role (role_name) values (?)
Hibernate: insert into sys_user_role (sys_user_id, sys_role_id) values (?, ?)
级联添加
@Test
public void testAdd(){
User user=new User();
user.setUserName("小李");
Role role=new Role();
role.setRoleName("老师");
//配置用户到角色关系
user.getRoles().add(role);
userDao.save(user);
}
测试级联删除的时候,需要将配置create改为update
//级联删除
@Test
@Transactional
@Rollback(value = false)
public void testCasCadeDelete(){
User user=userDao.findOne(1l);
//System.out.println(user);
userDao.delete(user);
}
每个注解的意思
多表的查询
1.对象导航查询
查询一个对象的同时,通过此对象查询他的关联对象
案例:客户和联系人
从一方查询多方
* 默认:使用延迟加载(****)
从多方查询一方
* 默认:使用立即加载
使用一对多案例
构建数据
查询一个客户,获取该客户下的所有联系人
@Autowired
private CustomerDao customerDao;
@Test
//由于是在java代码中测试,为了解决no session问题,将操作配置到同一个事务中
@Transactional
public void testFind() {
Customer customer = customerDao.findOne(5l);
Set<LinkMan> linkMans = customer.getLinkMans();//对象导航查询
for(LinkMan linkMan : linkMans) {
System.out.println(linkMan);
}
}
查询一个联系人,获取该联系人的所有客户
@Autowired
private LinkManDao linkManDao;
@Test
public void testFind() {
LinkMan linkMan = linkManDao.findOne(4l);
Customer customer = linkMan.getCustomer(); //对象导航查询
System.out.println(customer);
}
问题1:我们查询客户时,要不要把联系人查询出来?
分析:如果我们不查的话,在用的时候还要自己写代码,调用方法去查询。如果我们查出来的,不使用时又会白白的浪费了服务器内存。
解决:采用延迟加载的思想。通过配置的方式来设定当我们在需要使用时,发起真正的查询。
配置方式:
/** * 在客户对象的@OneToMany注解中添加fetch属性 * FetchType.EAGER :立即加载 * FetchType.LAZY :延迟加载 */
@OneToMany(mappedBy="customer",fetch=FetchType.EAGER)
private Set<LinkMan> linkMans = new HashSet<>(0);
问题2:我们查询联系人时,要不要把客户查询出来?
分析:例如:查询联系人详情时,肯定会看看该联系人的所属客户。如果我们不查的话,在用的时候还要自己写代码,调用方法去查询。如果我们查出来的话,一个对象不会消耗太多的内存。而且多数情况下我们都是要使用的。
解决: 采用立即加载的思想。通过配置的方式来设定,只要查询从表实体,就把主表实体对象同时查出来
配置方式
/** * 在联系人对象的@ManyToOne注解中添加fetch属性 * FetchType.EAGER :立即加载 * FetchType.LAZY :延迟加载 */
@ManyToOne(targetEntity=Customer.class,fetch=FetchType.EAGER)
@JoinColumn(name="cst_lkm_id",referencedColumnName="cust_id")
private Customer customer;
@OneToMany(mappedBy = "customer",cascade = CascadeType.ALL)
/** 放弃外键维权, mappedBy:对方配置关系的属性名称 cascade:配置级联(可以配置到设置多表的映射关系的注释上) CascadeType.ALL :所有 PERSIST,保存 MERGE,更新 REMOVE,删除 fetch:配置关联对象的加载方式 EAGER:立即加载 LAZY:延迟加载 */
立即加载:当我们查询客户的时候,他下面所有的联系人就都查出来了,不管我们用不用,所以不建议使用立即加载。
从多的一方查询一方,默认是立即加载,要想改为延迟加载,需要看操作主体,在操作主体上联系人的实体类里面对客户 进行相关配置。
即从联系人查询所属的客户
/** * 配置联系人到客户的多对一关系 * 使用注解的形式配置多对一关系 * 1.配置表关系 * @ManyToOne : 配置多对一关系 * targetEntity:对方的实体类字节码 * 2.配置外键(中间表) * name:外键的名字 * referencedColumnName:主键的名字 * * * 配置外键的过程,配置到了多的一方,就会在多的一方维护外键 * */
@ManyToOne(targetEntity = Customer.class,fetch = FetchType.LAZY)
@JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
private Customer customer;
//测试对象导航查询(根据联系人查找客户)
@Test
@Transactional//解决could not initialize proxy - no Session
public void testQuery2(){
LinkMan linkMan = linkManDao.findOne(1l);
Customer customer = linkMan.getCustomer();
System.out.println(customer);
}
什么时候使用什么时候发送sql语句。一开始的时候只是查用户,还没用到,等打印用户的时候,才会发送select的sql语句,这就是延迟加载
总结
1.对象导航查询
查询一个对象的同时,通过此对象查询他的关联对象
案例:客户和联系人
从一方查询多方
- 默认:使用延迟加载
从多方查询一方
- 默认:使用立即加载
边栏推荐
猜你喜欢

一文搞懂JVM垃圾收集

RHCE ansible second operation

Que se passe - t - il lorsque vous compilez et installez une base de données MySQL bloquée dans un système Linux?

DDD 超越 MVC了吗

Letv a plus de 400 employés? Le jour de l'immortel sans patron, les autorités ont répondu...

多项式插值拟合(一)

一文搞懂JVM内存结构

MySQL存储引擎详解

Understand network namespaces

RHCE Study Guide Chapter 5 VIM editor
随机推荐
Oracle gets the last and first data (gets the first and last data by time)
Detailed explanation of dynamic compression and static compression of gzip
人脸关键点检测
ncnn paramdict&modelbin
仿射变换实现
【人脸识别】基于直方图Histogram实现人脸识别附matlab代码
Understand the switch and its role
内置键盘连续444
HCIA static comprehensive experiment
多项式插值拟合(三)
wangEditor介绍(入门级)
ncnn Mat矩阵类
Binary installation kubernetes 1.23.2
Oracle获取最后一条,第一条数据(按时间获取第一条和最后一条数据)
[MCU simulation] (I) proteus8.9 installation tutorial
要开源节流
Introduction to wangeditor (entry level)
Summary of the most complete methods of string interception in Oracle
Rhce8 Study Guide Chapter 7 service management
Full virtualization and semi virtualization