0
点赞
收藏
分享

微信扫一扫

Spring Data JPA--动态查询--Specification--使用

陈情雅雅 2022-02-25 阅读 157


简介

说明

        ​本文用实例介绍JPA如何使用Specification进行动态查询(多条件查询)。

        有时候我们在查询某个实体的时候,给定的查询条件不是固定的,这个时候就需要动态构建相应的查询语句。

        (说点其他的:jpa相比Mybatis-Plus,差太多了。学jpa完全是为了维护以前的项目,如果新建项目,一定要用Mybatis-Plus????)

JpaSpecificationExecutor中定义的方法

public interface JpaSpecificationExecutor<T> {
T findOne(Specification<T> var1);

List<T> findAll(Specification<T> var1);

Page<T> findAll(Specification<T> var1, Pageable var2);

List<T> findAll(Specification<T> var1, Sort var2);

long count(Specification<T> var1);
}

接口Specification

可以简单理解为,Specification构造的就是查询条件。我们看看Specification中定义的方法。

/*
* root :T表示查询对象的类型,代表查询的根对象,可以通过root获取实体中的属性
* query :代表一个顶层查询对象,用来自定义查询
* cb :用来构建查询,此对象里有很多条件方法
**/
public interface Specification<T> {
Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);
}

  1. root
  1. Root接口,主要用于处理实体和字段、实体与实体之间的关系。
  1. query
  1. CriteriaQuery接口,主要用于对查询结果的处理。包括groupBy、orderBy、having、distinct等操作。
  1. criteriaBuilder
  1. CriteriaBuilder接口,主要用于拼接各种条件、模拟sql函数等。

CriteriaBuilder接口(太多了,只截取一部分):

Spring Data JPA--动态查询--Specification--使用_数据库

使用

如果你想使用这个功能,只需要继承JpaSpecificationExecutor接口。

public interface UserRepository extends CrudRepository<User, Long>, JpaSpecificationExecutor<User> {
}

公共代码

配置文件及依赖

spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/jpa?characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username: root
password: 222333

jpa:
# 打印sql
show-sql: true
# 关闭属性的延时加载
open-in-view: false
# hibernate:
# # 是否开启自动更新数据库表结构。生产中不要开启。
# # 有以下几个值供选择。常用:update、none
# # create:每次加载hibernate时都删除上次生成的表,再根据你的model类生成新表。即使两次没有改变也这样执行,可能导致表数据丢失。
# # create-drop:每次加载hibernate时,先删除已存在的表结构再重新生成,sessionFactory一关闭,表就自动删除。
# # update:第一次加载hibernate时根据model类自动建立表结构,以后加载hibernate时根据model类自动更新表结构。
# # 即使表结构变了但表中的行仍然存在不会删除以前的行。
# # 部署到服务器后,表结构是不会被马上建立起来的,是要等应用第一次运行起来后才会。
# # validate:每次加载hibernate时,验证创建数据库表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值。
# # none:关闭自动更新
# ddl-auto: none
#
# # hibernate5及之后的命名策略配置。
# naming:
# # 负责模型对象层次的处理,将对象模型处理为逻辑名称
# # 有以下5个值供选择:
# # ImplicitNamingStrategyJpaCompliantImpl(默认值)后四者均继承自它。
# # ImplicitNamingStrategyComponentPathImpl
# # ImplicitNamingStrategyLegacyHbmImpl
# # ImplicitNamingStrategyLegacyJpaImpl
# # SpringImplicitNamingStrategy
# implicit-strategy: org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl
#
# # 映射成真实的数据名称的处理,将逻辑名称处理为物理名称。
# # 有以下2个值供选择:
# # PhysicalNamingStrategyStandardImpl:直接映射,若有@Column则以其为准。等同于之前的DefaultNamingStrategy
# # SpringPhysicalNamingStrategy(默认值):字段为小写,当有大写字母的时候会转换为分隔符号“_”。等同于之前的ImprovedNamingStrategy
# physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
#
# # hibernate5之前的命名策略配置。
# # 有以下2个值供选择:
# # DefaultNamingStrategy(默认值):直接映射,若有@Column则以其为准。
# # ImprovedNamingStrategy:字段为小写,当有大写字母的时候会转换为分隔符号“_”。
# # naming-strategy: org.hibernate.cfg.ImprovedNamingStrategy

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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo_jpa</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo_jpa</name>
<description>demo_jpa</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.1-api</artifactId>
<version>1.0.2.Final</version>
</dependency>

