Spring Data JPA入门到放弃

参考文档:SpringData JPA:一文带你搞懂 - 知乎 (zhihu.com)

一、 前言

1.1 概述

Java持久化技术是Java开发中的重要组成部分,它主要用于将对象数据持久化到数据库中,以及从数据库中查询和恢复对象数据。在Java持久化技术领域,Java Persistence API (JPA) 和 Spring Data JPA 是两个非常流行的框架。本文将对这两个框架进行简要介绍,以帮助您更好地理解它们的作用和使用方法。

1.2 JPA简介

Java Persistence API (JPA) 是一种基于 ORM (Object-Relational Mapping) 技术的 Java EE 规范(JPA是一种规范,而不是具体的框架)。它主要用于将 Java 对象映射到关系型数据库中,以便于对数据进行持久化操作。

JPA 主要由三个部分组成,分别是 Entity、EntityManager 和 Query。其中 Entity 用于描述 Java 对象和数据库表之间的映射关系;EntityManager 用于管理实体对象的生命周期和完成实体对象与数据库之间的操作;Query 用于查询数据。

JPA 支持多种底层实现,如 Hibernate、EclipseLink 等。在使用时,只需要引入相应的实现框架即可。 总结如下:

  • JPA(Java Persistence API)是为Java EE平台设计的一种ORM解决方案。
  • JPA提供了一些标准的API以及关系映射的元数据,使得Java开发人员可以在没有具体SQL编程经验的情况下,通过简单的注解配置实现对数据的访问和操作。
  • JPA提供了对事务的支持,允许Java开发人员进行基于POJO的开发,在运行时将这些POJO映射成关系数据库表和列,最大限度地减少了Java开发者与数据库的交互。

1.3 Spring Data JPA简介

Spring Data JPA 是 Spring 框架下的一个模块,是基于 JPA 规范的上层封装,旨在简化 JPA 的使用。

Spring Data JPA 提供了一些常用的接口,如 JpaRepository、JpaSpecificationExecutor 等,这些接口包含了很多常用的 CRUD 操作方法,可直接继承使用。同时,Spring Data JPA 还提供了基于方法命名规范的查询方式,可以根据方法名自动生成相应的 SQL 语句,并执行查询操作。这种方式可以大大减少编写 SQL 语句的工作量。

除了基础的 CRUD 操作外,Spring Data JPA 还提供了一些高级功能,如分页、排序、动态查询等。同时,它也支持多种数据库,如 MySQL、PostgreSQL、Oracle 等。 总结如下:

  • Spring Data JPA 是 Spring Data 项目家族中的一员,它为基于Spring框架应用程序提供了更加便捷和强大的数据操作方式。
  • Spring Data JPA 支持多种数据存储技术,包括关系型数据库和非关系型数据库。
  • Spring Data JPA 提供了简单、一致且易于使用的API来访问和操作数据存储,其中包括基本的CRUD操作、自定义查询方法、动态查询等功能。
  • Spring Data JPA 也支持QueryDSL、Jinq、Kotlin Query等其他查询框架

二,快速开始

2.1 配置环境

  • 使用 Spring Data JPA 需要在项目中配置相关依赖项和数据源。
  • Spring Data JPA 支持的数据库类型包括 MySQL、PostgreSQL、Oracle、MongoDB 等。

2.2 添加依赖

  • 在项目的 pom.xml 文件中添加如下 Spring Data JPA 相关依赖:
<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
​
<dependency>
 <groupId>mysql</groupId>
 <artifactId>mysql-connector-java</artifactId>
</dependency>
  • 如果使用其他数据库类型,可以替换 mysql-connector-java 依赖,并相应地调整数据源配置。

2.3 创建实体类

  • 在项目中创建实体类,用于映射数据库表和列。
  • 实体类需要使用 @Entity 注解进行标记,并且需要指定主键和自动生成策略,@Table可以指定该实体对应生成数据表的名称
@Entity
@Table(name = "user")
public class User {
 @Id
 @GeneratedValue(strategy = GenerationType.IDENTITY)
 private Long id;
 private String name;
 // ...
 // 省略 getter 和 setter 方法
}

