BAEL-7296: Calling Custom Database Functions with JPA (#15869)
This commit is contained in:
		
							parent
							
								
									6328bf349e
								
							
						
					
					
						commit
						1b843fd34a
					
				| @ -62,6 +62,11 @@ | |||||||
|             <artifactId>modelmapper</artifactId> |             <artifactId>modelmapper</artifactId> | ||||||
|             <version>${modelmapper.version}</version> |             <version>${modelmapper.version}</version> | ||||||
|         </dependency> |         </dependency> | ||||||
|  |         <dependency> | ||||||
|  |             <groupId>commons-codec</groupId> | ||||||
|  |             <artifactId>commons-codec</artifactId> | ||||||
|  |             <version>${commons-codec.version}</version> | ||||||
|  |         </dependency> | ||||||
|         <dependency> |         <dependency> | ||||||
|             <groupId>org.projectlombok</groupId> |             <groupId>org.projectlombok</groupId> | ||||||
|             <artifactId>lombok</artifactId> |             <artifactId>lombok</artifactId> | ||||||
| @ -88,6 +93,7 @@ | |||||||
|         <hypersistence-utils.version>3.7.0</hypersistence-utils.version> |         <hypersistence-utils.version>3.7.0</hypersistence-utils.version> | ||||||
|         <jackson.version>2.16.0</jackson.version> |         <jackson.version>2.16.0</jackson.version> | ||||||
|         <modelmapper.version>3.2.0</modelmapper.version> |         <modelmapper.version>3.2.0</modelmapper.version> | ||||||
|  |         <commons-codec.version>1.16.1</commons-codec.version> | ||||||
|         <lombok.version>1.18.30</lombok.version> |         <lombok.version>1.18.30</lombok.version> | ||||||
|     </properties> |     </properties> | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -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(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -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<String, Object> hibernateProperties) { | ||||||
|  |         hibernateProperties.put("hibernate.dialect", "com.baeldung.customfunc.CustomH2Dialect"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -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; | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -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<Product, Integer> { | ||||||
|  | 
 | ||||||
|  |     @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<String> getProductNameListInSha256HexByNativeSelect(); | ||||||
|  | 
 | ||||||
|  |     @Query(value = "SELECT sha256Hex(p.name) FROM Product p") | ||||||
|  |     List<String> getProductNameListInSha256Hex(); | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -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); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -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'); | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user