Spring Data JPA 基本使用


Spring Data JPA

文档官网地址:Spring Data JPA 官方文档


一、Spring Data JPA是什么?

Spring Data JPA provides repository support for the Java Persistence API (JPA). It eases development of applications that need to access JPA data sources.

Spring Data JPA, part of the larger Spring Data family, makes it easy to easily implement JPA based repositories. This module deals with enhanced support for JPA based data access layers. It makes it easier to build Spring-powered applications that use data access technologies.

Implementing a data access layer of an application has been cumbersome for quite a while. Too much boilerplate code has to be written to execute simple queries as well as perform pagination, and auditing. Spring Data JPA aims to significantly improve the implementation of data access layers by reducing the effort to the amount that’s actually needed. As a developer you write your repository interfaces, including custom finder methods, and Spring will provide the implementation automatically.

大体意思:Spring Data JPA 为 Java Persistence API (JPA) 提供存储库支持,它简化了需要访问 JPA 数据源的应用程序的开发,Spring Data JPA 是更大的 Spring Data 家族的一部分,该模块处理对基于 JPA 的数据访问层的增强支持,它使构建使用数据访问技术的 Spring 驱动的应用程序变得更加容易。Spring Data JPA 在通过减少实际需要的工作量来显着改进数据访问层的实现。作为开发人员,编写存储库接口,包括自定义查找器方法,Spring 将自动提供实现(通过方法名可以自动实现方法具体逻辑,这也是JPA最好用的一点)。

二、SpringBoot配置JPA

1.引入Maven依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

2. Yaml配置基本信息

spring:
  jpa:
    #数据库类型
    database: mysql
    #是否显示执行的SQL(开发模式建议开启,容易排查错误)
    show-sql: true
    hibernate:
      #ddl-auto:create ----每次运行该程序,没有表格会新建表格,表内有数据会清空;
      #ddl-auto:create-drop ----每次程序结束的时候会清空表
      #ddl-auto:update ---- 每次运行程序,没有表格会新建表格,表内有数据不会清空,只会更新
      #ddl-auto: validate ---- 运行程序会校验数据与数据库的字段类型是否相同,不同会报错
      ddl-auto: none
      naming:
        #命名策略
        physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
    #Open Session In View简称OSIV,OSIV技术来解决LazyInitialization问题会导致open的session生命周期过长,
    #它贯穿整个request,在view渲染完之后才能关闭session释放数据库连接,不建议开启
    #如果你有做多数据源切换,如果开启了OSIV那么将无法实现多数据源切换,本人在这个坑陷了很久。
    open-in-view: false

关于open-in-view属性的详细信息可以参考文档:聊聊JPA中的open-in-view

3. 启动类配置基本信息

一般来说启动类并不需要配置什么关于JPA的信息,不过这里还是讲解几个常用注解信息:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@EntityScan
@SpringBootApplication
@EnableJpaRepositories
@EnableTransactionManagement
public class DemoJpaApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoJpaApplication.class, args);
    }

}

一、@EntityScan注解
注解原文有这段话:Configures the base packages used by auto-configuration when scanning for entity(配置扫描实体时自动配置使用的基本包),注解的basePackages属性用于配置程序要扫描的实体(Entity)包路径。通常在主应用程序所在包或者其子包的某个位置我们定义了Entity和Repository,这样基于Springboot的自动配置,无需额外配置,我们定义的Entity即可被发现和使用。但有时候我们需要定义Entity不在应用程序所在包及其子包,比如引入子模块或者依赖,需要将子模块或者依赖的Entity也要进行扫描,那么就需要配置该注解并给basePackages赋予相应的路径。

二、@EnableJpaRepositories
注解原文介绍:Annotation to enable JPA repositories. Will scan the package of the annotated configuration class for Spring Data repositories by default.(启用JPA Repository 注解,默认情况下扫描配置类包以查找Spring data Repository),注解的basePackages属性用于配置程序要扫描的实体(repositories)包路径。通常在主应用程序所在包或者其子包的某个位置我们定义了Repository,这样基于Springboot的自动配置,无需额外配置,我们定义的Repository即可被发现和使用。但有时候我们需要定义Repository不在应用程序所在包及其子包,比如引入子模块或者依赖,需要将子模块或者依赖的Repository也要进行扫描,那么就需要配置该注解并给basePackages赋予相应的路径。


