From 1b843fd34a24fef67979bcd1e84667f50a7af236 Mon Sep 17 00:00:00 2001
From: Manfred <77407079+manfred106@users.noreply.github.com>
Date: Wed, 21 Feb 2024 03:31:55 +0000
Subject: [PATCH] BAEL-7296: Calling Custom Database Functions with JPA
(#15869)
---
.../spring-boot-persistence-4/pom.xml | 6 ++
.../customfunc/CustomFunctionApplication.java | 13 +++++
.../baeldung/customfunc/CustomH2Dialect.java | 25 ++++++++
.../customfunc/CustomHibernateConfig.java | 16 +++++
.../java/com/baeldung/customfunc/Product.java | 31 ++++++++++
.../customfunc/ProductRepository.java | 24 ++++++++
.../ProductRepositoryIntegrationTest.java | 58 +++++++++++++++++++
.../src/test/resources/product-data.sql | 17 ++++++
8 files changed, 190 insertions(+)
create mode 100644 persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/customfunc/CustomFunctionApplication.java
create mode 100644 persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/customfunc/CustomH2Dialect.java
create mode 100644 persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/customfunc/CustomHibernateConfig.java
create mode 100644 persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/customfunc/Product.java
create mode 100644 persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/customfunc/ProductRepository.java
create mode 100644 persistence-modules/spring-boot-persistence-4/src/test/java/com/baeldung/customfunc/ProductRepositoryIntegrationTest.java
create mode 100644 persistence-modules/spring-boot-persistence-4/src/test/resources/product-data.sql
diff --git a/persistence-modules/spring-boot-persistence-4/pom.xml b/persistence-modules/spring-boot-persistence-4/pom.xml
index 13cb4d5b82..acff937114 100644
--- a/persistence-modules/spring-boot-persistence-4/pom.xml
+++ b/persistence-modules/spring-boot-persistence-4/pom.xml
@@ -62,6 +62,11 @@
modelmapper
${modelmapper.version}
+
+ commons-codec
+ commons-codec
+ ${commons-codec.version}
+
org.projectlombok
lombok
@@ -88,6 +93,7 @@
3.7.0
2.16.0
3.2.0
+ 1.16.1
1.18.30
diff --git a/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/customfunc/CustomFunctionApplication.java b/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/customfunc/CustomFunctionApplication.java
new file mode 100644
index 0000000000..6e731501cb
--- /dev/null
+++ b/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/customfunc/CustomFunctionApplication.java
@@ -0,0 +1,13 @@
+package com.baeldung.customfunc;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class CustomFunctionApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(CustomFunctionApplication.class, args);
+ }
+
+}
\ No newline at end of file
diff --git a/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/customfunc/CustomH2Dialect.java b/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/customfunc/CustomH2Dialect.java
new file mode 100644
index 0000000000..56b26a8fcf
--- /dev/null
+++ b/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/customfunc/CustomH2Dialect.java
@@ -0,0 +1,25 @@
+package com.baeldung.customfunc;
+
+import org.hibernate.boot.model.FunctionContributions;
+import org.hibernate.dialect.H2Dialect;
+
+import org.hibernate.query.sqm.function.FunctionKind;
+import org.hibernate.query.sqm.function.SqmFunctionRegistry;
+import org.hibernate.query.sqm.produce.function.PatternFunctionDescriptorBuilder;
+import org.hibernate.type.spi.TypeConfiguration;
+
+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();
+ }
+
+}
\ No newline at end of file
diff --git a/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/customfunc/CustomHibernateConfig.java b/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/customfunc/CustomHibernateConfig.java
new file mode 100644
index 0000000000..424970f59a
--- /dev/null
+++ b/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/customfunc/CustomHibernateConfig.java
@@ -0,0 +1,16 @@
+package com.baeldung.customfunc;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer;
+
+import java.util.Map;
+
+@Configuration
+public class CustomHibernateConfig implements HibernatePropertiesCustomizer {
+
+ @Override
+ public void customize(Map hibernateProperties) {
+ hibernateProperties.put("hibernate.dialect", "com.baeldung.customfunc.CustomH2Dialect");
+ }
+
+}
\ No newline at end of file
diff --git a/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/customfunc/Product.java b/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/customfunc/Product.java
new file mode 100644
index 0000000000..bfbea7a0c5
--- /dev/null
+++ b/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/customfunc/Product.java
@@ -0,0 +1,31 @@
+package com.baeldung.customfunc;
+
+import jakarta.persistence.*;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+@Entity
+@Table(name = "product")
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@EqualsAndHashCode(of = {"id"})
+@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;
+
+}
\ No newline at end of file
diff --git a/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/customfunc/ProductRepository.java b/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/customfunc/ProductRepository.java
new file mode 100644
index 0000000000..ea9ceba0ff
--- /dev/null
+++ b/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/customfunc/ProductRepository.java
@@ -0,0 +1,24 @@
+package com.baeldung.customfunc;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.jpa.repository.query.Procedure;
+import org.springframework.data.repository.query.Param;
+
+import java.util.List;
+
+public interface ProductRepository extends JpaRepository {
+
+ @Procedure(name = "Product.sha256Hex")
+ String getSha256HexByNamedMapping(@Param("value") String value);
+
+ @Query(value = "CALL SHA256_HEX(:value)", nativeQuery = true)
+ String getSha256HexByNativeCall(@Param("value") String value);
+
+ @Query(value = "SELECT SHA256_HEX(name) FROM product", nativeQuery = true)
+ List getProductNameListInSha256HexByNativeSelect();
+
+ @Query(value = "SELECT sha256Hex(p.name) FROM Product p")
+ List getProductNameListInSha256Hex();
+
+}
\ No newline at end of file
diff --git a/persistence-modules/spring-boot-persistence-4/src/test/java/com/baeldung/customfunc/ProductRepositoryIntegrationTest.java b/persistence-modules/spring-boot-persistence-4/src/test/java/com/baeldung/customfunc/ProductRepositoryIntegrationTest.java
new file mode 100644
index 0000000000..a5b2ebe461
--- /dev/null
+++ b/persistence-modules/spring-boot-persistence-4/src/test/java/com/baeldung/customfunc/ProductRepositoryIntegrationTest.java
@@ -0,0 +1,58 @@
+package com.baeldung.customfunc;
+
+import org.apache.commons.codec.binary.Hex;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.nio.charset.StandardCharsets;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+
+@SpringBootTest(classes = CustomFunctionApplication.class, properties = {
+ "spring.jpa.show-sql=true",
+ "spring.jpa.properties.hibernate.format_sql=true",
+ "spring.jpa.generate-ddl=true",
+ "spring.jpa.defer-datasource-initialization=true",
+ "spring.sql.init.data-locations=classpath:product-data.sql"
+})
+@Transactional
+public class ProductRepositoryIntegrationTest {
+
+ private static final String TEXT = "Hand Grip Strengthener";
+ private static final String EXPECTED_HASH_HEX = getSha256Hex(TEXT);
+
+ @Autowired
+ private ProductRepository productRepository;
+
+ private static String getSha256Hex(String value) {
+ return Hex.encodeHexString(DigestUtils.sha256(value.getBytes(StandardCharsets.UTF_8)));
+ }
+
+ @Test
+ void whenGetSha256HexByNamedMapping_thenReturnCorrectHash() {
+ var hash = productRepository.getSha256HexByNamedMapping(TEXT);
+ assertThat(hash).isEqualTo(EXPECTED_HASH_HEX);
+ }
+
+ @Test
+ void whenGetSha256HexByNativeCall_thenReturnCorrectHash() {
+ var hash = productRepository.getSha256HexByNativeCall(TEXT);
+ assertThat(hash).isEqualTo(EXPECTED_HASH_HEX);
+ }
+
+ @Test
+ void whenCallGetSha256HexNative_thenReturnCorrectHash() {
+ var hashList = productRepository.getProductNameListInSha256HexByNativeSelect();
+ assertThat(hashList.get(0)).isEqualTo(EXPECTED_HASH_HEX);
+ }
+
+ @Test
+ void whenCallGetSha256Hex_thenReturnCorrectHash() {
+ var hashList = productRepository.getProductNameListInSha256Hex();
+ assertThat(hashList.get(0)).isEqualTo(EXPECTED_HASH_HEX);
+ }
+
+}
\ No newline at end of file
diff --git a/persistence-modules/spring-boot-persistence-4/src/test/resources/product-data.sql b/persistence-modules/spring-boot-persistence-4/src/test/resources/product-data.sql
new file mode 100644
index 0000000000..7a59da68b7
--- /dev/null
+++ b/persistence-modules/spring-boot-persistence-4/src/test/resources/product-data.sql
@@ -0,0 +1,17 @@
+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;
+ }
+';
+
+INSERT INTO product(name) VALUES('Hand Grip Strengthener');
\ No newline at end of file