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);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/298119.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

leetcode经典【双指针】例题

删除有序数组中的重复项&#xff1a; https://leetcode.cn/problems/remove-duplicates-from-sorted-array/ 解题思路&#xff1a; 首先注意数组是有序的&#xff0c;那么重复的元素一定会相邻。 注: 要求删除重复元素&#xff0c;实际上就是将不重复的元素移到数组的左侧。 考…

18.标题统计

题目 import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner sc new Scanner(System.in);String str sc.nextLine();int res 0;for(int i0;i<str.length();i) {char c str.charAt(i);if(c! && c!\n) {res;}}System.o…

BUUCTF--pwnable_start1

查看保护&#xff1a; 32位程序保护全没开&#xff0c;黑盒测试下效果&#xff1a; 存在栈溢出&#xff0c;那么这题的想法就是直接ret2shellcode了。IDA中看看具体流程&#xff1a; 出奇的少&#xff0c;这题不能看反汇编的代码&#xff0c;直接去看汇编&#xff1a; 主要就2个…

sql——窗口范围之partition by 与 order by

partition by 关键字 partition by 在开窗函数中&#xff0c;常用于表示某个分区&#xff0c;规则了数据的范围 order by 关键字 order by 常用于对分区内的数据进行排序&#xff0c;常见的情况下&#xff0c;order by还能规定sql语句的影响范围。 rows between unbounded …

kannegiesser触摸屏维修CTT-11 4PP420.1043-K37

贝加莱触摸屏维修4PP420.1043-K37 kannegiesser工控机触摸屏维修CTT-11 工控机触摸屏维修常见故障现象 1、工控机开机有显示&#xff0c;但是屏幕很暗&#xff0c;用调亮度功能键调试无任何变化&#xff1b; 2、工控机开机触摸屏白屏或花屏&#xff0c;但是外接显示器正常&a…

机器学习(四) -- 模型评估(3)

系列文章目录 机器学习&#xff08;一&#xff09; -- 概述 机器学习&#xff08;二&#xff09; -- 数据预处理&#xff08;1-3&#xff09; 机器学习&#xff08;三&#xff09; -- 特征工程&#xff08;1-2&#xff09; 机器学习&#xff08;四&#xff09; -- 模型评估…

【JAVA】volatile 关键字的作用

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a; JAVA ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 正文 volatile 的作用&#xff1a; 结语 我的其他博客 前言 在多线程编程中&#xff0c;保障数据的一致性和线程之间的可见性是…

优化|PLSA理论与实践

PLSA又称为概率潜在语义分析&#xff0c;是一种利用概率生成模型对文本集合进行话题分析的无监督学习方法。该模型最大的特点是加入了主题这一隐变量&#xff0c;文本生成主题&#xff0c;主题生成单词&#xff0c;从而得到单词-文本共现矩阵。本文将对包含物理学、计算机科学、…

嵌入式(五)通信协议 | 串行异步同步 UART SPI I2C 全解析

文章目录 0 串口通信协议1 通用异步收发传输器 UART1.1 串口配置1.2 串口初始化1.3 串口发送和接收方式1.3.1 轮询方式发送1.3.2 中断方式发送1.3.3 查询方式接收1.3.4 中断方式接收 2 串行外设接口 SPI2.1 标准的四线SPI接口2.2 SPI的四种模式2.3 配置2.4 发送和接收Master向S…

[python]gym安装报错ERROR: Failed building wheel for box2d-py

报错截图&#xff1a; box2d是一个游戏领域的2D图形C引擎&#xff0c;用来模拟2D刚体物体运动和碰撞。 swig是一个将c/c代码封装为Python库的工具&#xff08;是Python调用c/c库的一种常见手段&#xff09;&#xff0c;所以在运行时box2d会依赖到swig。而swig并不是一个python库…

C#,简单选择排序算法(Simple Select Sort)的源代码与数据可视化

