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…
Reference in New Issue