2.4 创建Repository接口

  • 在项目中创建 Repository 接口,用于定义数据访问方法。
  • Repository 接口需要继承自 JpaRepository 接口,并且需要使用 @Repositor 注解进行标记,例如:
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
 //自定义方法
 User findByName(String name);
}
  • 在示例中,我们定义了一个名为 UserRepository 的接口,它继承自 JpaRepository 接口,泛型参数分别为实体类型和主键类型,并且新增了一个自定义查询方法 findByName
  • JpaRepository 接口包含了基本的增删改查方法,且增和改都是调用save方法(通过已存在id来判断)

2.5 运行示例应用程序

  • 编写示例代码并运行应用程序,以验证 Spring Data JPA 的功能和使用方法。
  • 示例代码可以是简单的控制台程序,也可以是 Web 应用程序。下面是一个基于 Spring Boot 的 Web 应用程序的示例代码:
@SpringBootApplication 
public class Application implements CommandLineRunner {

  @Autowired
  private UserRepository userRepository;

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

  @Override
  public void run(String... args) throws Exception {
      User user = new User();
      user.setName("Alice");
      user.setEmail("alice@example.com");
      userRepository.save(user);

      User savedUser = userRepository.findByName("Alice");
      System.out.println(savedUser);
  }
  • 在示例代码中,我们使用 @SpringBootApplication 注解标记了应用程序入口类,并且在 main 方法中启动了应用程序。
  • CommandLineRunner 接口的 run 方法用于定义初始化逻辑,在示例中我们创建了一个名为 Alice 的用户,并将其保存到数据库中,随后使用 findByName 方法查询并输出该用户信息。

三、实体映射

在使用 Spring Data JPA 时,需要定义实体类和数据表之间的映射关系。下面介绍常用的实体映射注解。

3.1 Entity注解

@Entity 注解用于标记实体类,表示该类会被映射到数据库中的一个表。

示例代码:

@Entity
public class User {
    // 省略属性和方法
}

3.2 Table注解

@Table 注解用于标注实体类与数据库表之间的映射关系,并可以指定表的名称、唯一约束等信息。

示例代码:

@Entity
@Table(name = "user")
public class User {
    // 省略属性和方法
}

3.3 Column注解

@Column 注解用于标注实体类属性与数据表字段之间的映射关系,并可以指定字段名称、长度、精度等信息。(指定数据库列的长度和是否为null)

示例代码:

@Entity
@Table(name = "user")
public class User {
    @Id
    private Long id;

    @Column(name = "user_name", length = 20, nullable = false)
    private String userName;

    // 省略其他属性和方法
}

3.4 Id注解

@Id 注解用于标注实体类属性作为主键,通常与 @GeneratedValue 注解一起使用指定主键生成策略。

示例代码:

@Entity
@Table(name = "user")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    // 省略其他属性和方法
}

3.5 GeneratedValue注解

@GeneratedValue 注解用于指定主键生成策略,通常与 @Id 注解一起使用。

示例代码:

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

    // 省略其他属性和方法
}

3.6 关系映射

关系映射通常包括一对一、一对多和多对多等关系。在 Spring Data JPA 中,可以使用 @OneToOne@OneToMany 和 @ManyToMany 注解来标注关系映射。这些注解通常与 @JoinColumn 注解一起使用,用于指定关联的外键列。

示例代码:

@Entity
@Table(name = "user")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    //一(用户user)对多 (地址address)
    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
    private List<Address> addresses;

    // 省略其他属性和方法
}

@Entity
@Table(name = "address")
public class Address {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    //多 (地址address)对一(用户user)
    @ManyToOne
    @JoinColumn(name = "user_id")
    private User user;

    // 省略其他属性和方法
}

在上例中,User 和 Address 之间是一对多的关系,所以在 User 实体类中使用了 @OneToMany 注解,在 Address 实体类中使用了 @ManyToOne 注解。mappedBy 属性用于指定关联的属性名称,这里是 user,表示 Address 实体类中的 user 属性与 User 实体类中的 addresses 属性相对应。cascade 属性表示级联操作,这里使用 CascadeType.ALL 表示在删除 User 实体时同时删除其关联的所有 Address 实体。@JoinColumn 注解用于指定外键名称,这里是 user_id,表示 Address 表中的 user_id 列与 User 表中的主键相对应。