三、Start Using JPA

1.核心概念Repository

Spring Data 存储库抽象中的中央接口是Repository,它需要域类来管理以及域类的 ID 类型作为类型参数。此接口主要用作标记接口,以捕获要使用的类型并帮助您发现扩展此接口的接口,CrudRepository 接口为被管理的实体类提供了简单的 CRUD 功能。
一、CrudRepository具体实现

@NoRepositoryBean
public interface CrudRepository<T, ID> extends Repository<T, ID> {

     保存给定的信息
    <S extends T> S save(S entity);
     批量保存信息
    <S extends T> Iterable<S> saveAll(Iterable<S> entities);
     根据ID查询信息
    Optional<T> findById(ID id);
     根据ID判断是否存在
    boolean existsById(ID id);
     查询所有信息
    Iterable<T> findAll();
     根据ID查询信息
    Iterable<T> findAllById(Iterable<ID> ids);
     统计数量
    long count();
     根据ID删除
    void deleteById(ID id);
     删除给定实体
    void delete(T entity);
     根据ID删除实体
    void deleteAllById(Iterable<? extends ID> ids);
     批量删除实体
    void deleteAll(Iterable<? extends T> entities);
     删除所有
    void deleteAll();
}

二、除了CrudRepository之外,有一个PagingAndSortingRepository抽象,用于做分页查询和排序:

@NoRepositoryBean
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {

	/**
	 * Returns all entities sorted by the given options.
	 *
	 * @param sort the {@link Sort} specification to sort the results by, can be {@link Sort#unsorted()}, must not be
	 *          {@literal null}.
	 * @return all entities sorted by the given options
	 */
	Iterable<T> findAll(Sort sort);

	/**
	 * Returns a {@link Page} of entities meeting the paging restriction provided in the {@link Pageable} object.
	 *
	 * @param pageable the pageable to request a paged result, can be {@link Pageable#unpaged()}, must not be
	 *          {@literal null}.
	 * @return a page of entities
	 */
	Page<T> findAll(Pageable pageable);
}

因为JpaRepository继承了PagingAndSortingRepository,而PagingAndSortingRepository又继承了CrudRepository,所以我们只需要扩展JpaRepository实现我们自己的Repository

2.扩展JapRepository实现自己的Repository

一、构建User Entity:

@Entity
@Data
@Builder
@Table(name = "user")
@AllArgsConstructor
@NoArgsConstructor
public class User {
    /**
     JPA提供的四种标准用法为TABLE,SEQUENCE,IDENTITY,AUTO. 
     TABLE:使用一个特定的数据库表格来保存主键。 
     SEQUENCE:根据底层数据库的序列来生成主键,条件是数据库支持序列。 
     IDENTITY:主键由数据库自动生成(主要是自动增长型) 
     AUTO:主键由程序控制。
     */
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private Long id;

    @Column(name = "name")
    private String name;

    @Column(name = "phone")
    private String phone;

    @Column(name = "hobby")
    private String hobby;

}

二、扩展JapRepository实现自己的Repository

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

}

三、创建测试类,进行CRUD测试

@SpringBootTest(classes = DemoJpaApplication.class)
@RunWith(SpringRunner.class)
@ActiveProfiles(value = "test")
class DemoJpaApplicationTests {

    @Autowired
    UserRepository userRepository;

