Spring Data JPA 提供了-个Specifcation接口,Specification 接口封装了JPA的Criteria查询条件,从而可以通过此接口更加方便地使用Criteria查询。
接下来以示例的方式来详细讲解使用Specification的查询、分页、动态查询等操作。
1.创建maven项目,
命名为springdatajpaspecificationtest
修改pom.xml文件
4.0.0 com.example springdatajpaspecificationtest0.0.1-SNAPSHOT springdatajpaspecificationtest springdatajpaspecificationtest spring-boot-starter-parent org.springframework.boot 2.7.6 19 UTF-8 UTF-8 2.6.13 org.springframework.boot spring-boot-starter-thymeleaforg.springframework.boot spring-boot-starter-weborg.springframework.boot spring-boot-starter-testtest org.springframework.boot spring-boot-starter-data-jpamysql mysql-connector-javaorg.assertj assertj-coreorg.springframework.boot spring-boot-dependencies${spring-boot.version} pom import org.apache.maven.plugins maven-compiler-plugin3.8.1 19 UTF-8 org.springframework.boot spring-boot-maven-plugin${spring-boot.version} com.example.springdatajpaspecificationtest.SpringdatajpaspecificationtestApplication true repackage repackage
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.ImplicitNamingStrategyComponentPathImplspring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl#指定数据库方言spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialectspring.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 Setstudents=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 getStudents() { return students; } public void setStudents(Set 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, JpaSpecificationExecutor {}
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, JpaSpecificationExecutor {}
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;@Servicepublic class SchoolService { //注入数据访问层接口对象 @Resource private StudentRepository studentRepository; @Resource private ClazzRepository clazzRepository; @Transactional public void saveClazzAll(Listclazzs){ clazzRepository.saveAll(clazzs); } @Transactional public void saveStudentAll(List students){ studentRepository.saveAll(students); } @SuppressWarnings("serial")//serial连续的 public List
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> 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 > getStuDatas() { return stuDatas; } public void setStuDatas(List > 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班"); //保存班级对象数据 Listclazzs = 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 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 > getStusBySex(char sex){ return schoolService.getStusBySex(sex); } //动态查询学生信息 //可以根据学生对象的姓名(模糊匹配)、地址查询(模糊匹配)、性别、班级查询学生信息 @RequestMapping("/getStusByDynamic") List > getStusByDynamic(Student student){ return schoolService.getStusByDynamic(student); } //分页查询某个班级的学生信息 @RequestMapping("/getStusByPage") PageData getStusByPage(String clazzName, int pageIndex, int pageSize){ //分页查询某个班级的学生信息 Page page=schoolService.getStusByPage(clazzName,pageIndex,pageSize); List students=page.getContent(); List > stuDatas = new ArrayList<>(); for (Student stu :students){ Map 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