1. 概述
數據庫函數是數據庫管理系統中的關鍵組件,它們能夠封裝邏輯並將其執行限制在數據庫內部。 它們促進了高效的數據處理和操作。
在本教程中,我們將探討在 JPA 和 Spring Boot 應用程序中調用自定義數據庫函數的方法。
2. 項目設置
我們將後續章節中演示的概念使用 H2 數據庫。
讓我們在我們的 pom.xml 中包含 Spring Boot Data JPA 和 H2 依賴項:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>3.2.2</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.2.224</version>
</dependency>3. 數據庫函數
數據庫函數是數據庫對象,通過在數據庫中執行一組 SQL 語句或操作來執行特定任務。這在邏輯具有大量數據時可以提高性能。雖然數據庫函數和存儲過程在操作方式上相似,但它們之間存在差異。
3.1 函數與存儲過程
雖然不同的數據庫系統之間可能存在特定差異,但它們的主要區別可以總結如下表:
| 特性 | 數據庫函數 | 存儲過程 |
|---|---|---|
| 調用方式 | 可在查詢中調用 | 必須顯式調用 |
| 返回值 | 始終返回單個值 | 可能返回無、單個或多個值 |
| 參數 | 僅支持輸入參數 | 支持輸入和輸出參數 |
| 調用 | 不能使用函數調用存儲過程 | 可以使用存儲過程調用函數 |
| 用途 | 通常用於執行計算或數據轉換 | 常用於複雜的業務邏輯 |
3.2. H2 函數
為了説明如何從 JPA 調用數據庫函數,我們將創建一個 H2 數據庫函數,以演示如何從 JPA 調用它。H2 數據庫函數實際上是嵌入的 Java 源代碼,它將被編譯並執行。
CREATE ALIAS SHA256_HEX AS '
import java.sql.*;
@CODE
String getSha256Hex(Connection conn, String value) throws SQLException {
var sql = "SELECT RAWTOHEX(HASH(''SHA-256'', ?))";
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setString(1, value);
ResultSet rs = stmt.executeQuery();
if (rs.next()) {
return rs.getString(1);
}
}
return null;
}
';此數據庫函數 SHA256_HEX 接受一個字符串作為輸入參數,通過 SHA-256 散列算法進行處理,並隨後返回其 SHA-256 散列的十六進制表示。
4. 調用存儲過程
第一種方法是類似於存儲過程的數據庫函數在 JPA 中進行調用。 我們通過在實體類中添加 @NamedStoredProcedureQuery 註解來實現的。 此註解允許我們在實體類中直接指定存儲過程的元數據。
以下是包含定義的存儲過程 SHA256_HEX 的 Product 實體類示例:
@Entity
@Table(name = "product")
@NamedStoredProcedureQuery(
name = "Product.sha256Hex",
procedureName = "SHA256_HEX",
parameters = @StoredProcedureParameter(mode = ParameterMode.IN, name = "value", type = String.class)
)
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "product_id")
private Integer id;
private String name;
// constructor, getters and setters
}在實體類中,我們使用 Product 實體類並對其進行@NamedStoredProcedureQuery 註解。我們將 Product.sha256Hex 設置為命名存儲過程的名稱。
在我們的存儲庫定義中,我們使用@Procedure 註解存儲庫方法,並引用我們@NamedStoredProcedureQuery 的名稱。此存儲庫方法接受一個字符串參數,然後將其傳遞給數據庫函數,並返回數據庫函數調用的結果。
public interface ProductRepository extends JpaRepository<Product, Integer> {
@Procedure(name = "Product.sha256Hex")
String getSha256HexByNamed(@Param("value") String value);
}我們將會看到 Hibernate 像調用存儲過程一樣,在 Hibernate 日誌中執行時調用它:
Hibernate:
{call SHA256_HEX(?)}是主要用於調用存儲過程。 數據庫函數可以單獨調用,類似於存儲過程。但是,它可能不適合用於與 SELECT 查詢一起使用的數據庫函數。
5. 原生查詢
通過原生查詢調用數據庫函數的一種方法是使用原生查詢。 使用原生查詢調用數據庫函數有兩的不同方式。
5.1. 本地調用
從之前的 Hibernate 日誌中,我們可以看到 Hibernate 執行了一個 CALL 命令。同樣,我們可以使用相同的命令來調用我們的數據庫函數:
public interface ProductRepository extends JpaRepository<Product, Integer> {
@Query(value = "CALL SHA256_HEX(:value)", nativeQuery = true)
String getSha256HexByNativeCall(@Param("value") String value);
}執行結果將與我們使用 @NamedStoredProcedureQuery 的示例相同。
5.2. 原生選擇
如前所述,我們無法將其與選擇查詢結合使用。我們將將其轉換為選擇查詢,並將其應用於表中列的值。在我們的示例中,我們定義了一個倉庫方法,它使用原生選擇查詢來調用數據庫函數,該函數應用於 name 列(即 Product 表)的值:
public interface ProductRepository extends JpaRepository<Product, Integer> {
@Query(value = "SELECT SHA256_HEX(name) FROM product", nativeQuery = true)
String getProductNameListInSha256HexByNativeSelect();
}在執行後,我們可以獲得與我們定義的完全相同的查詢,因為我們將其定義為原生查詢。
Hibernate:
SELECT
SHA256_HEX(name)
FROM
product6. 函數註冊
函數註冊是 Hibernate 中定義和註冊自定義數據庫函數的過程,這些函數可用於 JPA 或 Hibernate 查詢中。這有助於 Hibernate 將自定義函數翻譯成相應的 SQL 語句。
6.1. 自定義方言
通過創建自定義方言,我們可以註冊自定義函數。 以下是擴展默認的 H2Dialect 類並註冊我們自定義函數的自定義方言類:
public class CustomH2Dialect extends H2Dialect {
@Override
public void initializeFunctionRegistry(FunctionContributions functionContributions) {
super.initializeFunctionRegistry(functionContributions);
SqmFunctionRegistry registry = functionContributions.getFunctionRegistry();
TypeConfiguration types = functionContributions.getTypeConfiguration();
new PatternFunctionDescriptorBuilder(registry, "sha256hex", FunctionKind.NORMAL, "SHA256_HEX(?1)")
.setExactArgumentCount(1)
.setInvariantType(types.getBasicTypeForJavaType(String.class))
.register();
}
}當 Hibernate 初始化一個方言時,它會通過 initializeFunctionRegistry() 方法將可用的數據庫函數註冊到函數註冊表中。 我們通過覆蓋 initializeFunctionRegistry() 方法來註冊默認方言未包含的額外數據庫函數。
PatternFunctionDescriptorBuilder 創建一個 JPQL 函數映射,將我們的數據庫函數 SHA256_HEX 映射到 JPQL 函數 sha256Hex,並將映射註冊到函數註冊表中。參數 ?1 表示數據庫函數的第一個輸入參數。
6.2. Hibernate 配置
我們需要指示 Spring Boot 採用 CustomH2Dialect 而不是默認的 H2Dialect。這裏使用了 HibernatePropertiesCustomizer,這是一個 Spring Boot 提供的接口,用於自定義 Hibernate 使用的屬性。
我們通過覆蓋 customize() 方法來添加額外的屬性,表明我們將使用 CustomH2Dialect。
@Configuration
public class CustomHibernateConfig implements HibernatePropertiesCustomizer {
@Override
public void customize(Map<String, Object> hibernateProperties) {
hibernateProperties.put("hibernate.dialect", "com.baeldung.customfunc.CustomH2Dialect");
}
}Spring Boot 在應用程序啓動時會自動檢測並應用自定義配置。
6.3. 倉庫方法
在我們的倉庫中,現在可以使用將新定義的函數 sha256Hex 替代原生查詢的 JPQL 查詢:
public interface ProductRepository extends JpaRepository<Product, Integer> {
@Query(value = "SELECT sha256Hex(p.name) FROM Product p")
List<String> getProductNameListInSha256Hex();
}當我們執行後檢查Hibernate日誌時,我們看到Hibernate正確地將JPQL的sha256Hex函數翻譯為我們數據庫中的函數SHA256_HEX:
Hibernate:
select
SHA256_HEX(p1_0.name)
from
product p1_07'7. 結論
在本文中,我們對數據庫函數和存儲過程進行了簡要比較。兩者都提供了一種強大的方式來封裝邏輯並執行在數據庫中。
此外,我們還探討了調用數據庫函數的不同方法,包括利用 <em @NamedStoredProcedureQuery</em> 註解、原生查詢以及通過自定義方言註冊函數。通過將數據庫函數整合到存儲庫方法中,我們可以輕鬆構建數據庫驅動的應用程序。