    @Test
    @Transactional
    @Rollback(value = false)
    void contextLoads() {
      // Problem: Transaction Rollback while updating - Junit of DataJPA
      // Answer: you also need to add @Rollback(false) with @Test, @Transactional
      //对于JPA的修改或者删除,需要增加事务,所以要加上@Transactional和@Rollback
        System.out.println("----查询所有信息");
        System.out.println(userRepository.findAll());
        System.out.println("----根据ID查询信息");
        System.out.println(userRepository.findById(1L));
        System.out.println("---新增信息");
        userRepository.save(User.builder().name("测试").hobby("打篮球").phone("123456").build());
        System.out.println("--根据ID删除信息");
        userRepository.deleteById(2L);
        System.out.println("-----分页查询,按名字降序");
        System.out.println(userRepository.findAll(PageRequest.of(0,10,Sort.by(Sort.Order.desc("name")))));
    }

3、自定义接口,JPA自动实现接口

可以看到JPA提供了很多基本的CRUD,这大大简化了开发。JPA除了提供基本的CURD方法,也允许你自定义方法,它会根据你定义的方法名进行自动接口实现,即约定大约配置原则。这可以让你不用写任何SQL或者多余的方法实现,完成条件查询。当你按名称自定义方法时,JPA会猜想你要实现的功能,从而实现自动查询并封装结果:
在这里插入图片描述
Spring Data JPA文档提供了一份关系清单如下:

关键词样品JPSQL片段
AndfindByLastnameAndFirstname… where x.lastname = ?1 and x.firstname = ?2
OrfindByLastnameOrFirstname… where x.lastname = ?1 or x.firstname = ?2
Is,EqualsfindByFirstname,findByFirstnameIs,findByFirstnameEquals… where x.firstname = ?1
BetweenfindByStartDateBetween… where x.startDate between ?1 and ?2
LessThanfindByAgeLessThan… where x.age < ?1
LessThanEqualfindByAgeLessThanEqual… where x.age <= ?1
GreaterThanfindByAgeGreaterThan… where x.age > ?1
GreaterThanEqualfindByAgeGreaterThanEqual… where x.age >= ?1
AfterfindByStartDateAfter… where x.startDate > ?1
BeforefindByStartDateBefore… where x.startDate < ?1
IsNullfindByAgeIsNull… where x.age is null
IsNotNull,NotNullfindByAge(Is)NotNull… where x.age not null
LikefindByFirstnameLike… where x.firstname like ?1
NotLikefindByFirstnameNotLike… where x.firstname not like ?1
StartingWithfindByFirstnameStartingWith… where x.firstname like ?1(参数绑定后附%)
EndingWithfindByFirstnameEndingWith… where x.firstname like ?1(参数与前面的%绑定)
ContainingfindByFirstnameContaining… where x.firstname like ?1(绑定在%中的参数)
OrderByfindByAgeOrderByLastnameDesc… where x.age = ?1 order by x.lastname desc
NotfindByLastnameNot… where x.lastname <> ?1
InfindByAgeIn(Collection ages)… where x.age in ?1
NotInfindByAgeNotIn(Collection age)… where x.age not in ?1
TruefindByActiveTrue()… where x.active = true
FalsefindByActiveFalse()… where x.active = false
IgnoreCasefindByFirstnameIgnoreCase… where UPPER(x.firstame) = UPPER(?1)

4、JPA使用注解查询

JPA支持注解查询,常用注解**@Query**,该注解可以用于查询、修改、删除,不支持插入操作,但是当用于修改、删除时需要配合注解@Modifying使用,并且需要在Service层加上**@Transactional**开启事务,具体原因可以了解:Spring Data JPA @Modifying

基本使用(查询、修改、删除)如下:

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

    @Query(value = "SELECT * FROM user WHERE name = ?1", nativeQuery = true)
    User findUserByUserName(String name);

    @Modifying
    @Query(value = "UPDATE user set name=?1 where  id=?2",nativeQuery = true)
    void updateUserNameById(String userName,Long id);

    @Modifying
    @Query(value = "DELETE FROM user WHERE name = ?1",nativeQuery = true)
    Integer deleteUserByUserName(String name);
}

5、JPA使用审计功能

Spring Data提供了复杂的支持,以透明地跟踪创建或更改实体的人员以及发生的时间点。Spring Data JPA 附带实体侦听器,可以在你保存或者获取实体时自动填充某些信息,从Spring Data JPA 5开始,你可以通过使用注解@EnableJpaAuditing配置审计类来启用审计。Spring Data 提供了审计常用注解@CreatedBy、@CreatedDate、@LastModifiedBy、@LastModifiedDate来自动填充实体内容。

常用审计注解作用如下:
@CreatedBy:捕获创建实体的用户
@LastModifiedBy:捕获修改实体的用户
@CreatedDate:捕获实体创建的时候
@LastModifiedDate:捕获实体修改的时间

一、实体配置启用审核
通常每个实体都有一些公共的属性,如创建时间、创建人、修改时间、修改人等。因此可以将这类属性抽出成一个公共实体,然后为该类添加注解@EntityListeners(AuditingEntityListener.class)*启用审计功能。代码演示如下:

/**
 * @Author: Greyfus
 * @Create: 2022-08-30 13:51
 * @Version:
 * @Description:
 */