排序算法是编程的基础。 常见的四种排序算法是&#xff1a;简单选择排序、冒泡排序、插入排序和快速排序。其中的快速排序的优势明显&#xff0c;一般使用递归方式实现&#xff0c;但遇到数据量大的情况则无法适用。实际工程中一般使用“非递归”方式实现。本文搜集发布四种算法…

港口车路协同系统方案

目前&#xff0c;国内自动驾驶应用的两种主流路线是单车智能、单车智能V2X。国内多数港口仍采用4G通信技术&#xff0c;单车智能在港口应用的稳定性较差&#xff0c;比如可能受到金属集装箱干扰及移动通信速率不稳定的影响。单车智能V2X将降低对通信速率的要求&#xff0c;可以…

【BCC动态跟踪PostgreSQL】

BPF Compiler Collection (BCC)是基于eBPF的Linux内核分析、跟踪、网络监控工具。其源码存放于GitCode - 开发者的代码家园 想要监控PostgreSQL数据库的相关SQL需要在编译PostgreSQL的时候开启dtrace。下文主要介绍几个和PostgreSQL相关的工具,其他工具可根据需求自行了解。 …

移动通信原理与关键技术学习(第四代蜂窝移动通信系统)

前言&#xff1a;LTE 标准于2008 年底完成了第一个版本3GPP Release 8的制定工作。另一方面&#xff0c;ITU 于2007 年召开了世界无线电会议WRC07&#xff0c;开始了B3G 频谱的分配&#xff0c;并于2008 年完成了IMT-2000&#xff08;即3G&#xff09;系统的演进——IMT-Advanc…

Leetcode 剑指 Offer II 060. 前 K 个高频元素

题目难度: 中等 原题链接 今天继续更新 Leetcode 的剑指 Offer&#xff08;专项突击版&#xff09;系列, 大家在公众号 算法精选 里回复 剑指offer2 就能看到该系列当前连载的所有文章了, 记得关注哦~ 题目描述 给定一个整数数组 nums 和一个整数 k &#xff0c;请返回其中出现…

缘分的计算

题目描述&#xff1a; 缘分是一个外国人难以理解的中文名词。大致说来&#xff0c;缘分是一种冥冥中将两人&#xff08;通常是情人&#xff09;结合的力量。仅管这是种迷信&#xff0c;很多人——特别是女生——喜欢去计算它。 不幸的是&#xff0c;644 也是这样。有天&#x…

【linux笔记】top、ps

【linux笔记】top命令 top&#xff08;Table of process&#xff09;是动态变化的。而ps是静态的。 PID — 进程id USER — 进程所有者 PR — 进程优先级 NI — nice值。负值表示高优先级&#xff0c;正值表示低优先级 VIRT — 进程使用的虚拟内存总量&#xff0c;单位kb。VI…

二叉树的最大深度,力扣

目录 题目地址&#xff1a; 题目&#xff1a; 我们直接看题解吧&#xff1a; 快速理解题解小建议&#xff1a; 审题目事例提示&#xff1a; 解题方法&#xff1a; 解题方法分析&#xff1a; 方法1后序遍历&#xff08;DFS&#xff09; 解题分析&#xff1a; 解题思路&#xff1…

启动 Mac 时显示闪烁的问号

启动 Mac 时显示闪烁的问号 如果启动时在 Mac 屏幕上看到闪烁的问号&#xff0c;这意味着你的 Mac 无法找到自身的系统软件。 如果 Mac 启动时出现闪烁的问号且无法继续启动&#xff0c;请尝试以下步骤。 1.通过按住其电源按钮几秒钟来关闭 Mac。 2.按一下电源按钮&#xf…

强化学习5——动态规划初探

动态规划具体指的是在某些复杂问题中&#xff0c;将问题转化为若干个子问题&#xff0c;并在求解每个子问题的过程中保存已经求解的结果&#xff0c;以便后续使用。实际上动态规划更像是一种通用的思路&#xff0c;而不是具体某个算法。 在强化学习中&#xff0c;被用于求解值函…
最新文章