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>
|
||||
<version>${modelmapper.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-codec</groupId>
|
||||
<artifactId>commons-codec</artifactId>
|
||||
<version>${commons-codec.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
|
@ -88,6 +93,7 @@
|
|||
<hypersistence-utils.version>3.7.0</hypersistence-utils.version>
|
||||
<jackson.version>2.16.0</jackson.version>
|
||||
<modelmapper.version>3.2.0</modelmapper.version>
|
||||
<commons-codec.version>1.16.1</commons-codec.version>
|
||||
<lombok.version>1.18.30</lombok.version>
|
||||
</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