package com.example.jpa.demo.domain;

import lombok.Data;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.io.Serializable;
import java.util.Date;

@Data
@MappedSuperclass
//实体启动审计
@EntityListeners(AuditingEntityListener.class)
public class BaseDomain implements Serializable {

    private static final long serialVersionUID = 4904967912188051010L;

    @CreatedBy
    @Column(name = "create_by")
    private String createBy;

    @LastModifiedBy
    @Column(name = "update_by")
    private String updateBy;

    @CreatedDate
    @Column(name = "create_date")
    private Date createDate;

    @LastModifiedDate
    @Column(name = "update_date")
    private Date updateDate;

}

二、实现AuditorAware接口,告知审计当前用户信息
如果您使用@CreatedBy或@LastModifiedBy,审计基础设施需要了解当前的主体。为此,Spring Data提供了一个AuditorAware SPI接口,您必须实现这一点,告诉基础设施当前用户或系统与应用程序交互的情况。通用类型T定义了使用@CreatedBy或@LastModifiedBy注释的属性的类型。AuditorAware接口的getCurrentAuditor方法将返回的数据自动填充到标有@CreatedBy或@LastModifiedBy注释的字段上,返回的类型必须与字段类型一致。演示代码如下:

/**
 * @Author: Greyfus
 * @Create: 2022-08-30 13:57
 * @Version:
 * @Description:
 */
package com.example.jpa.demo.audit;

import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.AuditorAware;

import java.util.Optional;

@Configuration
public class SpringAuditorAware implements AuditorAware<String> {

    @Override
    public Optional<String> getCurrentAuditor() {
//        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        return Optional.ofNullable("测试人员");
    }
}

上面的代码写死了返回的用户是测试人员,通常需要根据业务获取当前登录人,常用技术比如Spring Security获取当前登录人。

三、启用审计功能并指定审计的实现类
在启动类配置@EnableJpaAuditing注解来启动审计功能,并通过auditorAwareRef参数指定配置类。演示代码如下:

package com.example.jpa.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@EntityScan
@EnableJpaAuditing(auditorAwareRef = "springAuditorAware")
@SpringBootApplication
@EnableJpaRepositories
@EnableTransactionManagement
public class DemoJpaApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoJpaApplication.class, args);
    }

}

四、测试审计功能是否有效
创建User实体,并继承BaseDomain,因为BaseDomain已经添加了@EntityListeners注解,所以User不再需要即可使用审计功能。演示代码如下:

/**
 * @Author: Greyfus
 * @Create: 2022-08-27 12:56
 * @Version:
 * @Description:
 */
package com.example.jpa.demo.domain;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.CreatedBy;

import javax.persistence.*;

@Entity
@Data
@Builder
@Table(name = "user")
@AllArgsConstructor
@NoArgsConstructor
public class User extends BaseDomain {

    /**
     * TABLE:使用一个特定的数据库表格来保存主键。
     * SEQUENCE:根据底层数据库的序列来生成主键,条件是数据库支持序列。
     * IDENTITY:主键由数据库自动生成(主要是自动增长型)
     * AUTO:主键由程序控制。
     */

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Long id;

    @Column(name = "name")
    private String name;

    @Column(name = "phone")
    private String phone;

    @Column(name = "hobby")
    private String hobby;

}

向数据库插入一条数据,演示代码如下:

@SpringBootTest(classes = DemoJpaApplication.class)
@RunWith(SpringRunner.class)
@ActiveProfiles(value = "test")
class DemoJpaApplicationTests {

    @Autowired
    UserRepository userRepository;

    @Test
    @Transactional
    @Rollback(value = false)
    public void createUser() {
        User user = User.builder().name("审计测试员").phone("131587478**").hobby("睡觉").build();
        userRepository.save(user);
    }

通过查看数据库发现create_by、update_by、create_date、update_date已经自动填充。
在这里插入图片描述

6、JPA使用Specification实现复杂查询

简介:”Spring Data Jpa 提供了多种方法来创建底层数据库查询,例如使用方法名称、@Query注释、命名查询等。Spring
Data JPA Criteria 查询有助于为域或实体类创建查询的 where 子句。Spring Data Jpa 提供 Criteria Api 以编程方式为底层数据库构建查询。Spring Data Jpa 采用 Eric Evans 书中“领域驱动设计”的概念,使用相同的语义,Spring Data 创建JpaSpecificationExecutor接口。

/**
 * Interface to allow execution of {@link Specification}s based on the JPA criteria API.
 *
 * @author Oliver Gierke
 * @author Christoph Strobl
 */
public interface JpaSpecificationExecutor<T> {