<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>3.0.2</version>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>

</project>

表结构及数据

business.sql

DROP TABLE IF EXISTS t_user;

CREATE TABLE `t_user`
(
`id` bigint(0) AUTO_INCREMENT,
`user_name` VARCHAR(32),
`age` int,
PRIMARY KEY (id)
) ENGINE = InnoDB;

INSERT INTO `t_user` values (1, 'Tony1', 21);
INSERT INTO `t_user` values (2, 'Tony2', 22);
INSERT INTO `t_user` values (3, 'Tony3', 23);
INSERT INTO `t_user` values (4, 'Tony3', 24);

代码

Entity

package com.example.demo.user.entity;

import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;

@Data
@NoArgsConstructor
@Entity
@Table(name = "t_user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String userName;

private Integer age;
}

Repository

package com.example.demo.user.repository;

import com.example.demo.user.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {

}

实例:简单查询(列表/分页)

Controller

package com.example.demo.user.controller;

import com.example.demo.user.entity.User;
import com.example.demo.user.repository.UserRepository;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
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.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.ArrayList;
import java.util.List;

@Api(tags = "动态查询(Specification)")
@RestController
@RequestMapping("/bySpecification")
public class DynamicQueryBySpecificationController {
@Autowired
private UserRepository userRepository;

@ApiOperation("列表")
@GetMapping("list")
public void list() {
Specification<User> specification = buildCondition1("Tony", 23);
List<User> all = userRepository.findAll(specification);
System.out.println(all);
}

@ApiOperation("分页")
@GetMapping("page")
public void page() {
Specification<User> specification = buildCondition1("Tony", 23);

Sort sort = Sort.by(Sort.Direction.DESC, "id");
PageRequest pageRequest = PageRequest.of(0, 2, sort);

Page<User> page = userRepository.findAll(specification, pageRequest);
System.out.println(page.getContent());
}

// 通过userName和age进行查询(与的关系)
private Specification<User> buildCondition1(String userName, Integer age) {
//重写toPredicate方法
return (Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) -> {

List<Predicate> predicateList = new ArrayList<>();

if (StringUtils.isNotBlank(userName)) {
// 本处我都转为小写,进行模糊匹配
predicateList.add(cb.like(cb.lower(root.get("userName").as(String.class)),
"%" + userName.toLowerCase() + "%"));
}

if (age != null) {
predicateList.add(cb.equal(root.get("age").as(Integer.class), age));
}

return cb.and(predicateList.toArray(new Predicate[0]));

// 也可以这么写返回值,但麻烦
// Predicate[] andPredicate = new Predicate[predicateList.size()];
// return cb.and(predicateList.toArray(andPredicate));
};
}
}

测试

测试列表

访问: ​bySpecification/list">​http://localhost:8080/bySpecification/list​​

后端结果:

Hibernate: select user0_.id as id1_0_, user0_.age as age2_0_, user0_.user_name as user_nam3_0_ from t_user user0_ where (lower(user0_.user_name) like ?) and user0_.age=23
[User(id=3, userName=Tony3, age=23)]

测试分页

访问: ​bySpecification/page">​http://localhost:8080/bySpecification/page​​

后端结果:

Hibernate: select user0_.id as id1_0_, user0_.age as age2_0_, user0_.user_name as user_nam3_0_ from t_user user0_ where (lower(user0_.user_name) like ?) and user0_.age=23 order by user0_.id desc limit ?
[User(id=3, userName=Tony3, age=23)]

实例:复杂查询(列表)

Controller

package com.example.demo.user.controller;