四、Repository接口

Repository 接口是 Spring Data JPA 的核心接口之一,它提供了基本的增删改查方法和自定义查询方法,以及分页和排序等功能。在使用时需要继承 Repository 接口并指定对应的实体类和主键类型。

示例代码:

public interface UserRepository extends Repository<User, Long> {
    // 省略基本增删改查方法和自定义查询方法
}

4.1 各类Repository接口

4.1.1 增删改查方法接口:Repository 

在继承 Repository 接口后,会默认提供基本的增删改查方法,无需额外的代码实现即可使用。常用的方法如下:

方法名描述
T save(T entity)保存实体对象
Iterable saveAll(Iterable entities)批量保存实体对象
Optional findById(ID id)根据主键获取实体对象
boolean existsById(ID id)判断是否存在特定主键的实体对象
Iterable findAll()获取所有实体对象
Iterable findAllById(Iterable ids)根据主键批量获取实体对象
long count()获取实体对象的数量
void deleteById(ID id)根据主键删除实体对象
void delete(T entity)删除实体对象
void deleteAll(Iterable<? extends T> entities)批量删除实体对象

示例代码:

public interface UserRepository extends Repository<User, Long> {
    // 保存用户
    User save(User user);

    // 根据主键获取用户
    Optional<User> findById(Long id);

    // 获取所有用户
    Iterable<User> findAll();

    // 根据主键删除用户
    void deleteById(Long id);
}

4.1.2 分页和基本查询接口:JpaRepository

1,特点:
  1. 提供了基本的CRUD操作:JpaRepository接口继承自PagingAndSortingRepository和CrudRepository接口,因此提供了基本的CRUD操作,包括保存、更新、删除、查询等。

  2. 支持分页和排序:JpaRepository接口继承自PagingAndSortingRepository接口,因此支持分页和排序功能,可以方便地进行分页查询和结果排序。

  3. 自动生成查询方法:JpaRepository接口支持使用方法名来自动生成查询方法,无需手动编写SQL语句,可以根据方法名的规则来自动生成查询方法。

  4. 提供了一些其他便利方法:JpaRepository接口还提供了一些其他便利方法,如批量操作、统计记录数等。 

2,父接口继承结构:

 4.1.3 动态查询接口:JpaSpecificationExecutor

1,介绍

JpaSpecificationExecutor接口定义了一些方法,用于执行基于JPA规范的动态查询操作,包括:

  1. <T> List<T> findAll(Specification<T> spec):根据给定的Specification对象进行查询,返回符合条件的实体对象列表。

  2. <T> List<T> findAll(Specification<T> spec, Sort sort):根据给定的Specification对象进行查询,并按照指定的排序规则返回结果。

  3. <T> Page<T> findAll(Specification<T> spec, Pageable pageable):根据给定的Specification对象进行分页查询,返回符合条件的实体对象分页结果。

JpaSpecificationExecutor接口相对于JpaRepository接口提供了更加灵活、动态的查询功能,适用于需要根据不同条件进行动态查询的场景。

2,接口方法:

3,使用示例代码:
//构造排序对象
Sort.Order showOrder = Sort.Order.asc("order");
Sort sort = Sort.by(showOrder);
String param  = "something";
//拼接sql
PredicateBuilder<DocumentationCategory> predicateBuilder = Specifications.or();
for (int i = 0; i < parentIds.length; i++) {
     predicateBuilder = predicateBuilder.like("name", "%" + param  + "%");
} 
//创建查询条件对象
Specification<DocumentationCategory> specification = predicateBuilder.build();
//执行查询
List<DocumentationCategory> documentationCategoryList = documentationCategoryRepository.findAll(specification, sort);

4.2 自定义查询方法

在 Repository 接口中可以定义自定义查询方法,实现按照指定规则查询数据。Spring Data JPA 支持三种方式定义自定义查询方法:方法名称查询、参数设置查询、使用 @Query 注解查询。

4.2.1 示例代码

