Specification查询

Spring Data JPA 提供了-个Specifcation接口,Specification 接口封装了JPA的Criteria查询条件,从而可以通过此接口更加方便地使用Criteria查询。

接下来以示例的方式来详细讲解使用Specification的查询、分页、动态查询等操作。

项目目录
项目目录

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值