在 Spring Data JPA 中尋找最大值
一、簡介
使用 Spring Data JPA 時,在資料庫中尋找特定值是一項常見任務。其中一項任務是尋找特定列中的最大值。
在本教程中,我們將探索使用 Spring Data JPA 實現此目的的幾種方法。我們將檢查如何使用儲存庫方法、JPQL 和本機查詢以及 Criteria API 來尋找資料庫列中的最大值。
2. 實體範例
在繼續之前,我們必須在專案中新增所需的[spring-boot-starter-data-jpa](https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-jpa)依賴項:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
之後,讓我們定義一個簡單的實體來使用:
@Entity
public class Employee {
@Id
@GeneratedValue
private Integer id;
private String name;
private Long salary;
// constructors, getters, setters, equals, hashcode
}
在以下範例中,我們將使用不同的方法來尋找所有員工的salary列的最大值。
3. 在儲存庫中使用派生查詢
Spring Data JPA 提供了一種強大的機制來使用儲存庫方法定義自訂查詢。其中一個機制是派生查詢,它允許我們透過宣告方法名稱來實作 SQL 查詢。
讓我們為Employee類別建立一個儲存庫:
public interface EmployeeRepository extends JpaRepository<Employee, Integer> {
Optional<Employee> findTopByOrderBySalaryDesc();
}
我們剛剛實作了一種使用查詢派生機制來產生適當 SQL 的方法。根據方法名稱,我們將所有員工依salary降序排列,然後傳回第一個,也就是salary最高的員工。
值得注意的是,這種方法總是傳回一個設定了所有急切屬性的實體。但是,如果我們只想檢索單一salary值,我們可以透過實作投影功能來稍微修改我們的程式碼。
讓我們建立一個附加介面並修改儲存庫:
public interface EmployeeSalary {
Long getSalary();
}
public interface EmployeeRepository extends JpaRepository<Employee, Integer> {
Optional<EmployeeSalary> findTopSalaryByOrderBySalaryDesc();
}
當我們只需要返回實體的特定部分時,此解決方案非常有用。
4.使用JPQL
另一種簡單的方法是使用@Query註釋。這讓我們可以直接在儲存庫介面中定義自訂JPQL (Java 持久性查詢語言)查詢。
讓我們實作 JQPL 查詢來檢索最高salary值:
public interface EmployeeRepository extends JpaRepository<Employee, Integer> {
@Query("SELECT MAX(e.salary) FROM Employee e")
Optional<Long> findTopSalaryJQPL();
}
和以前一樣,該方法會傳回所有員工中最高的salary值。此外,我們可以輕鬆檢索實體的單列,而無需任何額外的投影。
5. 使用本機查詢
我們剛剛在我們的存儲庫中引入了@Query註釋。這種方法還允許我們直接使用本機查詢來編寫原始 SQL 。
為了達到相同的結果,我們可以實現:
public interface EmployeeRepository extends JpaRepository<Employee, Integer> {
@Query(value = "SELECT MAX(salary) FROM Employee", nativeQuery = true)
Optional<Long> findTopSalaryNative();
}
解決方案與JPQL類似。使用本機查詢對於利用特定的 SQL 功能或最佳化非常有用。
6. 實作預設儲存庫方法
我們也可以使用自訂 Java 程式碼來尋找最大值。讓我們實作另一個解決方案而不添加額外的查詢:
public interface EmployeeRepository extends JpaRepository<Employee, Integer> {
default Optional<Long> findTopSalaryCustomMethod() {
return findAll().stream()
.map(Employee::getSalary)
.max(Comparator.naturalOrder());
}
}
我們透過使用自訂邏輯來新增新的預設方法來擴展我們的儲存庫。我們使用內建的findAll()方法來檢索所有Employee實體,然後對它們進行串流處理並找到最高salary 。與先前的方法不同,所有過濾邏輯都發生在應用程式層,而不是資料庫。
7. 使用分頁
Spring Data JPA 提供對分頁和排序功能的支援。我們仍然可以使用它們來找到員工的最高salary 。
即使不實現任何專用查詢或擴充儲存庫,我們也可以實現我們的目標:
public Optional<Long> findTopSalary() {
return findAll(PageRequest.of(0, 1, Sort.by(Sort.Direction.DESC, "salary")))
.stream()
.map(Employee::getSalary)
.findFirst();
}
眾所周知, PagingAndSortingRepository介面為Pageable和Sort類型提供了額外的支援。因此,我們在JpaRepository內建的findAll()方法也可以接受這些參數。我們只是實作了一種不同的方法,而沒有在儲存庫中添加其他方法。
8. 使用條件 API
Spring Data JPA 也提供了 Criteria API——一種更程式化的建構查詢的方法。這是一種無需使用原始 SQL 即可建立複雜查詢的更動態且類型安全的方法。
首先,我們將EntityManager bean 注入到我們的服務中,然後建立一個使用 Criteria API 尋找最高salary方法:
@Service
public class EmployeeMaxValueService {
@Autowired
private EntityManager entityManager;
public Optional<Long> findMaxSalaryCriteriaAPI() {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Long> query = cb.createQuery(Long.class);
Root<Employee> root = query.from(Employee.class);
query.select(cb.max(root.get("salary")));
TypedQuery<Long> typedQuery = entityManager.createQuery(query);
return Optional.ofNullable(typedQuery.getSingleResult());
}
}
在此方法中,我們首先從注入的EntityManager bean 取得CriteriaBuilder實例。然後,我們建立一個CriteriaBuilder來指定結果類型,並建立一個Root來定義FROM子句。最後,我們選擇salary欄位的最大值並執行查詢。
我們再次檢索了所有員工的最高salary 。然而,這種方法比前一種方法更複雜,所以如果我們需要實作一個簡單的查詢,可能會有點不知所措。如果我們有更複雜的結構,無法透過簡單地擴展儲存庫來處理,那麼此解決方案可能會很有用。
9. 結論
在本文中,我們探索了使用 Spring Data JPA 查找列的最大值的各種方法。
我們從派生查詢開始,它提供了一種簡單直觀的方法來僅透過方法命名約定來定義查詢。然後,我們研究了使用JPQL 和帶有@Query註釋的本機查詢,為正在執行的 SQL 提供更大的靈活性和直接控制。
我們還在儲存庫中實作了自訂預設方法,以利用 Java 的 Stream API 在應用程式層級處理資料。此外,我們還檢查如何使用分頁和排序來僅使用內建 API 來尋找結果。
最後,我們利用Criteria API 以更程式化和類型安全的方法來建構複雜查詢。透過了解這些不同的方法,我們可以為特定用例選擇最合適的方法,平衡簡單性、控制和效能。
本教學課程使用的完整原始碼可在 GitHub 上取得。