1,文档表:
@Getter
@Setter
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class Documentation extends AbstractBaseEntity {
    /**
     * 标题
     */
    @Column(name = "title", length = 200, nullable = false)
    private String title;

    /**
     * 单位
     */
    @Column(name = "unit", length = 200)
    private String unit;

    /**
     * 文档类型,DOCUMENTATION_TYPE
     */
    @Column(name = "doc_type", length = 50)
    private String docType;

    /**
     * 年份
     */
    @Column(name = "year", length = 4, nullable = false)
    private String year;

    /**
     * 排序
     */
    @Column(name = "show_order")
    private Integer showOrder;

    /**
     * 是否置顶
     */
    @Column(name = "top_able", columnDefinition = "boolean default false")
    private Boolean top;

    /**
     * 是否上屏
     */
    @Column(name = "show_able", columnDefinition = "boolean default false")
    private Boolean show;

    /**
     * 文档资料目录
     */
    @ManyToOne
    @JoinColumn(name = "document_category_id", nullable = false)
    private DocumentationCategory documentCategory;

    /**
     * 文号
     */
    @Column(name = "document_number")
    private String documentNumber;

    /**
     * 文档来源
     */
    @ManyToOne(targetEntity = Organization.class)
    @JoinColumn(name = "document_source_id")//指定document_source_id字段为外键列
    private Organization organization;
}

对应的数据库:

CREATE TABLE `mt_rd_documentation` (
  `id` bigint(20) NOT NULL,
  `created_by` bigint(20) DEFAULT NULL,
  `created_time` datetime NOT NULL,
  `last_modified_by` bigint(20) DEFAULT NULL,
  `last_modified_time` datetime NOT NULL,
  `filter_path` varchar(380) DEFAULT NULL,
  `doc_type` varchar(50) DEFAULT NULL,
  `document_number` varchar(255) DEFAULT NULL,
  `is_html` bit(1) DEFAULT b'0',
  `show_able` bit(1) DEFAULT b'0',
  `show_order` int(11) DEFAULT NULL,
  `title` varchar(200) NOT NULL,
  `top_able` bit(1) DEFAULT b'0',
  `unit` varchar(200) DEFAULT NULL,
  `year` varchar(4) NOT NULL,
  `content` bigint(20) DEFAULT NULL,
  `document_category_id` bigint(20) NOT NULL,
  `document_source_id` bigint(20) DEFAULT NULL, #框架自动根据关系映射成bigint字段保存外键
  PRIMARY KEY (`id`),
  KEY `idx_rd_documentation` (`title`),
  KEY `FKb8ucjiqbadllcx19p889x7iy8` (`document_category_id`),
  KEY `FKhgiblpnx645pltghu1h5yy943` (`document_source_id`),
  CONSTRAINT `FKb8ucjiqbadllcx19p889x7iy8` FOREIGN KEY (`document_category_id`) REFERENCES `mt_rd_documentation_category` (`id`),
  CONSTRAINT `FKhgiblpnx645pltghu1h5yy943` FOREIGN KEY (`document_source_id`) REFERENCES `mt_sys_organization` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2,文件来源表(单位表): 
public class Organization extends AbstractTreeEntity<Organization> {


    @ApiModelProperty(
        value = "主键,自动增长, 新建时为null",
        required = true
    )
    @ValueCopyIgnore
    @Id
    @GeneratedValue(
        generator = "snowflake"
    )
    @GenericGenerator(
        name = "snowflake",
        strategy = "com.matech.framework.spring.jpa.SnowflakeIdGenerator"
    )
    private Long id;

    @Column(
        name = "name",
        length = 100,
        nullable = false
    )
    private String name;
    @Column(
        name = "short_name",
        length = 100,
        nullable = false
    )
    private String shortName;
    @Column(
        name = "organization_type",
        nullable = false
    )
    @Enumerated(EnumType.STRING)
    private OrganizationType organizationType;
    @Column(
        name = "is_available",
        nullable = false
    )
    private Boolean available = true;
    @ManyToMany(
        mappedBy = "organizations",
        fetch = FetchType.LAZY
    )
    @OrderBy("showOrder asc")
    private Set<Employee> employees = new LinkedHashSet();
    @Transient
    private Unit unit;
    @Transient
    private Department department;
    @Transient
    private Position position;
    @Column(
        name = "is_interior",
        nullable = false
    )
    private Boolean interior = true;
    @ManyToMany(
        fetch = FetchType.LAZY
    )
    @JoinTable(
        name = "mt_sys_organization_role",
        joinColumns = {@JoinColumn(
    name = "organization_id"
)},
        inverseJoinColumns = {@JoinColumn(
    name = "role_id"
)}
    )
    @OrderBy("name asc")
    private Set<Role> roles = new LinkedHashSet();
    @Transient
    private String fullName;
}
 3,Query语句:

        下面Query中可以直接根据实体的对象成员的元素进行过滤,框架通过根据@JoinColumn配置的关系,自动进行联表查询,实现简化SQL开发的工作量,使得程序员有更多的余力致力于业务代码的开发(注意:这个不是普通的SQL语句(还需进过jpa框架解析的,所以可以用实体的字段名))

        如果某些复杂的业务场景,需要写《原生的SQL语句》这就需要在@Query注解中指定nativeQuery = true 参数,该参数默认为false,表示不使用原生SQL。值得注意的是采用原生SQL时,不能直接指定返回一个包含子对象成员的对象结果, 因为原生SQL不经过JPA框架处理,返回对象成员时,数据库保存的是该对象在数据库的id,而实体对象成员需要的是一个对象,会导致类型转换错误。但是某些场景可以直接返回,例如:返回对象成员都是基础数据类型等。

@Repository
public interface DocumentationRepository extends JpaRepository<Documentation, Long>, JpaSpecificationExecutor<Documentation> {
    @Query("select d.organization  from Documentation d where d.organization.name = '研发部'")
    List<Organization> getAllOrg();
    @Query(value = "select org.id from mt_rd_documentation d inner JOIN" +
            " mt_sys_organization org on d.document_source_id = org.id where org.name = '研发部'", nativeQuery = true)
    List<Long> getAllOrg2();
}

4.2.1 方法名称查询

        方法名称查询是 Spring Data JPA 中最简单的一种自定义查询方法,并且不需要额外的注解或 XML 配置。它通过方法名来推断出查询的条件,例如以 findBy 开头的方法表示按照某些条件查询,以 deleteBy 开头的方法表示按照某些条件删除数据。

示例代码:

public interface UserRepository extends Repository<User, Long> {
    // 根据用户名查询用户
    User findByUserName(String userName);

    // 根据年龄查询用户列表
    List<User> findByAge(Integer age);

    // 根据用户名和密码查询用户
    User findByUserNameAndPassword(String userName, String password);

    // 根据主键和用户名删除用户
    void deleteByIdAndUserName(Long id, String userName);
}

4.2.2 查询参数设置

        除了方法名称查询外,还可以使用参数设置方式进行自定义查询。它通过在方法上使用 @Query 注解来指定查询语句,然后使用 @Param 注解来指定方法参数与查询语句中的参数对应关系。

示例代码:

public interface UserRepository extends Repository<User, Long> {
    // 根据用户名查询用户
    @Query("SELECT u FROM User u WHERE u.userName = :userName")
    User findByUserName(@Param("userName") String userName);

    // 根据用户名和密码查询用户
    @Query("SELECT u FROM User u WHERE u.userName = :userName AND u.password = :password")
    User findByUserNameAndPassword(@Param("userName") String userName, @Param("password") String password);
}

4.2.3 使用@Query注解

        在自定义查询方法时,还可以使用 @Query 注解直接指定查询语句。@Query 注解的 value 属性表示查询语句,可以使用占位符 ?1、?2 等表示方法参数

示例代码:

public interface UserRepository extends Repository<User, Long> {
    // 根据用户名查询用户
    @Query(value = "SELECT * FROM user WHERE user_name = ?1", nativeQuery = true)
    User findByUserName(String userName);

    // 根据用户名和密码查询用户
    @Query(value = "SELECT * FROM user WHERE user_name = ?1 AND password = ?2", nativeQuery = true)
    User findByUserNameAndPassword(String userName, String password);
}

4.3 使用 Sort 和 Pageable 进行排序和分页

在查询数据时,经常需要对结果进行排序和分页操作。Spring Data JPA 提供了 Sort 和 Pageable 两个类来实现排序和分页功能。

Sort 类表示排序规则,可以使用 Sort.by() 静态方法创建实例,并指定排序属性和排序方向。常用方法如下:

方法名描述
static Sort by(Sort.Order... orders)根据排序规则创建 Sort 实例
static Sort.Order by(String property)根据属性升序排序
static Sort.Order by(String property, Sort.Direction direction)根据属性排序

示例代码:

public interface UserRepository extends Repository<User, Long> {
    // 根据年龄升序查询用户列表
    List<User> findByOrderByAgeAsc();

    // 根据年龄降序分页查询用户列表
    Page<User> findBy(Pageable pageable);
}

Pageable 类表示分页信息,可以使用 PageRequest.of() 静态方法创建实例,并指定页码、每页数据量和排序规则。常用方法如下:

方法名描述
static PageRequest of(int page, int size, Sort sort)创建分页信息实例
static PageRequest of(int page, int size, Sort.Direction direction, String... properties)创建分页信息实例

示例代码:

public interface UserRepository extends Repository<User, Long> {
    // 根据年龄降序分页查询用户列表
    Page<User> findBy(Pageable pageable);
}

// 使用
Pageable pageable = PageRequest.of(0, 10, Sort.by("age").descending());
Page<User> page = userRepository.findBy(pageable);
List<User> userList = page.getContent();

4.4 使用 @Modifying 注解进行修改

在 Spring Data JPA 中,使用 update 和 delete 语句需要使用 @Modifying 注解标注,并且需要添加 @Transactional 注解开启事务。需要注意的是,@Modifying 注解只支持 DML 语句。

示例代码:

public interface UserRepository extends Repository<User, Long> {
    // 更新用户密码
    @Modifying
    @Transactional
    @Query("UPDATE User u SET u.password = :password WHERE u.id = :id")
    void updatePasswordById(@Param("id") Long id, @Param("password") String password);

    // 删除年龄大于等于 age 的用户
    @Modifying
    @Transactional
    @Query("DELETE FROM User u WHERE u.age >= :age")
    void deleteByAgeGreaterThanEqual(@Param("age") Integer age);
}

五、高级查询

5.1 使用 Specification 进行动态查询

在实际应用中,我们经常需要根据条件动态生成查询语句。Spring Data JPA 提供了 Specification 接口来支持动态查询,它可以通过使用匿名内部类或 Lambda 表达式来实现。

Specification 接口定义了 toPredicate() 方法,该方法接受一个 Root<T> 对象和一个 CriteriaQuery<?> 对象作为参数,并返回一个 Predicate 对象,表示查询条件。

在 toPredicate() 方法内部,可以通过 root.get() 方法获取实体属性,并使用 criteriaBuilder 构建查询条件。 1.例如,以下 Lambda 表达式表示查询 age 大于等于 18 的用户。

public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
    List<User> findAll(Specification<User> spec);
}

