51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

在 JPA 和 Sping Data JPA 中使用 Java Record

1、概览 {#1概览}

在本教程中,我们将探讨如何在 JPA 中使用 Java Record,包括以下内容。

  1. 为什么 Record 不能作为 Entity 使用。
  2. 在 JPA 中使用 Record。
  3. 在 Spring Boot 应用中使用 Spring Data JPA 和 Record。

2、Record 和 Enttiy {#2record-和-enttiy}

Record 是不可变的,用于存储数据。它们包含字段、全参数构造函数、getter、toStringequals/hashCode 方法。由于它们是不可变的,因此没有 setter。由于其语法简洁,在 Java 中经常被用作数据传输对象(DTO)。

Entity(实体)是映射到数据库表的类。它们用于表示数据库中的条目。它们的字段被映射到数据库表中的列。

2.1、Record 不能作为 Entity {#21record-不能作为-entity}

实体由 JPA provider 处理。JPA provider 负责创建数据库表,将实体映射到表,并将实体持久化到数据库。在流行的 JPA provider(如 Hibernate)中,实体是使用代理来创建和管理的。

代理是在运行时生成并继承实体类的类。这些代理依赖于实体类的无参数构造函数和 setter。由于 Record 不具有这些,所以它们不能用作实体。

2.2、在 JPA 中使用 Record 的其他方法 {#22在-jpa-中使用-record-的其他方法}

由于在 Java 中使用 Record 的简便性和安全性,在 JPA 中以其他方式使用 Record 可能是有益的。

在 JPA中,我们可以通过以下方式使用 Record:

  • 将查询结果转换为 Record。
  • 使用 Record 作为DTO在各个层之间传输数据。
  • 将实体转换为 Record。

3、项目设置 {#3项目设置}

我们将使用 Spring Boot 创建一个使用 JPA 和 Spring Data JPA 的简单应用程序。然后,我们将了解在与数据库交互时使用 Record 的几种方法。

3.1、依赖 {#31依赖}

首先在项目中添加 Spring Data JPA 依赖:

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

除了 Spring Data JPA,我们还需要配置一个数据库。我们可以使用任何SQL数据库。例如,我们可以使用H2内存数据库。

3.2、Entity 和 Record {#32entity-和-record}

创建一个用于与数据库交互的实体 Book,该实体将映射到数据库中的 book 表:

@Entity
@Table(name = "book")
public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String title;
    private String author;
    private String isbn;
    
    // constructors, getters, setters
}

创建一个与 Book 实体相对应的 Record:

public record BookRecord(Long id, String title, String author, String isbn) {}

接下来,我们将了解在应用中使用 Record 而不是实体的几种方法。

4、在 JPA 中使用 Record {#4在-jpa-中使用-record}

JPA API 提供了一些与数据库交互的方法,在这些方法中可以使用 Record。让我们来看看其中的几种。

4.1、Criteria Builder {#41criteria-builder}

先来看看如何在 CriteriaBuilder 中使用 Record。创建一个查询,返回数据库中的所有 book:

public class QueryService {
    @PersistenceContext
    private EntityManager entityManager;
    
    public List<BookRecord> findAllBooks() {
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<BookRecord> query = cb.createQuery(BookRecord.class);
        Root<Book> root = query.from(Book.class);
        query.select(cb.construct(BookRecord.class, root.get("id"), root.get("title"), root.get("author"), root.get("isbn")));
        return entityManager.createQuery(query).getResultList();
    }
}

在上面的代码中,使用 CriteriaBuilder 创建一个 CriteriaQuery,返回一个 BookRecord

让我们来看看上述代码中的一些步骤:

  • 使用 CriteriaBuilder.createQuery() 方法创建 CriteriaQuery。将想要返回的 record 类作为参数传递给 CriteriaBuilder.createQuery() 方法。
  • 然后,使用 CriteriaQuery.from() 方法创建一个 Root。将实体类作为参数传递。这就是我们指定要查询的表的方法。
  • 然后,使用 CriteriaQuery.select() 方法指定一个 select 子句。我们使用 CriteriaBuilder.construct() 方法将查询结果转换为 record。将 record 的类和实体的字段作为参数传递给记录构造函数
  • 最后,使用 EntityManager.createQuery() 方法从 CriteriaQuery 创建一个 TypedQuery。然后,使用 TypedQuery.getResultList() 方法获取查询结果。

这将创建一个 select 查询来获取数据库中的所有 book。然后使用 construct() 方法将每个结果转换为 BookRecord,并在调用 getResultList() 方法时返回 Record List 而不是实体 List。

通过这种方式,我们可以使用实体类创建查询,但在应用程序的其余部分使用 Record。

4.2、Typed Query {#42typed-query}

CriteriaBuilder 类似,我们可以使用类型查询来返回 record 而不是实体。在 QueryService 中添加一个方法,使用类型查询获取单条 book 记录:

public BookRecord findBookById(Long id) {
    TypedQuery<BookRecord> query = entityManager
      .createQuery("SELECT new com.baeldung.jpa.records.BookRecord(b.id, b.title, b.author, b.isbn) FROM Book b WHERE b.id = :id",
        BookRecord.class);
    query.setParameter("id", id);
    return query.getSingleResult();
}

TypedQuery 允许将查询结果转换为任何类型,只要该类型有一个构造函数,且该构造函数的参数数量与查询结果相同。

在上面的代码中,使用 EntityManager.createQuery() 方法创建一个 TypedQuery。传递查询字符串和 record 类作为参数。然后,使用 TypedQuery.setParameter() 方法设置查询参数。最后,使用 TypedQuery.getSingleResult() 方法获取查询结果,该结果将是一个 BookRecord 对象。

4.3、原生查询 {#43原生查询}

我们也可以使用原生查询来获取查询结果,封装为 record。然而,原生查询不允许我们将结果转换为任何类型。相反,我们需要使用映射将结果转换为 record。首先,让我们在实体中定义一个映射:

@SqlResultSetMapping(
  name = "BookRecordMapping",
  classes = @ConstructorResult(
    targetClass = BookRecord.class,
    columns = {
      @ColumnResult(name = "id", type = Long.class),
      @ColumnResult(name = "title", type = String.class),
      @ColumnResult(name = "author", type = String.class),
      @ColumnResult(name = "isbn", type = String.class)
    }
  )
)
@Entity
@Table(name = "book")
public class Book {
    // ...
}

映射的工作原理如下:

  • @SqlResultSetMapping 注解的 name 属性指定了映射的名称。
  • @ConstructorResult 注解指定要使用 record 的构造函数来转换结果。
  • @ConstructorResult 注解的 targetClass 属性指定了 record 类。
  • @ColumnResult 注解指定了列的名称和类型。这些列值将传递给 record 的构造函数。

然后,我们可以在原生查询中使用该映射,以 record 形式获取结果:

public List<BookRecord> findAllBooksUsingMapping() {
    Query query = entityManager.createNativeQuery("SELECT * FROM book", "BookRecordMapping");
    return query.getResultList();
}

这将创建一个原生查询,返回数据库中的所有 book 记录。当调用 getResultList() 方法时,它将使用映射将结果转换为 BookRecord,并返回 record List 而不是实体 List。

5、在 Spring Data JPA 中使用 Record {#5在-spring-data-jpa-中使用-record}

Spring Data JPA 为 JPA API 提供了一些改进。它使我们能够以几种方式在 Spring Data JPA Repository 中使用 Record。让我们看看如何在 Spring Data JPA Repository 中使用 Record。

5.1、自动映射实体到Record {#51自动映射实体到record}

Spring Data Repository 允许使用 Record 作为存 Repository 中方法的返回类型。这将自动映射实体到 record。这只有在 record 的字段与实体完全相同时才可能实现。让我们来看一个示例:

public interface BookRepository extends JpaRepository<Book, Long> {
    List<BookRecord> findBookByAuthor(String author);
}

由于 BookRecordBook 实体具有相同的字段,所以当我们调用 findBookByAuthor() 方法时,Spring Data JPA 将自动映射实体到 record,并返回 record List 而不是实体 List。

5.2、在 @Query 中使用 Record {#52在-query-中使用-record}

TypedQuery 类似,可以在 Spring Data JPA Repository 的 @Query 查询方法中使用 Record。示例如下:

public interface BookRepository extends JpaRepository<Book, Long> {
    @Query("SELECT new com.baeldung.jpa.records.BookRecord(b.id, b.title, b.author, b.isbn) FROM Book b WHERE b.id = :id")
    BookRecord findBookById(@Param("id") Long id);
}

当调用 findBookById() 方法时,Spring Data JPA 将自动把查询结果转换成 BookRecord,并返回 record 而不是实体。

5.3、自定义 Repository 实现 {#53自定义-repository-实现}

如果不能选择自动映射,我们也可以定义一个自定义 repository 实现,以定义自己的映射。让我们从创建一个 CustomBookRecord 类开始,该类将作为 repository 中方法的返回类型:

public record CustomBookRecord(Long id, String title) {}

注意,CustomBookRecord 类没有与 Book 实体的字段完全相同。它只有 idtitle 字段。

然后,可以创建一个使用 CustomBookRecord 类的自定义 repository 实现:

public interface CustomBookRepository {
    List<CustomBookRecord> findAllBooks();
}

在 repository 的实现中,我们可以定义用于将查询结果映射到 CustomBookRecord 类的方法:

@Repository
public class CustomBookRepositoryImpl implements CustomBookRepository {
    private final JdbcTemplate jdbcTemplate;

    public CustomBookRepositoryImpl(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public List<CustomBookRecord> findAllBooks() {
        return jdbcTemplate.query("SELECT id, title FROM book", (rs, rowNum) -> new CustomBookRecord(rs.getLong("id"), rs.getString("title")));
    }
}

在上面的代码中,使用 JdbcTemplate.query() 方法来执行查询,并使用 RowMapper 接口实现的 lambda 表达式将结果映射到 CustomBookRecord 中。

6、总结 {#6总结}

在本文中,我们了解了如何在 JPA 和 Spring Data JPA 中使用 Record。以及如何在 CriteriaBuilderTypedQuery 和原生查询的 JPA API 中使用 Record。我们还了解了如何使用自动映射、自定义查询和自定义 repository 实现在Spring Data JPA repository 中使用 Record。


参考: https://www.baeldung.com/spring-jpa-java-records

赞(4)
未经允许不得转载:工具盒子 » 在 JPA 和 Sping Data JPA 中使用 Java Record