	/**
	 * Returns a single entity matching the given {@link Specification} or {@link Optional#empty()} if none found.
	 *
	 * @param spec can be {@literal null}.
	 * @return never {@literal null}.
	 * @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one entity found.
	 */
	Optional<T> findOne(@Nullable Specification<T> spec);

	/**
	 * Returns all entities matching the given {@link Specification}.
	 *
	 * @param spec can be {@literal null}.
	 * @return never {@literal null}.
	 */
	List<T> findAll(@Nullable Specification<T> spec);

	/**
	 * Returns a {@link Page} of entities matching the given {@link Specification}.
	 *
	 * @param spec can be {@literal null}.
	 * @param pageable must not be {@literal null}.
	 * @return never {@literal null}.
	 */
	Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable);

	/**
	 * Returns all entities matching the given {@link Specification} and {@link Sort}.
	 *
	 * @param spec can be {@literal null}.
	 * @param sort must not be {@literal null}.
	 * @return never {@literal null}.
	 */
	List<T> findAll(@Nullable Specification<T> spec, Sort sort);

	/**
	 * Returns the number of instances that the given {@link Specification} will return.
	 *
	 * @param spec the {@link Specification} to count instances for. Can be {@literal null}.
	 * @return the number of instances.
	 */
	long count(@Nullable Specification<T> spec);
}

一、Specification根据需求创建动态查询
准备工作:自己扩展的Repository继承JpaSpecificationExecutor接口

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

    @Query(value = "SELECT * FROM user WHERE name = ?1", nativeQuery = true)
    User findUserByUserName(String name);

    @Modifying
    @Query(value = "UPDATE user set name=?1 where  id=?2",nativeQuery = true)
    void updateUserNameById(String userName,Long id);

    @Modifying
    @Query(value = "DELETE FROM user WHERE name = ?1",nativeQuery = true)
    Integer deleteUserByUserName(String name);
}

(1) 查询大于或小于某个值或者时间的数据。

    @Test
    @Transactional
    @Rollback(value = false)
    public void specificationQuery() throws ParseException {
        /**
         * 查询条件,创建时间大于2022年9月2日
         */
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date createTime = dateFormat.parse("2022-09-02 00:00:00");

        Specification<User> userSpecification = ((root, query, criteriaBuilder) -> {
            List<Predicate> predicateList = new ArrayList<>();

            if (Objects.nonNull(createTime)) {
                predicateList.add(criteriaBuilder.greaterThan(root.get("createDate"), createTime));
            }

            /**
             * 根据ID升序排序
             */
            query.orderBy(criteriaBuilder.asc(root.get("id")));
            return query.where(criteriaBuilder.and(predicateList.toArray(new Predicate[0]))).getRestriction();
        });

        List<User> userList = userRepository.findAll(userSpecification);
        
    }

通过控制台可以查看生成的查询语句逻辑:
在这里插入图片描述

如果要查询大于等于某个值的条件,将criteriaBuilder.greaterThan改为criteriaBuilder.greaterThanOrEqualTo即可。
如果要查询小于等于某个值的条件,那么使用criteriaBuilder.lessThan或者criteriaBuilder.lessThanOrEqualTo即可。

(2) 范围查询between…and (criteriaBuilder.between)

  @Test
    @Transactional
    @Rollback(value = false)
    public void specificationQuery() throws ParseException {
        /**
         * 查询条件,创建时间大于2022年9月2日,小于当前时间
         */
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date createTime = dateFormat.parse("2023-09-02 00:00:00");
        Date endTime = new Date();
        Specification<User> userSpecification = ((root, query, criteriaBuilder) -> {
            List<Predicate> predicateList = new ArrayList<>();

            if (Objects.nonNull(createTime)&&Objects.nonNull(endTime)) {
                predicateList.add(criteriaBuilder.between(root.get("createDate"), createTime, endTime));
            }

            /**
             * 根据ID升序排序
             */
            query.orderBy(criteriaBuilder.asc(root.get("id")));
            return query.where(criteriaBuilder.and(predicateList.toArray(new Predicate[0]))).getRestriction();
        });

        List<User> userList = userRepository.findAll(userSpecification);

    }

