將 Java 記錄與 JPA 結合使用
一、概述
在本教程中,我們將探討如何將 Java Records 與 JPA 結合使用。我們將從探索為什麼不能在實體中使用記錄開始。
然後,我們將了解如何通過 JPA 使用記錄。我們還將了解如何在 Spring Boot 應用程序中使用帶有 Spring Data JPA 的記錄。
2. 記錄與實體
記錄是不可變的,用於存儲數據。它們包含字段、全參數構造函數、getter、 toString,
和equals/hashCode
方法。因為它們是不可變的,所以它們沒有設置器。由於其簡潔的語法,它們通常用作 Java 應用程序中的數據傳輸對象 (DTO)。
實體是映射到數據庫表的類。它們用於表示數據庫中的條目。它們的字段映射到數據庫表中的列。
2.1.記錄不能是實體
實體由JPA 提供程序處理。 JPA 提供者負責創建數據庫表,將實體映射到表,並將實體持久保存到數據庫。在 Hibernate 等流行的 JPA 提供程序中,實體是使用代理創建和管理的。
代理是在運行時生成並擴展實體類的類。這些代理依賴於實體類具有無參數構造函數和設置器。由於記錄沒有這些,因此它們不能用作實體。
2.2.在 JPA 中使用記錄的其他方式
由於在 Java 應用程序中使用記錄的簡便性和安全性,以其他方式將它們與 JPA 一起使用可能會有所幫助。
在JPA中,我們可以通過以下方式使用記錄:
- 將查詢結果轉換為記錄
- 使用記錄作為 DTO 在層之間傳輸數據
- 將實體轉換為記錄。
3.項目設置
我們將使用 Spring Boot 創建一個使用 JPA 和 Spring Data JPA 的簡單應用程序。然後我們將看看在與數據庫交互時使用記錄的幾種方法。
3.1.依賴關係
讓我們首先將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.實體和記錄
讓我們創建一個我們將用來與數據庫交互的實體。我們將創建一個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
實體的記錄:
public record BookRecord(Long id, String title, String author, String isbn) {}
接下來,我們將看看在我們的應用程序中使用記錄而不是實體的幾種方法。
4. 在 JPA 中使用記錄
JPA API 提供了幾種與可以使用記錄的數據庫進行交互的方法。讓我們來看看其中的幾個。
4.1.條件生成器
讓我們首先看看如何通過CriteriaBuilder
使用記錄。我們將進行一個查詢,返回數據庫中的所有書籍:
`public class QueryService {
@PersistenceContext
private EntityManager entityManager;
public List
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery
Root
query.select(cb.construct(BookRecord.class, root.get("id"), root.get("title"), root.get("author"), root.get("isbn")));
return entityManager.createQuery(query).getResultList();
}
}`
在上面的代碼中,我們使用CriteriaBuilder
創建一個返回BookRecord
CriteriaQuery
。
讓我們看一下上面代碼中的一些步驟:
- 我們使用
CriteriaBuilder.createQuery()
方法創建一個CriteriaQuery
。我們將要返回的記錄的類作為參數傳遞 - 然後我們使用
CriteriaQuery.from()
方法創建一個Root
。我們將實體類作為參數傳遞。這就是我們指定要查詢的表的方式 - 然後,我們使用
CriteriaQuery.select()
方法來指定一個選擇子句。我們使用CriteriaBuilder.construct()
方法將查詢結果轉換為記錄。我們將記錄的類和我們要傳遞給記錄構造函數的實體的字段作為參數 - 最後,我們使用
EntityManager.createQuery()
方法從CriteriaQuery
創建一個TypedQuery
。然後我們使用TypedQuery.getResultList()
方法來獲取查詢的結果
這將創建一個選擇查詢以獲取數據庫中的所有書籍。然後它將使用construct()
方法將每個結果轉換為BookRecord
,並在我們調用getResultList()
方法時返回記錄列表而不是實體列表。
這樣,我們可以使用實體類來創建查詢,而對應用程序的其餘部分使用記錄。
4.2.類型查詢
與CriteriaBuilder
類似,我們可以使用類型化查詢來返回記錄而不是實體。讓我們在QueryService
中添加一個方法,以使用類型化查詢獲取單本書作為記錄:
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
。我們將查詢字符串和記錄的類作為參數傳遞。然後,我們使用TypedQuery.setParameter()
方法來設置查詢的參數。最後,我們使用TypedQuery.getSingleResult()
方法獲取查詢結果,這將是一個BookRecord
對象。
4.3.本機查詢
我們還可以使用本機查詢來獲取查詢結果作為記錄。但是,本機查詢不允許我們將結果轉換為任何類型。相反,我們需要使用映射將結果轉換為記錄。首先,讓我們在我們的實體中定義一個映射:
@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
註解指定我們要使用記錄的構造函數來轉換結果。 -
@ConstructorResult
註釋的targetClass
屬性指定記錄的類。 -
@ColumnResult
註釋指定列名和列的類型。這些列值將傳遞給記錄的構造函數。
然後我們可以在我們的本地查詢中使用這個映射來獲取結果作為記錄:
public List<BookRecord> findAllBooksUsingMapping() { Query query = entityManager.createNativeQuery("SELECT * FROM book", "BookRecordMapping"); return query.getResultList(); }
這將創建一個返回數據庫中所有書籍的本機查詢。當我們調用getResultList()
方法時,它將使用映射將結果轉換為BookRecord
並返回記錄列表而不是實體列表。
5. 將記錄與 Spring Data JPA 結合使用
Spring Data JPA 對 JPA API 進行了一些改進。它使我們能夠以幾種方式將記錄與 Spring Data JPA 存儲庫一起使用。讓我們看看如何將記錄與 Spring Data JPA 存儲庫一起使用。
5.1.從實體到記錄的自動映射
Spring Data Repositories 允許我們使用記錄作為存儲庫中方法的返回類型。這將自動將實體映射到記錄。這只有在記錄具有與實體完全相同的字段時才有可能。讓我們看一個例子:
public interface BookRepository extends JpaRepository<Book, Long> { List<BookRecord> findBookByAuthor(String author); }
由於BookRecord
與Book
實體具有相同的字段,因此當我們調用findBookByAuthor()
方法時,Spring Data JPA 會自動將實體映射到記錄並返回記錄列表而不是實體列表。
5.2.將記錄與@Query 一起使用
與TypedQuery
類似,我們可以在 Spring Data JPA 存儲庫中使用帶有@Query
註釋的記錄。讓我們看一個例子:
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
並返回單個記錄而不是實體。
5.3.自定義存儲庫實現
如果自動映射不是一個選項,我們還可以定義一個自定義存儲庫實現,允許我們定義自己的映射。讓我們從創建一個CustomBookRecord
類開始,該類將用作存儲庫中方法的返回類型:
public record CustomBookRecord(Long id, String title) {}
請注意, CustomBookRecord
類沒有與Book
實體相同的字段。它只有id
和title
字段。
然後,我們可以創建一個將使用CustomBookRecord
類的自定義存儲庫實現:
public interface CustomBookRepository { List<CustomBookRecord> findAllBooks(); }
在存儲庫的實現中,我們可以定義用於將查詢結果映射到CustomBookRecord
類的方法:
`@Repository
public class CustomBookRepositoryImpl implements CustomBookRepository {
private final JdbcTemplate jdbcTemplate;
public CustomBookRepositoryImpl(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public List
return jdbcTemplate.query("SELECT id, title FROM book", (rs, rowNum) -> new CustomBookRecord(rs.getLong("id"), rs.getString("title")));
}
}`
在上面的代碼中,我們使用JdbcTemplate.query()
方法執行查詢,並使用 lambda 表達式將結果映射到CustomBookRecord
中,該表達式是RowMapper
接口的實現。
六,結論
在本文中,我們了解瞭如何通過 JPA 和 Spring Data JPA 使用記錄。我們已經了解瞭如何使用CriteriaBuilder
、 TypedQuery
和本機查詢將記錄與 JPA API 一起使用。我們還了解瞭如何使用自動映射、自定義查詢和自定義存儲庫實現將記錄與 Spring Data JPA 存儲庫一起使用。
本文的代碼示例可在 GitHub 上找到。