From ec53934f3077b1398b5efd11d7a0ff6239a4b35c Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Wed, 3 Jan 2024 18:13:22 +0100 Subject: [PATCH] fix problem with Postgres function array return value in Hibernate 6 --- build.gradle | 6 +- .../config/PostgresCustomDialect.java | 2 +- .../hsadminng/context/Context.java | 9 ++- .../HsOfficeCoopAssetsTransactionEntity.java | 2 +- .../HsOfficeCoopSharesTransactionEntity.java | 2 +- .../debitor/HsOfficeDebitorRepository.java | 6 +- .../membership/HsOfficeMembershipEntity.java | 2 +- .../partner/HsOfficePartnerRepository.java | 4 +- .../office/person/HsOfficePersonEntity.java | 2 +- .../HsOfficeRelationshipEntity.java | 2 +- .../hsadminng/mapper/PostgresArray.java | 57 +++++++++++++++++++ 11 files changed, 78 insertions(+), 16 deletions(-) create mode 100644 src/main/java/net/hostsharing/hsadminng/mapper/PostgresArray.java diff --git a/build.gradle b/build.gradle index a4ffb298..7f024d77 100644 --- a/build.gradle +++ b/build.gradle @@ -60,9 +60,10 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'com.github.gavlyukovskiy:datasource-proxy-spring-boot-starter:1.9.1' implementation 'org.springdoc:springdoc-openapi:2.3.0' + implementation 'org.postgresql:postgresql:42.7.1' implementation 'org.liquibase:liquibase-core:4.25.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 'org.openapitools:jackson-databind-nullable:0.2.6' implementation 'org.apache.commons:commons-text:1.11.0' @@ -75,7 +76,6 @@ dependencies { developmentOnly 'org.springframework.boot:spring-boot-devtools' - runtimeOnly 'org.postgresql:postgresql:42.7.1' annotationProcessor 'org.projectlombok:lombok' testAnnotationProcessor 'org.projectlombok:lombok' @@ -214,7 +214,7 @@ project.tasks.check.dependsOn(checkLicense) // JaCoCo Test Code Coverage jacoco { - toolVersion = "0.8.8" + toolVersion = "0.8.10" } test { finalizedBy jacocoTestReport // generate report after tests diff --git a/src/main/java/net/hostsharing/hsadminng/config/PostgresCustomDialect.java b/src/main/java/net/hostsharing/hsadminng/config/PostgresCustomDialect.java index 9cd2ec70..3c66716d 100644 --- a/src/main/java/net/hostsharing/hsadminng/config/PostgresCustomDialect.java +++ b/src/main/java/net/hostsharing/hsadminng/config/PostgresCustomDialect.java @@ -8,7 +8,7 @@ import static org.hibernate.dialect.DatabaseVersion.make; public class PostgresCustomDialect extends PostgreSQLDialect { public PostgresCustomDialect() { - super(make(13, 7)); + super(make(15, 5)); } } diff --git a/src/main/java/net/hostsharing/hsadminng/context/Context.java b/src/main/java/net/hostsharing/hsadminng/context/Context.java index f7f6f827..2730147d 100644 --- a/src/main/java/net/hostsharing/hsadminng/context/Context.java +++ b/src/main/java/net/hostsharing/hsadminng/context/Context.java @@ -15,9 +15,11 @@ import java.util.Collections; import java.util.Optional; import java.util.Set; import java.util.UUID; +import java.util.function.Function; import java.util.stream.Collectors; import static java.util.function.Predicate.not; +import static net.hostsharing.hsadminng.mapper.PostgresArray.fromPostgresArray; import static org.springframework.transaction.annotation.Propagation.MANDATORY; @Service @@ -81,11 +83,14 @@ public class Context { } 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() { - 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) { diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionEntity.java index 9955f6f1..16cc48a1 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionEntity.java @@ -47,7 +47,7 @@ public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable { @Column(name = "transactiontype") @Enumerated(EnumType.STRING) - @Type(PostgreSQLEnumType.class) + //@Type(PostgreSQLEnumType.class) private HsOfficeCoopAssetsTransactionType transactionType; @Column(name = "valuedate") diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionEntity.java index 1b5d1cc5..ed814e05 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionEntity.java @@ -43,7 +43,7 @@ public class HsOfficeCoopSharesTransactionEntity implements Stringifyable { @Column(name = "transactiontype") @Enumerated(EnumType.STRING) - @Type(PostgreSQLEnumType.class) + //@Type(PostgreSQLEnumType.class) private HsOfficeCoopSharesTransactionType transactionType; @Column(name = "valuedate") diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepository.java b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepository.java index 27cb6f92..617ec09d 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepository.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepository.java @@ -19,9 +19,9 @@ public interface HsOfficeDebitorRepository extends RepositoryThis example code worked with Hibernate 5 (Spring Boot 3.0.x): + *

+     *      return (UUID[]) em.createNativeQuery("select currentSubjectsUuids() as uuids", UUID[].class).getSingleResult();
+     * 
+ *

+ * + *

With Hibernate 6 (Spring Boot 3.1.x), this utility method can be used like such: + *


+     *      final byte[] result = (byte[]) em.createNativeQuery("select * from currentSubjectsUuids() as uuids", UUID[].class)
+     *                 .getSingleResult();
+     *      return fromPostgresArray(result, UUID.class, UUID::fromString);
+     * 
+ *

+ * + * @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 type of a single element of the Java array + */ + public static T[] fromPostgresArray(final byte[] pgArray, final Class elementClass, final Function 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[] newGenericArray(final Class elementClass, final int length) { + return (T[]) Array.newInstance(elementClass, length); + } + +}