// 使用
Specification<User> spec = (root, query, criteriaBuilder) -> criteriaBuilder.greaterThanOrEqualTo(root.get("age"), 18);
userRepostory.findAll(spec);

2.假设我们有一个 User 实体类,该实体包含了 username、age 和 email 等属性。现在我们需要根据传入的参数动态生成查询条件并查询用户列表。

首先,我们需要在 UserRepository 接口中继承 JpaSpecificationExecutor 接口,示例代码如下:

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

}

然后,我们可以在 UserService 中定义一个方法来进行动态查询,示例代码如下:

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public List<User> findUsersByCondition(String username, String email, Integer age) {
        return userRepository.findAll((root, query, criteriaBuilder) -> {
            List<Predicate> list = new ArrayList<>();
            if (StringUtils.isNotBlank(username)) {
                list.add(criteriaBuilder.equal(root.get("username"), username));
            }
            if (StringUtils.isNotBlank(email)) {
                list.add(criteriaBuilder.like(root.get("email"), "%" + email + "%"));
            }
            if (age != null) {
                list.add(criteriaBuilder.greaterThanOrEqualTo(root.get("age"), age));
            }
            Predicate[] predicates = list.toArray(new Predicate[list.size()]);
            return criteriaBuilder.and(predicates);
        });
    }

}

