Spring Data JPA 提供了-个Specifcation接口,Specification 接口封装了JPA的Criteria查询条件,从而可以通过此接口更加方便地使用Criteria查询。
接下来以示例的方式来详细讲解使用Specification的查询、分页、动态查询等操作。
![项目目录](https://i-blog.csdnimg.cn/blog_migrate/290787591f3cd5be7090f9c7bc69adae.png)
1.创建maven项目,
命名为springdatajpaspecificationtest
修改pom.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>springdatajpaspecificationtest</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springdatajpaspecificationtest</name>
<description>springdatajpaspecificationtest</description>
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.7.6</version>
</parent>
<properties>
<java.version>19</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.6.13</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>19</source>
<target>19</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>com.example.springdatajpaspecificationtest.SpringdatajpaspecificationtestApplication
</mainClass>
<skip>true</skip>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
2.配置基本属性。
修改application.properties文件(没有就按项目目录创建)
########
#数据源信息配置
########
#数据库地址
spring.datasource.url=jdbc:mysql://localhost:3306/springdatajpaspecification?characterEncoding=utf8&useSSL=false
#用户名
spring.datasource.username=root
#密码
spring.datasource.password=root
#数据库驱动
spring.datasource.driverClassName = com.mysql.cj.jdbc.Driver
#指定连接池中最大的活跃连接数
spring.datasource.tomcat.max-active=20
#指定连接池最大的空闲连接数
spring.datasource.max-idle=8
#指定必须保持连接到最小值
spring.datasource.min-idle=8
#指定启动连接池时,初始建立的连接数量
spring.datasource.initial-size=10
##############################################
### JPA持久化配置
##############################################
# 持久化配置,指定数据库类型
spring.jpa.database=MySQL
#指定是否需要在日志中显示SQL语句
spring.jpa.show-sql=true
#指定自动创建|更新|验证数据库表结构等配置,配置成update
#表示如果数据库中存在持久化类对应的表就不创建,不存在就创建对应的表
spring.jpa.hibernate.ddl-auto=update
#指定命名策略
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyComponentPathImpl
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
#指定数据库方言
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
spring.jpa.open-in-view=true
3.创建持久化类
在src/main/java/com/example/springdatajpaspecificationtest下新建四个包,分别是bean、controller、repository、service。
在bean包中创建两个持久化类Student.java和Clazz.java
Student.java
package com.example.springdatajpaspecificationtest.bean;
import javax.persistence.*;
import java.io.Serializable;
@Entity
@Table(name="tb_student")
//查询班级的学生信息
//定义方法名称findStudentByClazzName到query中的查询语句的关系
@NamedQuery(name="Student.findStudentByClazzName",query="select s from Student s where s.clazz.name = ?1")
public class Student implements Serializable{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
private String address;
private int age;
private char sex;
//学生与班级是多对一的关系,这里配置的是双向关联
@ManyToOne(fetch = FetchType.LAZY, targetEntity = Clazz.class)
@JoinColumn(name = "clazzId", referencedColumnName = "code")
private Clazz clazz ;
public Student(){
}
public Student(String name, String address, int age, char sex, Clazz clazz){
super();
this.name=name;
this.address=address;
this.age=age;
this.sex=sex;
this.clazz=clazz;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public char getSex() {
return sex;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public Clazz getClazz() {
return clazz;
}
public void setClazz(Clazz clazz) {
this.clazz = clazz;
}
public void setName(String name) {
this.name = name;
}
}
Clazz.java
package com.example.springdatajpaspecificationtest.bean;
//因为class是java中的保留关键字,不能用于命名变量,所以使用读音相似的clazz来命名,这是约定俗成的。
import javax.persistence.*;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
@Entity
@Table(name = "tb_clazz")
public class Clazz implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int code;
private String name;
//班级与学生是一对多的关联
@OneToMany(
fetch = FetchType.LAZY,
targetEntity = Student.class,
mappedBy = "clazz"
)
private Set<Student> students=new HashSet<>();
public Clazz(){
}
public Clazz(String name){
this.name = name;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public void setName(String name) {
this.name = name;
}
public Set<Student> getStudents() {
return students;
}
public void setStudents(Set<Student> students) {
this.students = students;
}
public String getName() {
return name;
}
}
4.定义数据访问层接口
在src/main/java/com/example/springdatajpaspecificationtest/repository下创建两个接口分别命名为StudentRepository.java和ClazzRepository.java
StudentRepository.java
package com.example.springdatajpaspecificationtest.repository;
import com.example.springdatajpaspecificationtest.bean.Student;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface StudentRepository extends JpaRepository<Student , Integer> , JpaSpecificationExecutor<Student>{
}
ClazzRepository.java
package com.example.springdatajpaspecificationtest.repository;
import com.example.springdatajpaspecificationtest.bean.Clazz;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface ClazzRepository extends JpaRepository<Clazz , Integer> , JpaSpecificationExecutor<Clazz>{
}
5.定义业务层类
在src/main/java/com/example/springdatajpaspecificationtest/service下创建SchoolService.java
package com.example.springdatajpaspecificationtest.service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import com.example.springdatajpaspecificationtest.bean.Clazz;
import com.example.springdatajpaspecificationtest.bean.Student;
import com.example.springdatajpaspecificationtest.repository.ClazzRepository;
import com.example.springdatajpaspecificationtest.repository.StudentRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
@Service
public class SchoolService {
//注入数据访问层接口对象
@Resource
private StudentRepository studentRepository;
@Resource
private ClazzRepository clazzRepository;
@Transactional
public void saveClazzAll(List<Clazz> clazzs){
clazzRepository.saveAll(clazzs);
}
@Transactional
public void saveStudentAll(List<Student> students){
studentRepository.saveAll(students);
}
/*
根据性别查询学生信息
@param clazzName
@return
*/
@SuppressWarnings("serial")//serial连续的
public List<Map<String, Object>> getStusBySex(char sex){
List<Student> students=studentRepository.findAll(new Specification<Student>() {
@Override
public Predicate toPredicate(Root<Student>root,CriteriaQuery<?>query,
CriteriaBuilder cb){
//root.get("sex")表示获取sex这个字段名称,equal表示执行equal查询
//相当与select s from Student s where s.sex = ?1
Predicate p1= cb.equal(root.get("sex"), sex);
return p1;
}
});
List<Map<String, Object>> results = new ArrayList<>();
//遍历查询出的学生对象,提取姓名、年龄、性别信息
for (Student student:students){
Map<String , Object> stu = new HashMap<>();
stu.put("name", student.getName());
stu.put("age", student.getAge());
stu.put("sex", student.getSex());
results.add(stu);
}
return results;
}
/*
动态查询学生信息:可以根据学生对象的姓名(模糊匹配)、地址查询(模糊匹配)、性别、班级
查询学生信息
如果没有传输参数,默认查询所有的学生信息
@param clazzName
@return
*/
@SuppressWarnings("serial")
public List<Map<String, Object>> getStusByDynamic(Student student){
List<Student> students = studentRepository.findAll(new Specification<Student>() {
@Override
public Predicate toPredicate(Root<Student>root, CriteriaQuery<?>query,
CriteriaBuilder cb){
//本集合用于封装查询条件
List<Predicate> predicates = new ArrayList<Predicate>();
if (student!=null){
/**是否传入用于查询的名字*/
if (StringUtils.hasLength(student.getName())){
//% 符号是通配符,表示匹配任意字符,包括空格和其他特殊字符。
// 因此,将 % 添加到查询字符串的开头和结尾,以确保找到所有包含 student.getName() 子字符串的记录。
predicates.add(cb.like(root.<String> get("name"),"%" + student.getName() + "%"));
}
/**是否传入用于查询的地址*/
if (StringUtils.hasLength(student.getAddress())){
predicates.add(cb.like(root.<String> get("address"),"%" + student.getAddress() + "%"));
}
/**是否传入用于查询的性别*/
if (student.getSex() != '\0'){
predicates.add(cb.equal(root.<String> get("sex"), student.getSex() ));
}
/**是否传入用于查询的班级信息*/
if (student.getClazz() !=null && StringUtils.hasLength(student.getClazz().getName())){
root.join("clazz", JoinType.INNER);
Path<String> clazzName = root.get("clazz").get("name");
predicates.add(cb.equal(clazzName, student.getClazz().getName()));
}
}
return query.where(predicates.toArray(new Predicate[predicates.size()])).getRestriction();
}
});
List<Map<String, Object>> results = new ArrayList<>();
//遍历查询出的学生对象,提取姓名、年龄、性别信息
for(Student stu :students){
Map<String , Object> stuMap = new HashMap<>();
stuMap.put("name", stu.getName());
stuMap.put("age", stu.getAge());
stuMap.put("sex", stu.getSex());
stuMap.put("address", stu.getAddress());
stuMap.put("clazzName", stu.getClazz().getName());
results.add(stuMap);
}
return results;
}
/*
分页查询某个班级的学生信息
@param clazzName代表班级名称
@param pageIndex代表当前查询第几页
@param pageSize代表每页查询的最大数据量
@return
*/
@SuppressWarnings("serial")
public Page<Student> getStusByPage(String clazzName , int pageIndex , int pageSize){
//指定排序参数对象:根据id,进行降序查询
Sort sort = Sort.by(Sort.Direction.DESC, "id");
//分页查询学生信息,返回分页实体对象数据
//page对象中包含了查询出来的数据信息以及与分页相关的信息
Page<Student>pages = studentRepository.findAll(new Specification<Student>() {
@Override
public Predicate toPredicate(Root<Student>root,CriteriaQuery<?>query,
CriteriaBuilder cb){
root.join("clazz",JoinType.INNER);
Path<String> cn = root.get("clazz").get("name");
Predicate p1 = cb.equal(cn, clazzName);
return p1;
}
},PageRequest.of(pageIndex-1, pageSize, sort));
return pages;
}
}
6.定义分页的页面数据对象
在com/example/springdatajpaspecificationtest下新建一个包vo,vo下创建一个Java类PageData.java
package com.example.springdatajpaspecificationtest.vo;
//此类用于封装分页查询出的数据信息
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/*
定义一个对象用于封装一页数据
*/
public class PageData {
//定义一个变量用于存放当前页码
private int pageIndex;
//定义一个变量用于保存满足查询条件下用于分页的数据总量
private long totalCount;
//定义一个变量用于保存当前条件下可以分得总页数
private int pageSize;
//定义一个变量用于保存当前页码查询出的数据总量
private int pageNum;
//定义一个变量用于保存当前查询出来的学生信息
private List<Map<String, Object>> stuDatas = new ArrayList<>();
public int getPageIndex() {
return pageIndex;
}
public void setPageIndex(int pageIndex) {
this.pageIndex = pageIndex;
}
public long getTotalCount() {
return totalCount;
}
public void setTotalCount(long totalCount) {
this.totalCount = totalCount;
}
public int getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
public int getPageNum() {
return pageNum;
}
public void setPageNum(int pageNum) {
this.pageNum = pageNum;
}
public List<Map<String, Object>> getStuDatas() {
return stuDatas;
}
public void setStuDatas(List<Map<String, Object>> stuDatas) {
this.stuDatas = stuDatas;
}
}
7.定义控制器类
在src/main/java/com/example/springdatajpaspecificationtest/controller下创建一个Java类,命名为StudentController.java
package com.example.springdatajpaspecificationtest.controller;
import java.util.*;
import javax.annotation.Resource;
import com.example.springdatajpaspecificationtest.bean.Clazz;
import com.example.springdatajpaspecificationtest.bean.Student;
import com.example.springdatajpaspecificationtest.service.SchoolService;
import com.example.springdatajpaspecificationtest.vo.PageData;
import org.springframework.data.domain.Page;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/student")
public class StudentController {
//注入SchoolService
@Resource
private SchoolService schoolService;
@RequestMapping("/save")
public String save() {
Clazz clazz1 = new Clazz("疯狂java开发1班");
Clazz clazz2 = new Clazz("疯狂java开发2班");
//保存班级对象数据
List<Clazz> clazzs = new ArrayList<>();
clazzs.add(clazz1);
clazzs.add(clazz2);
schoolService.saveClazzAll(clazzs);
Student swk = new Student("孙悟空","花果山",700,'男',clazz1);
Student zx = new Student("紫霞仙子","盘丝洞",500,'女',clazz1);
Student zzb = new Student("至尊宝","广州",500,'男',clazz1);
Student tsgz= new Student("铁扇公主","火焰山",500,'女',clazz2);
Student nmw = new Student("牛魔王","广州",500,'男',clazz2);
Student zzj = new Student("蜘蛛精","广州",700,'女',clazz2);
List<Student> students = new ArrayList<>();
students.add(swk);
students.add(zx);
students.add(zzb);
students.add(tsgz);
students.add(nmw);
students.add(zzj);
schoolService.saveStudentAll(students);
return "保存学生对象成功";
}
@RequestMapping("/getStusBySex")
public List<Map<String, Object>> getStusBySex(char sex){
return schoolService.getStusBySex(sex);
}
//动态查询学生信息
//可以根据学生对象的姓名(模糊匹配)、地址查询(模糊匹配)、性别、班级查询学生信息
@RequestMapping("/getStusByDynamic")
List<Map<String, Object>> getStusByDynamic(Student student){
return schoolService.getStusByDynamic(student);
}
//分页查询某个班级的学生信息
@RequestMapping("/getStusByPage")
PageData getStusByPage(String clazzName, int pageIndex, int pageSize){
//分页查询某个班级的学生信息
Page<Student> page=schoolService.getStusByPage(clazzName,pageIndex,pageSize);
List<Student> students=page.getContent();
List<Map<String, Object>> stuDatas = new ArrayList<>();
for (Student stu :students){
Map<String, Object> stuMap = new HashMap<>();
stuMap.put("name", stu.getName());
stuMap.put("id", stu.getId());
stuMap.put("age", stu.getAge());
stuMap.put("sex", stu.getSex());
stuMap.put("address", stu.getAddress());
stuMap.put("clazzName", stu.getClazz().getName());
stuDatas.add(stuMap);
}
//将分页查询出的结果数据进行分析
//然后把数据存入PageData对象中相应给浏览器显示
PageData data = new PageData();
data.setStuDatas(stuDatas);
data.setPageIndex(page.getNumber()+1);
data.setPageSize(page.getTotalPages());
data.setTotalCount(page.getTotalElements());
data.setPageNum(page.getSize());
return data;
}
}
8.测试应用
启动MySQL数据库,创建一个 新的数据库,命名为springdatajpaspecification, 然后在org.fkit.springdatajpaspecificationtest包下新建App.java启动类,App.java 和之前的项目一致,此处不再赘述。右击该类运行main方法。Spring Boot项目启动后,JPA会在数据库中自动创建持久化类对应的tb_ student 和tb clazzz 表。
测试添加学生和班级信息,在浏览器中输入如下地址:
http://localhost:8080/student/save
请求会提交到StudentController类的save方法进行处理,执行完成返回“保存学生对象成功”,查看数据库中的数据。
测试性别查询学生信息,在浏览器中输入如下地址:
http://localhost:8080/student/getStusBySex?sex=女
测试动态查询学生信息,在浏览器中输入如下地址:
http://localhost:8080/student/getStusByDynamic?clazzName=疯狂java开发1班&sex=女
http://localhost:8080/student/getStusByDynamic?address=广州&sex=男
测试分页查询某个班级下的学生信息,在浏览器中输入如下地址:
http://localhost:8080/student/getStusByPage?clazzName=疯狂java开发1班&pageIndex=1&pageSize=2
查询第二页数据,在浏览器中输入如下地址:
http://localhost:8080/student/getStusByPage?clazzName=疯狂java开发1班&pageIndex=2&pageSize=2