1、概览 {#1概览}
在本教程中,我们将探讨如何在 JPA 中使用 Java Record,包括以下内容。
- 为什么 Record 不能作为 Entity 使用。
- 在 JPA 中使用 Record。
- 在 Spring Boot 应用中使用 Spring Data JPA 和 Record。
2、Record 和 Enttiy {#2record-和-enttiy}
Record
是不可变的,用于存储数据。它们包含字段、全参数构造函数、getter、toString
和 equals/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);
}
由于 BookRecord
与 Book
实体具有相同的字段,所以当我们调用 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
实体的字段完全相同。它只有 id
和 title
字段。
然后,可以创建一个使用 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。以及如何在 CriteriaBuilder
、TypedQuery
和原生查询的 JPA API 中使用 Record。我们还了解了如何使用自动映射、自定义查询和自定义 repository 实现在Spring Data JPA repository 中使用 Record。
参考: https://www.baeldung.com/spring-jpa-java-records