在上述代码中,我们首先判断传入的 username、email、age 是否为空,然后通过 Lambda 表达式的方式构造查询条件,并将其作为参数传给 findAll 方法进行查询。在 Lambda 表达式中,我们使用 CriteriaBuilder 来构造查询条件,通过 root.get() 方法获取属性的值,然后使用 equal()like() 或者其他方法创建 Predicate 对象。最后,我们将所有的 Predicate 组合起来,返回结果。

5.2 使用 QueryDSL 进行复杂查询

QueryDSL 是一种流行的 Java 查询框架,它以编程方式构建类型安全的查询。Spring Data JPA 支持 QueryDSL,可以使用 QueryDSL 提供的 API 构建复杂的查询语句。

使用 QueryDSL 前,需要添加依赖和配置。首先,我们需要在 pom.xml 文件中添加以下依赖:

<dependency>
    <groupId>com.querydsl</groupId>
    <artifactId>querydsl-jpa</artifactId>
    <version>4.4.0</version>
</dependency>

<dependency>
    <groupId>com.querydsl</groupId>
    <artifactId>querydsl-apt</artifactId>
    <version>4.4.0</version>
    <scope>provided</scope>
</dependency>

然后,我们需要添加一个 Q 实体类,该类将用于构建 QueryDSL 查询语句。可使用官方提供的 Maven 插件生成 Q 类代码,示例代码如下:

<build>
    <plugins>
        <plugin>
            <groupId>com.mysema.maven</groupId>
            <artifactId>apt-maven-plugin</artifactId>
            <version>1.3.2</version>
            <executions>
                <execution>
                    <goals>
                        <goal>process</goal>
                    </goals>
                    <configuration>
                        <outputDirectory>target/generated-sources/java</outputDirectory>
                        <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>
@Entity
@Table(name = "user")
public class User {

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

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

    @Column(name = "age")
    private Integer age;

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

    // 省略 getter 和 setter 方法
}
@Generated("com.querydsl.codegen.EntitySerializer")
public class QUser extends EntityPathBase<User> {

    private static final long serialVersionUID = -777444987L;

    public static final QUser user = new QUser("user");

    public final NumberPath<Integer> age = createNumber("age", Integer.class);

    public final StringPath email = createString("email");

    public final NumberPath<Long> id = createNumber("id", Long.class);

    public final StringPath username = createString("username");

    public QUser(String variable) {
        super(User.class, forVariable(variable));
    }

    public QUser(Path<? extends User> path) {
        super(path.getType(), path.getMetadata());
    }

    public QUser(PathMetadata metadata) {
        super(User.class, metadata);
    }

}

现在我们就可以使用 QueryDSL 进行复杂查询了。以下是一个示例代码,用于查询年龄大于等于20岁并且邮箱包含 "example.com" 的用户列表:

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

}
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public List<User> findUsersByCondition(String email, Integer age) {
        QUser qUser = QUser.user;
        BooleanBuilder builder = new BooleanBuilder();
        if (StringUtils.isNotBlank(email)) {
            builder.and(qUser.email.endsWithIgnoreCase(email));
        }
        if (age != null) {
            builder.and(qUser.age.goe(age));
        }
        return (List<User>) userRepository.findAll(builder.getValue());
    }

}

在 UserServiceImpl 的 findUsersByCondition 方法中,我们首先创建了一个 QUser 对象,然后使用 BooleanBuilder 对象来构建查询条件,使用 endsWithIgnoreCase() 方法和 goe() 方法来构造查询条件。最后,我们将构建出来的查询条件传递给 findAll 方法进行查询。

5.3 使用 Native SQL 查询

在某些情况下,需要执行原生的 SQL 查询语句。Spring Data JPA 提供了 @Query 注解来支持使用原生 SQL 查询数据。在 @Query 注解中设置 nativeQuery=true 即可执行原生 SQL 语句。

以下示例代码演示了如何使用原生 SQL 查询 age 大于等于 18 的用户。

public interface UserRepository extends JpaRepository<User, Long> {
    @Query(value = "SELECT * FROM user WHERE age >= ?1", nativeQuery = true)
    List<User> findByAgeGreaterThanEqual(Integer age);
}

// 使用
userRepository.findByAgeGreaterThanEqual(18);
  • 35
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值