Spring Data JPA入门到放弃

2024-01-09 17:04:29

参考文档: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);

文章来源:https://blog.csdn.net/weixin_51474578/article/details/135364139
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。