通过控制台可以查看生成的查询语句逻辑:在这里插入图片描述
(3)模糊查询 Like(criteriaBuilder.like)

    @Test
    @Transactional
    @Rollback(value = false)
    public void specificationQuery() throws ParseException {

        String name = "审计";
        Specification<User> userSpecification = ((root, query, criteriaBuilder) -> {
            List<Predicate> predicateList = new ArrayList<>();

            if (Objects.nonNull(name)) {
                predicateList.add(criteriaBuilder.like(root.get("name"),"%"+name+"%"));
            }
            /**
             * 根据ID升序排序
             */
            query.orderBy(criteriaBuilder.asc(root.get("id")));
            return query.where(criteriaBuilder.and(predicateList.toArray(new Predicate[0]))).getRestriction();
        });

        List<User> userList = userRepository.findAll(userSpecification);

    }

通过控制台可以查看生成的查询语句逻辑:
在这里插入图片描述

(4)范围查询IN (criteriaBuilder.in(expression).value(T value))
以查询ID范围为例:

    @Test
    @Transactional
    @Rollback(value = false)
    public void specificationQuery(){
        
        List<Long> ids = new ArrayList<>();
        ids.add(1L);
        ids.add(2L);
        Specification<User> userSpecification = ((root, query, criteriaBuilder) -> {
            List<Predicate> predicateList = new ArrayList<>();

            if (Objects.nonNull(ids)) {
                predicateList.add(criteriaBuilder.in(root.get("id")).value(ids));
            }
            /**
             * 根据ID升序排序
             */
            query.orderBy(criteriaBuilder.asc(root.get("id")));
            return query.where(criteriaBuilder.and(predicateList.toArray(new Predicate[0]))).getRestriction();
        });

        List<User> userList = userRepository.findAll(userSpecification);

    }

通过控制台可以查看生成的查询语句逻辑:在这里插入图片描述

(5) 等值查询Equal 和非等值查询notEqual

           /**
           等值查询
           */
            if (Objects.nonNull(name)) {
                predicateList.add(criteriaBuilder.equal(root.get("name"),name));
            }
            /**
            非等值查询
            */
            if (Objects.nonNull(name)) {
                predicateList.add(criteriaBuilder.notEqual(root.get("name"),name));
            }

(6)分页查询
JpaSpecificationExecutor接口已经为我们定义好了分页查询接口,只需要我们制定好查询逻辑和分页对象即可实现分页查询。

	/**
	 * Returns a {@link Page} of entities matching the given {@link Specification}.
	 *
	 * @param spec can be {@literal null}.
	 * @param pageable must not be {@literal null}.
	 * @return never {@literal null}.
	 */
	Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable);

分页代码演示如下:

    @Test
    @Transactional
    @Rollback(value = false)
    public void specificationQuery(){

        List<Long> ids = new ArrayList<>();
        ids.add(1L);
        ids.add(2L);
        Specification<User> userSpecification = ((root, query, criteriaBuilder) -> {
            List<Predicate> predicateList = new ArrayList<>();

            if (Objects.nonNull(ids)) {
                predicateList.add(criteriaBuilder.in(root.get("id")).value(ids));
            }
            /**
             * 根据ID升序排序
             */
            query.orderBy(criteriaBuilder.asc(root.get("id")));
            return query.where(criteriaBuilder.and(predicateList.toArray(new Predicate[0]))).getRestriction();
        });

        /**
         * 分页查询
         */
        Page<User> userPage = userRepository.findAll(userSpecification, PageRequest.of(0, 10));
        //获取查询内容
        List<User> content = userPage.getContent();
        //获取总页数
        int totalPages = userPage.getTotalPages();
        //获取总条数
        long totalElements = userPage.getTotalElements();

    }

由于CriteriaBuilder接口定义了很多查询方式,比如isNotNullnotLikesubstring等,因为实在太多,这里不再演示,只需要根据具体的业务调用所需的方法即可。想进一步了解,推荐查看文档:Spring Data Jpa 规范

总结

关于JPA动态SQL查询还有一个JPA EntityManager的使用,在开发中对于某些复杂的逻辑会用到EntityManager,等空了再把EntityManager的基本使用写在文章上吧。

  • 6
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值