import com.example.demo.user.entity.User;
import com.example.demo.user.repository.UserRepository;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
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.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.ArrayList;
import java.util.List;

@Api(tags = "动态查询(Specification)")
@RestController
@RequestMapping("/bySpecification")
public class DynamicQueryBySpecificationController {
@Autowired
private UserRepository userRepository;

@ApiOperation("列表(复杂条件)")
@GetMapping("listComplex")
public void listComplex() {
Specification<User> specification1 = buildCondition2(1L, "Tony", 23);
List<User> all = userRepository.findAll(specification1);
System.out.println(all);

System.out.println("----------------------------------------------------------");

Specification<User> specification2 = buildCondition2(1L, "Tony", null);
List<User> all2 = userRepository.findAll(specification2);
System.out.println(all2);

System.out.println("----------------------------------------------------------");

Specification<User> specification3 = buildCondition2(null, "Tony", 23);
List<User> all3 = userRepository.findAll(specification3);
System.out.println(all3);
}

// 查找id不等于某个值 而且 userName或age满足条件的数据
private Specification<User> buildCondition2(Long excludeId, String userName, Integer age) {
//重写toPredicate方法
return (Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) -> {
List<Predicate> predicateList = new ArrayList<>();

if (excludeId != null) {
Predicate idPredicate = cb.notEqual(root.get("id").as(Long.class), excludeId);
predicateList.add(idPredicate);
}

// 拼接 userName或age 这个条件
List<Predicate> userNameOrAgePredicates = new ArrayList<>();
if (StringUtils.isNotBlank(userName)) {
// 本处我都转为小写,进行模糊匹配
Predicate userNamePredicate = cb.like(cb.lower(root.get("userName").as(String.class)),
"%" + userName.toLowerCase() + "%");
userNameOrAgePredicates.add(userNamePredicate);
}

if (age != null) {
Predicate agePredicate = cb.equal(root.get("age").as(Integer.class), age);
userNameOrAgePredicates.add(agePredicate);
}

Predicate[] tmpUserNameOrAgePredicate = new Predicate[userNameOrAgePredicates.size()];
Predicate userNameOrAgePredicate = cb.or(userNameOrAgePredicates.toArray(tmpUserNameOrAgePredicate));
predicateList.add(userNameOrAgePredicate);

return cb.and(predicateList.toArray(new Predicate[0]));

// 也可以这么写返回值,但麻烦
// Predicate[] andPredicate = new Predicate[predicateList.size()];
// return cb.and(predicateList.toArray(andPredicate));
};
}
}

 测试

测试列表

访问: ​bySpecification/listComplex">​http://localhost:8080/bySpecification/listComplex​​

后端结果:

Hibernate: select user0_.id as id1_0_, user0_.age as age2_0_, user0_.user_name as user_nam3_0_ from t_user user0_ where user0_.id<>1 and (lower(user0_.user_name) like ? or user0_.age=23)
[User(id=2, userName=Tony2, age=22), User(id=3, userName=Tony3, age=23), User(id=4, userName=Tony3, age=24)]
----------------------------------------------------------
Hibernate: select user0_.id as id1_0_, user0_.age as age2_0_, user0_.user_name as user_nam3_0_ from t_user user0_ where user0_.id<>1 and (lower(user0_.user_name) like ?)
[User(id=2, userName=Tony2, age=22), User(id=3, userName=Tony3, age=23), User(id=4, userName=Tony3, age=24)]
----------------------------------------------------------
Hibernate: select user0_.id as id1_0_, user0_.age as age2_0_, user0_.user_name as user_nam3_0_ from t_user user0_ where lower(user0_.user_name) like ? or user0_.age=23
[User(id=1, userName=Tony1, age=21), User(id=2, userName=Tony2, age=22), User(id=3, userName=Tony3, age=23), User(id=4, userName=Tony3, age=24)]

其他网址

​​SpringDataJpa中的复杂查询和动态查询,多表查询。(保姆级教程)​​


举报

相关推荐

0 条评论