fix problem with Postgres function array return value in Hibernate 6

This commit is contained in:
Michael Hoennig 2024-01-03 18:13:22 +01:00
parent 063fcf90a3
commit ec53934f30
11 changed files with 78 additions and 16 deletions

View File

@ -60,9 +60,10 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'com.github.gavlyukovskiy:datasource-proxy-spring-boot-starter:1.9.1' implementation 'com.github.gavlyukovskiy:datasource-proxy-spring-boot-starter:1.9.1'
implementation 'org.springdoc:springdoc-openapi:2.3.0' implementation 'org.springdoc:springdoc-openapi:2.3.0'
implementation 'org.postgresql:postgresql:42.7.1'
implementation 'org.liquibase:liquibase-core:4.25.1' implementation 'org.liquibase:liquibase-core:4.25.1'
implementation 'com.vladmihalcea:hibernate-types-60:2.21.1' implementation 'com.vladmihalcea:hibernate-types-60:2.21.1'
implementation 'io.hypersistence:hypersistence-utils-hibernate-64:3.7.0' implementation 'io.hypersistence:hypersistence-utils-hibernate-62:3.7.0'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.16.1' implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.16.1'
implementation 'org.openapitools:jackson-databind-nullable:0.2.6' implementation 'org.openapitools:jackson-databind-nullable:0.2.6'
implementation 'org.apache.commons:commons-text:1.11.0' implementation 'org.apache.commons:commons-text:1.11.0'
@ -75,7 +76,6 @@ dependencies {
developmentOnly 'org.springframework.boot:spring-boot-devtools' developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'org.postgresql:postgresql:42.7.1'
annotationProcessor 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok' testAnnotationProcessor 'org.projectlombok:lombok'
@ -214,7 +214,7 @@ project.tasks.check.dependsOn(checkLicense)
// JaCoCo Test Code Coverage // JaCoCo Test Code Coverage
jacoco { jacoco {
toolVersion = "0.8.8" toolVersion = "0.8.10"
} }
test { test {
finalizedBy jacocoTestReport // generate report after tests finalizedBy jacocoTestReport // generate report after tests

View File

@ -8,7 +8,7 @@ import static org.hibernate.dialect.DatabaseVersion.make;
public class PostgresCustomDialect extends PostgreSQLDialect { public class PostgresCustomDialect extends PostgreSQLDialect {
public PostgresCustomDialect() { public PostgresCustomDialect() {
super(make(13, 7)); super(make(15, 5));
} }
} }

View File

@ -15,9 +15,11 @@ import java.util.Collections;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static java.util.function.Predicate.not; import static java.util.function.Predicate.not;
import static net.hostsharing.hsadminng.mapper.PostgresArray.fromPostgresArray;
import static org.springframework.transaction.annotation.Propagation.MANDATORY; import static org.springframework.transaction.annotation.Propagation.MANDATORY;
@Service @Service
@ -81,11 +83,14 @@ public class Context {
} }
public String[] getAssumedRoles() { public String[] getAssumedRoles() {
return (String[]) em.createNativeQuery("select assumedRoles() as roles", String[].class).getSingleResult(); final byte[] result = (byte[]) em.createNativeQuery("select assumedRoles() as roles", String[].class).getSingleResult();
return fromPostgresArray(result, String.class, Function.identity());
} }
public UUID[] currentSubjectsUuids() { public UUID[] currentSubjectsUuids() {
return (UUID[]) em.createNativeQuery("select currentSubjectsUuids() as uuids", UUID[].class).getSingleResult(); final byte[] result = (byte[]) em.createNativeQuery("select currentSubjectsUuids() as uuids", UUID[].class)
.getSingleResult();
return fromPostgresArray(result, UUID.class, UUID::fromString);
} }
public static String getCallerMethodNameFromStackFrame(final int skipFrames) { public static String getCallerMethodNameFromStackFrame(final int skipFrames) {

View File

@ -47,7 +47,7 @@ public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable {
@Column(name = "transactiontype") @Column(name = "transactiontype")
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Type(PostgreSQLEnumType.class) //@Type(PostgreSQLEnumType.class)
private HsOfficeCoopAssetsTransactionType transactionType; private HsOfficeCoopAssetsTransactionType transactionType;
@Column(name = "valuedate") @Column(name = "valuedate")

View File

@ -43,7 +43,7 @@ public class HsOfficeCoopSharesTransactionEntity implements Stringifyable {
@Column(name = "transactiontype") @Column(name = "transactiontype")
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Type(PostgreSQLEnumType.class) //@Type(PostgreSQLEnumType.class)
private HsOfficeCoopSharesTransactionType transactionType; private HsOfficeCoopSharesTransactionType transactionType;
@Column(name = "valuedate") @Column(name = "valuedate")

View File

@ -19,9 +19,9 @@ public interface HsOfficeDebitorRepository extends Repository<HsOfficeDebitorEnt
@Query(""" @Query("""
SELECT debitor FROM HsOfficeDebitorEntity debitor SELECT debitor FROM HsOfficeDebitorEntity debitor
JOIN HsOfficePartnerEntity partner ON partner.uuid = debitor.partner JOIN HsOfficePartnerEntity partner ON partner.uuid = debitor.partner.uuid
JOIN HsOfficePersonEntity person ON person.uuid = partner.person JOIN HsOfficePersonEntity person ON person.uuid = partner.person.uuid
JOIN HsOfficeContactEntity contact ON contact.uuid = debitor.billingContact JOIN HsOfficeContactEntity contact ON contact.uuid = debitor.billingContact.uuid
WHERE :name is null WHERE :name is null
OR partner.details.birthName like concat(:name, '%') OR partner.details.birthName like concat(:name, '%')
OR person.tradeName like concat(:name, '%') OR person.tradeName like concat(:name, '%')

View File

@ -61,7 +61,7 @@ public class HsOfficeMembershipEntity implements Stringifyable {
@Column(name = "reasonfortermination") @Column(name = "reasonfortermination")
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Type(PostgreSQLEnumType.class) //@Type(PostgreSQLEnumType.class)
private HsOfficeReasonForTermination reasonForTermination; private HsOfficeReasonForTermination reasonForTermination;
public void setValidFrom(final LocalDate validFrom) { public void setValidFrom(final LocalDate validFrom) {

View File

@ -13,8 +13,8 @@ public interface HsOfficePartnerRepository extends Repository<HsOfficePartnerEnt
@Query(""" @Query("""
SELECT partner FROM HsOfficePartnerEntity partner SELECT partner FROM HsOfficePartnerEntity partner
JOIN HsOfficeContactEntity contact ON contact.uuid = partner.contact JOIN HsOfficeContactEntity contact ON contact.uuid = partner.contact.uuid
JOIN HsOfficePersonEntity person ON person.uuid = partner.person JOIN HsOfficePersonEntity person ON person.uuid = partner.person.uuid
WHERE :name is null WHERE :name is null
OR partner.details.birthName like concat(:name, '%') OR partner.details.birthName like concat(:name, '%')
OR contact.label like concat(:name, '%') OR contact.label like concat(:name, '%')

View File

@ -37,7 +37,7 @@ public class HsOfficePersonEntity implements Stringifyable {
@Column(name = "persontype") @Column(name = "persontype")
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Type(PostgreSQLEnumType.class) //@Type(PostgreSQLEnumType.class)
private HsOfficePersonType personType; private HsOfficePersonType personType;
@Column(name = "tradename") @Column(name = "tradename")

View File

@ -47,7 +47,7 @@ public class HsOfficeRelationshipEntity {
@Column(name = "reltype") @Column(name = "reltype")
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Type(PostgreSQLEnumType.class) //@Type(PostgreSQLEnumType.class)
private HsOfficeRelationshipType relType; private HsOfficeRelationshipType relType;
@Override @Override

View File

@ -0,0 +1,57 @@
package net.hostsharing.hsadminng.mapper;
import com.vladmihalcea.hibernate.type.range.Range;
import lombok.experimental.UtilityClass;
import org.postgresql.util.PGtokenizer;
import java.lang.reflect.Array;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.util.function.Function;
@UtilityClass
public class PostgresArray {
/**
* Converts a byte[], as returned for a Postgres-array by native queries, to a Java array.
*
* <p>This example code worked with Hibernate 5 (Spring Boot 3.0.x):
* <pre><code>
* return (UUID[]) em.createNativeQuery("select currentSubjectsUuids() as uuids", UUID[].class).getSingleResult();
* </code></pre>
* </p>
*
* <p>With Hibernate 6 (Spring Boot 3.1.x), this utility method can be used like such:
* <pre><code>
* final byte[] result = (byte[]) em.createNativeQuery("select * from currentSubjectsUuids() as uuids", UUID[].class)
* .getSingleResult();
* return fromPostgresArray(result, UUID.class, UUID::fromString);
* </code></pre>
* </p>
*
* @param pgArray the byte[] returned by a native query containing as rendered for a Postgres array
* @param elementClass the class of a single element of the Java array to be returned
* @param itemParser converts a string element to the specified elementClass
* @return a Java array containing the data from pgArray
* @param <T> type of a single element of the Java array
*/
public static <T> T[] fromPostgresArray(final byte[] pgArray, final Class<T> elementClass, final Function<String, T> itemParser) {
final var pgArrayLiteral = new String(pgArray, StandardCharsets.UTF_8);
if (pgArrayLiteral.length() == 2) {
return newGenericArray(elementClass, 0);
}
final PGtokenizer tokenizer = new PGtokenizer(pgArrayLiteral.substring(1, pgArrayLiteral.length()-1), ',');
tokenizer.remove("\"", "\"");
final T[] array = newGenericArray(elementClass, tokenizer.getSize()); // Create a new array of the specified type and length
for ( int n = 0; n < tokenizer.getSize(); ++n ) {
array[n] = itemParser.apply(tokenizer.getToken(n).trim().replace("\\\"", "\""));
}
return array;
}
@SuppressWarnings("unchecked")
private static <T> T[] newGenericArray(final Class<T> elementClass, final int length) {
return (T[]) Array.newInstance(elementClass, length);
}
}