Add h8-preview module: hibernate-demo suite against Hibernate 8.0.0.Beta3 and Jakarta Persistence 4.0.0-M7
Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Gth99spB1CzQmBFofXmU4Z
This commit is contained in:
@@ -0,0 +1,15 @@
|
|||||||
|
# h8-preview
|
||||||
|
|
||||||
|
Companion module for the ankurm.com article *Hibernate 8 and Jakarta Persistence 4.0 Preview: What Will Break*.
|
||||||
|
Hibernate ORM **8.0.0.Beta3** and Jakarta Persistence **4.0.0-M7** are pre-release; the numbers below can change.
|
||||||
|
|
||||||
|
| Path | What it is |
|
||||||
|
|---|---|
|
||||||
|
| `pom.xml` | standalone project (no Spring); `-Dhibernate.version=7.4.5.Final -Dpersistence.version=3.2.0 -Dh.label=h7` runs the same tests on Hibernate 7 |
|
||||||
|
| `src/test/.../ExceptionContractsTest`, `FlushOrderTest`, `MutatingNamedQueryTest` | same code on both versions; each writes a transcript |
|
||||||
|
| `src/broken/` | not built; compiled by `scripts/capture-compile-errors.sh` to capture the compiler's own words |
|
||||||
|
| `scripts/run-suite.sh` | runs the whole `../src` suite (the Hibernate 7 chapters) in a scratch copy against a chosen Hibernate / Boot pair |
|
||||||
|
| `output/` | every transcript quoted in the article |
|
||||||
|
|
||||||
|
There is deliberately no `docs/` folder for this module; the explanations are in the article.
|
||||||
|
The parent repository's older `docs/` chapters belong to earlier articles and are unchanged.
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# 01-exception-contracts (Hibernate ORM 7.4.5.Final, JDK 25)
|
||||||
|
unbound parameter : threw org.hibernate.QueryParameterException
|
||||||
|
null parameter : returned 0
|
||||||
|
type mismatch : threw org.hibernate.query.QueryArgumentException
|
||||||
|
unique violation : threw org.hibernate.exception.ConstraintViolationException <- org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException
|
||||||
|
unknown parameter : threw java.lang.IllegalArgumentException <- org.hibernate.query.UnknownParameterException
|
||||||
|
named query, no row: 0 rows
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# 01-exception-contracts (Hibernate ORM 8.0.0.Beta3, JDK 25)
|
||||||
|
unbound parameter : threw org.hibernate.QueryParameterException
|
||||||
|
null parameter : returned 0
|
||||||
|
type mismatch : threw org.hibernate.query.QueryArgumentException
|
||||||
|
unique violation : threw org.hibernate.exception.ConstraintViolationException <- org.h2.jdbc.JdbcBatchUpdateException
|
||||||
|
unknown parameter : threw java.lang.IllegalArgumentException <- org.hibernate.query.UnknownParameterException
|
||||||
|
named query, no row: 0 rows
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# 02-flush-order (Hibernate ORM 7.4.5.Final, JDK 25)
|
||||||
|
default : [Parent, Child, Parent, Child]
|
||||||
|
hibernate.flush.queue.type=legacy: [Parent, Child, Parent, Child]
|
||||||
|
hibernate.flush.queue.type=graph : [Parent, Child, Parent, Child]
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# 02-flush-order (Hibernate ORM 8.0.0.Beta3, JDK 25)
|
||||||
|
default : [Parent, Child]
|
||||||
|
hibernate.flush.queue.type=legacy: [Parent, Child, Parent, Child]
|
||||||
|
hibernate.flush.queue.type=graph : [Parent, Child]
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# 03-mutating-named-query (Hibernate ORM 7.4.5.Final, JDK 25)
|
||||||
|
session factory built; createNamedQuery(..).executeUpdate() returned 0
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# 03-mutating-named-query (Hibernate ORM 8.0.0.Beta3, JDK 25)
|
||||||
|
threw java.lang.IllegalArgumentException <- org.hibernate.query.IllegalSelectQueryException :: Expecting a SELECT Query [org.hibernate.query.sqm.tree.spi.select.SqmSelectStatement], but found org.hibernate.query.sqm.tree.spi.update.SqmUpdateStatement [update MutatingNamedQueryEntity m set m.name = :name]
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
# compile-errors (javac --release 25 -Xlint:removal -Xlint:deprecation; files in src/broken/)
|
||||||
|
--- UsesTransformers on h7
|
||||||
|
UsesTransformers.java:14: warning: [deprecation] Transformers in org.hibernate.transform has been deprecated
|
||||||
|
.setResultTransformer(Transformers.aliasToBean(Dto.class)).list();
|
||||||
|
UsesTransformers.java:14: warning: [deprecation] <S>setResultTransformer(ResultTransformer<S>) in NativeQuery has been deprecated
|
||||||
|
.setResultTransformer(Transformers.aliasToBean(Dto.class)).list();
|
||||||
|
where S is a type-variable:
|
||||||
|
S extends Object declared in method <S>setResultTransformer(ResultTransformer<S>)
|
||||||
|
Note: src/broken/UsesTransformers.java uses unchecked or unsafe operations.
|
||||||
|
Note: Recompile with -Xlint:unchecked for details.
|
||||||
|
2 warnings
|
||||||
|
--- UsesTransformers on h8
|
||||||
|
UsesTransformers.java:3: error: package org.hibernate.transform does not exist
|
||||||
|
import org.hibernate.transform.Transformers;
|
||||||
|
UsesTransformers.java:14: error: cannot find symbol
|
||||||
|
.setResultTransformer(Transformers.aliasToBean(Dto.class)).list();
|
||||||
|
symbol: variable Transformers
|
||||||
|
location: class UsesTransformers
|
||||||
|
2 errors
|
||||||
|
--- UsesReplicate on h7
|
||||||
|
UsesReplicate.java:8: warning: [deprecation] ReplicationMode in org.hibernate has been deprecated
|
||||||
|
s.replicate(detached, ReplicationMode.OVERWRITE);
|
||||||
|
UsesReplicate.java:8: warning: [deprecation] replicate(Object,ReplicationMode) in Session has been deprecated
|
||||||
|
s.replicate(detached, ReplicationMode.OVERWRITE);
|
||||||
|
2 warnings
|
||||||
|
--- UsesReplicate on h8
|
||||||
|
UsesReplicate.java:1: error: cannot find symbol
|
||||||
|
import org.hibernate.ReplicationMode;
|
||||||
|
symbol: class ReplicationMode
|
||||||
|
location: package org.hibernate
|
||||||
|
UsesReplicate.java:8: error: cannot find symbol
|
||||||
|
s.replicate(detached, ReplicationMode.OVERWRITE);
|
||||||
|
symbol: variable ReplicationMode
|
||||||
|
location: class UsesReplicate
|
||||||
|
2 errors
|
||||||
|
--- StatelessInsertReturnsId on h7
|
||||||
|
--- StatelessInsertReturnsId on h8
|
||||||
|
StatelessInsertReturnsId.java:7: error: incompatible types: void cannot be converted to Object
|
||||||
|
Object id = ss.insert(new com.ankurm.h8.Author("A", "[email protected]"));
|
||||||
|
1 error
|
||||||
|
--- QueryVariable on h7
|
||||||
|
--- QueryVariable on h8
|
||||||
|
QueryVariable.java:9: warning: [removal] getResultList() in Query has been deprecated and marked for removal
|
||||||
|
return q.getResultList().size();
|
||||||
|
where T is a type-variable:
|
||||||
|
T extends Object declared in interface Query
|
||||||
|
1 warning
|
||||||
|
--- TypedQueryVariable on h7
|
||||||
|
--- TypedQueryVariable on h8
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# binary-compatibility (BinaryProbe compiled against one Hibernate, run on the other)
|
||||||
|
--- compiled on h7, run on h8
|
||||||
|
Exception in thread "main" java.lang.NoSuchMethodError: 'org.hibernate.query.Query org.hibernate.Session.createQuery(java.lang.String, java.lang.Class)'
|
||||||
|
--- compiled on h8, run on h7
|
||||||
|
Exception in thread "main" java.lang.NoSuchMethodError: 'org.hibernate.query.SelectionQuery org.hibernate.Session.createQuery(java.lang.String, java.lang.Class)'
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# persistence-spi (javap on jakarta.persistence.spi.PersistenceUnitInfo, and on Spring's implementation)
|
||||||
|
--- methods that exist in Jakarta Persistence 4.0.0-M7 but not in 3.2.0
|
||||||
|
public abstract FetchType getDefaultToOneFetchType();
|
||||||
|
public abstract List<String> getAllClassNames();
|
||||||
|
public abstract List<String> getAllModuleDescriptors();
|
||||||
|
public abstract List<String> getAllPackageDescriptors();
|
||||||
|
public abstract List<String> getManagedModuleDescriptors();
|
||||||
|
public abstract List<String> getManagedPackageDescriptors();
|
||||||
|
--- org.springframework.orm.jpa.persistenceunit.SpringPersistenceUnitInfo 7.0.9: methods mentioning those names
|
||||||
|
class found, 15 methods and constructors listed; matches for the six new names: 0
|
||||||
|
--- org.springframework.orm.jpa.persistenceunit.SpringPersistenceUnitInfo 7.1.0-M2: methods mentioning those names
|
||||||
|
class found, 15 methods and constructors listed; matches for the six new names: 0
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# dependency-versions (hibernate-core 8.0.0.Beta3 POM: what it declares)
|
||||||
|
<artifactId>jakarta.persistence-api</artifactId> <version>4.0.0-M7</version>
|
||||||
|
<artifactId>hibernate-models</artifactId> <version>1.3.4</version>
|
||||||
|
<artifactId>byte-buddy</artifactId> <version>1.18.11</version>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# suite on h7 hibernate=managed by Boot 4.1.1 persistence-api=managed by Boot 4.1.1
|
||||||
|
spring boot parent: 4.1.1
|
||||||
|
pins (hibernate-models 1.3.4, jandex 3.6.0): no
|
||||||
|
test-compile exit code: 0
|
||||||
|
tests run: 205 failures: 0 errors: 2 skipped: 0
|
||||||
|
test classes with a problem: 1
|
||||||
|
distinct final causes (count = failing test methods):
|
||||||
|
1 org.hibernate.query.UnknownNamedQueryException: No query named 'XmlQueryEmployee.findBySalaryAboveXml'
|
||||||
|
1 org.hibernate.query.UnknownParameterException: No parameter named ':min' in query with named parameters []
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# suite on h8-boot42 hibernate=8.0.0.Beta3 persistence-api=4.0.0-M7
|
||||||
|
spring boot parent: 4.2.0-M2
|
||||||
|
pins (hibernate-models 1.3.4, jandex 3.6.0): yes
|
||||||
|
test-compile exit code: 0
|
||||||
|
tests run: 205 failures: 1 errors: 158 skipped: 0
|
||||||
|
test classes with a problem: 52
|
||||||
|
distinct final causes (count = failing test methods):
|
||||||
|
140 (context failed earlier in the same JVM; see the first failure of that context)
|
||||||
|
14 java.lang.NoSuchMethodException: org.springframework.orm.jpa.persistenceunit.SpringPersistenceUnitInfo.getAllPackageDescriptors()
|
||||||
|
4 org.hibernate.query.NamedQueryValidationException: Errors in named queries:
|
||||||
|
1 java.lang.AssertionError:
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# suite on h8-pinned hibernate=8.0.0.Beta3 persistence-api=4.0.0-M7
|
||||||
|
spring boot parent: 4.1.1
|
||||||
|
pins (hibernate-models 1.3.4, jandex 3.6.0): yes
|
||||||
|
test-compile exit code: 0
|
||||||
|
tests run: 205 failures: 1 errors: 158 skipped: 0
|
||||||
|
test classes with a problem: 52
|
||||||
|
distinct final causes (count = failing test methods):
|
||||||
|
140 (context failed earlier in the same JVM; see the first failure of that context)
|
||||||
|
14 java.lang.NoSuchMethodException: org.springframework.orm.jpa.persistenceunit.SpringPersistenceUnitInfo.getAllPackageDescriptors()
|
||||||
|
4 org.hibernate.query.NamedQueryValidationException: Errors in named queries:
|
||||||
|
1 java.lang.AssertionError:
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# suite on h8-unpinned hibernate=8.0.0.Beta3 persistence-api=4.0.0-M7
|
||||||
|
spring boot parent: 4.1.1
|
||||||
|
pins (hibernate-models 1.3.4, jandex 3.6.0): no
|
||||||
|
test-compile exit code: 0
|
||||||
|
tests run: 202 failures: 2 errors: 181 skipped: 0
|
||||||
|
test classes with a problem: 67
|
||||||
|
distinct final causes (count = failing test methods):
|
||||||
|
140 (context failed earlier in the same JVM; see the first failure of that context)
|
||||||
|
39 java.lang.ExceptionInInitializerError: Exception java.lang.NoSuchFieldError: Class org.hibernate.models.spi.AnnotationTarget$Kind does not have member field 'org.hibernat
|
||||||
|
1 javax.naming.NameAlreadyBoundException: Name jdbc already bound. Use rebind() to override
|
||||||
|
1 org.opentest4j.AssertionFailedError: Expected javax.naming.NameAlreadyBoundException to be thrown, but nothing was thrown.
|
||||||
|
1 java.lang.AssertionError:
|
||||||
|
1 java.lang.NoSuchFieldError: Class org.hibernate.models.spi.AnnotationTarget$Kind does not have member field 'org.hibernate.models.spi.AnnotationTarget$Kind MODULE'
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>com.ankurm</groupId>
|
||||||
|
<artifactId>h8-preview</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
<name>h8-preview</name>
|
||||||
|
<description>Hibernate ORM 8 beta against Hibernate 7: what breaks, run with plain Hibernate (no Spring)</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.release>25</maven.compiler.release>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<!-- override on the command line to run the same tests on Hibernate 7:
|
||||||
|
-Dhibernate.version=7.4.5.Final -Dpersistence.version=3.2.0 -Dh.label=h7 -->
|
||||||
|
<hibernate.version>8.0.0.Beta3</hibernate.version>
|
||||||
|
<persistence.version>4.0.0-M7</persistence.version>
|
||||||
|
<h.label>h8</h.label>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.hibernate.orm</groupId>
|
||||||
|
<artifactId>hibernate-core</artifactId>
|
||||||
|
<version>${hibernate.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>jakarta.persistence</groupId>
|
||||||
|
<artifactId>jakarta.persistence-api</artifactId>
|
||||||
|
<version>${persistence.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.h2database</groupId>
|
||||||
|
<artifactId>h2</artifactId>
|
||||||
|
<version>2.4.240</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
<version>6.0.3</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.assertj</groupId>
|
||||||
|
<artifactId>assertj-core</artifactId>
|
||||||
|
<version>3.27.7</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>3.6.0</version>
|
||||||
|
<configuration>
|
||||||
|
<systemPropertyVariables>
|
||||||
|
<h.label>${h.label}</h.label>
|
||||||
|
<h.version>${hibernate.version}</h.version>
|
||||||
|
</systemPropertyVariables>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
Executable
+31
@@ -0,0 +1,31 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Compiles each file in src/broken/ against Hibernate 7.4.5.Final and against 8.0.0.Beta3 and keeps the compiler's own words.
|
||||||
|
# Then compiles BinaryProbe against 7 and runs it on 8 (and the reverse) to show linkage errors.
|
||||||
|
set -uo pipefail
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
W=$(mktemp -d)
|
||||||
|
cp_for() { mvn -B -q dependency:build-classpath -Dmdep.outputFile="$W/cp-$1.txt" -Dhibernate.version=$2 -Dpersistence.version=$3 >/dev/null 2>&1; cat "$W/cp-$1.txt"; }
|
||||||
|
CP7=$(cp_for h7 7.4.5.Final 3.2.0); CP8=$(cp_for h8 8.0.0.Beta3 4.0.0-M7)
|
||||||
|
{
|
||||||
|
echo "# compile-errors (javac --release 25 -Xlint:removal -Xlint:deprecation; files in src/broken/)"
|
||||||
|
for f in UsesTransformers UsesReplicate StatelessInsertReturnsId QueryVariable TypedQueryVariable; do
|
||||||
|
for v in h7 h8; do
|
||||||
|
CP=$([ $v = h7 ] && echo "$CP7" || echo "$CP8")
|
||||||
|
mkdir -p "$W/out-$v"
|
||||||
|
echo "--- $f on $v"
|
||||||
|
javac --release 25 -Xlint:removal -Xlint:deprecation -proc:none -d "$W/out-$v" -sourcepath src/main/java -cp "$CP" src/broken/$f.java 2>&1 \
|
||||||
|
| grep -v JAVA_TOOL | sed 's#^src/broken/##' | grep -v -E '^\s*\^?$' || true
|
||||||
|
done
|
||||||
|
done
|
||||||
|
} > output/04-compile-errors.txt
|
||||||
|
{
|
||||||
|
echo "# binary-compatibility (BinaryProbe compiled against one Hibernate, run on the other)"
|
||||||
|
for pair in "h7 h8" "h8 h7"; do
|
||||||
|
set -- $pair
|
||||||
|
CPC=$([ $1 = h7 ] && echo "$CP7" || echo "$CP8"); CPR=$([ $2 = h7 ] && echo "$CP7" || echo "$CP8")
|
||||||
|
rm -rf "$W/bin-$1"; mkdir -p "$W/bin-$1"
|
||||||
|
javac --release 25 -proc:none -d "$W/bin-$1" -sourcepath src/main/java -cp "$CPC" src/broken/BinaryProbe.java 2>&1 | grep -v JAVA_TOOL
|
||||||
|
echo "--- compiled on $1, run on $2"
|
||||||
|
java -cp "$W/bin-$1:$CPR" BinaryProbe 2>&1 | grep -E '^(createQuery ran|Exception in thread|java\.lang\.)' | cut -c1-230 | head -3
|
||||||
|
done
|
||||||
|
} > output/05-binary-compatibility.txt
|
||||||
Executable
+28
@@ -0,0 +1,28 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Facts read from jars: the Jakarta Persistence SPI change and whether Spring implements it.
|
||||||
|
set -uo pipefail
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
W=$(mktemp -d); R="$HOME/.m2/repository"
|
||||||
|
for a in jakarta.persistence:jakarta.persistence-api:3.2.0 jakarta.persistence:jakarta.persistence-api:4.0.0-M7 org.springframework:spring-orm:7.0.9 org.springframework:spring-orm:7.1.0-M2; do
|
||||||
|
mvn -B -q dependency:get -Dartifact=$a >/dev/null 2>&1
|
||||||
|
done
|
||||||
|
{
|
||||||
|
echo "# persistence-spi (javap on jakarta.persistence.spi.PersistenceUnitInfo, and on Spring's implementation)"
|
||||||
|
for v in 3.2.0 4.0.0-M7; do
|
||||||
|
javap -cp "$R/jakarta/persistence/jakarta.persistence-api/$v/jakarta.persistence-api-$v.jar" jakarta.persistence.spi.PersistenceUnitInfo 2>/dev/null \
|
||||||
|
| grep -E 'public abstract|public default' | sed 's/^ *//; s/jakarta.persistence.spi.//g; s/java.util.//g; s/java.lang.//g; s/jakarta.persistence.//g' | sort > "$W/spi-$v.txt"
|
||||||
|
done
|
||||||
|
echo "--- methods that exist in Jakarta Persistence 4.0.0-M7 but not in 3.2.0"
|
||||||
|
comm -13 "$W/spi-3.2.0.txt" "$W/spi-4.0.0-M7.txt" | sed 's/^/ /'
|
||||||
|
for v in 7.0.9 7.1.0-M2; do
|
||||||
|
echo "--- org.springframework.orm.jpa.persistenceunit.SpringPersistenceUnitInfo $v: methods mentioning those names"
|
||||||
|
all=$(javap -cp "$R/org/springframework/spring-orm/$v/spring-orm-$v.jar" org.springframework.orm.jpa.persistenceunit.SpringPersistenceUnitInfo 2>/dev/null | grep -c '(' || true)
|
||||||
|
n=$(javap -cp "$R/org/springframework/spring-orm/$v/spring-orm-$v.jar" org.springframework.orm.jpa.persistenceunit.SpringPersistenceUnitInfo 2>/dev/null | grep -c -E 'AllPackageDescriptors|DefaultToOneFetchType|AllClassNames|ModuleDescriptors|ManagedPackageDescriptors' || true)
|
||||||
|
echo " class found, $all methods and constructors listed; matches for the six new names: $n"
|
||||||
|
done
|
||||||
|
} > output/06-persistence-spi.txt
|
||||||
|
{
|
||||||
|
echo "# dependency-versions (hibernate-core 8.0.0.Beta3 POM: what it declares)"
|
||||||
|
curl -s https://repo1.maven.org/maven2/org/hibernate/orm/hibernate-core/8.0.0.Beta3/hibernate-core-8.0.0.Beta3.pom \
|
||||||
|
| grep -B1 -A2 -E '<artifactId>(hibernate-models|jakarta.persistence-api|byte-buddy|jandex)</artifactId>' | grep -E 'artifactId|version' | sed 's/^ *//' | paste -sd' ' - | sed 's#</version>#</version>\n#g' | sed 's/^ *//' | grep -v '^$'
|
||||||
|
} > output/07-hibernate-core-pom.txt
|
||||||
Executable
+16
@@ -0,0 +1,16 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Regenerates every file under output/. Needs JDK 25, Maven 3.9 and network access to Maven Central.
|
||||||
|
# ./scripts/run-all.sh
|
||||||
|
# 01-03: the plain-Hibernate tests on 8.0.0.Beta3, then on 7.4.5.Final (mvn clean between, or stale classes link wrongly)
|
||||||
|
# 04-07: javac / javap / POM facts. suite-*.txt: the whole ../src suite in a scratch copy, four ways (slow, ~10 minutes).
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
mvn -B clean test
|
||||||
|
mvn -B clean test -Dhibernate.version=7.4.5.Final -Dpersistence.version=3.2.0 -Dh.label=h7
|
||||||
|
./scripts/capture-compile-errors.sh
|
||||||
|
./scripts/capture-facts.sh
|
||||||
|
./scripts/run-suite.sh h7
|
||||||
|
./scripts/run-suite.sh h8-unpinned 8.0.0.Beta3 4.0.0-M7 no
|
||||||
|
./scripts/run-suite.sh h8-pinned 8.0.0.Beta3 4.0.0-M7 yes
|
||||||
|
./scripts/run-suite.sh h8-boot42 8.0.0.Beta3 4.0.0-M7 yes 4.2.0-M2
|
||||||
|
ls -1 output
|
||||||
Executable
+65
@@ -0,0 +1,65 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Runs the whole hibernate-demo suite (../src) against a chosen Hibernate / Jakarta Persistence pair in a
|
||||||
|
# scratch copy, and writes a plain-text summary. The original tree is never touched.
|
||||||
|
#
|
||||||
|
# ./scripts/run-suite.sh <label> [hibernate.version jakarta-persistence.version]
|
||||||
|
# ./scripts/run-suite.sh h7 # what Spring Boot 4.1.1 manages
|
||||||
|
# ./scripts/run-suite.sh h8 8.0.0.Beta3 4.0.0-M7 # Hibernate 8 beta
|
||||||
|
set -uo pipefail
|
||||||
|
HERE="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
LABEL="${1:?label}"; HV="${2:-}"; PV="${3:-}"; PIN="${4:-}"; BOOT="${5:-}" # PIN=yes adds the hibernate-models / jandex pins below
|
||||||
|
W="$(mktemp -d)"
|
||||||
|
mkdir -p "$W/src" && cp -r "$HERE/../src/." "$W/src/" && cp "$HERE/../pom.xml" "$W/pom.xml"
|
||||||
|
cd "$W"
|
||||||
|
[ -n "$BOOT" ] && sed -i "0,/<version>4.1.1<\/version>/s//<version>$BOOT<\/version>/" pom.xml
|
||||||
|
if [ "$PIN" = yes ]; then
|
||||||
|
# Maven's nearest-wins rule picks hibernate-models 1.1.1 (through hibernate-search-mapper-orm) over the 1.3.4
|
||||||
|
# hibernate-core 8 asks for. Pinning is the whole fix for the classpath problem.
|
||||||
|
python3 - <<'PY'
|
||||||
|
p=open('pom.xml').read()
|
||||||
|
pin=""" <dependencyManagement><dependencies>
|
||||||
|
<dependency><groupId>org.hibernate.models</groupId><artifactId>hibernate-models</artifactId><version>1.3.4</version></dependency>
|
||||||
|
<dependency><groupId>io.smallrye</groupId><artifactId>jandex</artifactId><version>3.6.0</version></dependency>
|
||||||
|
</dependencies></dependencyManagement>
|
||||||
|
"""
|
||||||
|
open('pom.xml','w').write(p.replace('</project>',pin+'</project>'))
|
||||||
|
PY
|
||||||
|
fi
|
||||||
|
ARGS=(-B -Dmaven.test.failure.ignore=true)
|
||||||
|
[ -n "$HV" ] && ARGS+=("-Dhibernate.version=$HV")
|
||||||
|
[ -n "$PV" ] && ARGS+=("-Djakarta-persistence.version=$PV")
|
||||||
|
OUT="$HERE/output"
|
||||||
|
mvn "${ARGS[@]}" test-compile > "$W/compile.log" 2>&1; CRC=$?
|
||||||
|
{
|
||||||
|
echo "# suite on ${LABEL} hibernate=${HV:-managed by Boot 4.1.1} persistence-api=${PV:-managed by Boot 4.1.1}"
|
||||||
|
echo "spring boot parent: ${BOOT:-4.1.1}"
|
||||||
|
echo "pins (hibernate-models 1.3.4, jandex 3.6.0): ${PIN:-no}"
|
||||||
|
echo "test-compile exit code: $CRC"
|
||||||
|
} > "$OUT/suite-$LABEL.txt"
|
||||||
|
if [ $CRC -ne 0 ]; then
|
||||||
|
grep -E '^\[ERROR\] /' "$W/compile.log" | sed "s#$W/src/##" | sed 's/^\[ERROR\] //' | sort -u > "$OUT/compile-errors-$LABEL.txt"
|
||||||
|
echo "compile errors: $(wc -l < "$OUT/compile-errors-$LABEL.txt")" >> "$OUT/suite-$LABEL.txt"
|
||||||
|
else
|
||||||
|
mvn "${ARGS[@]}" test > "$W/test.log" 2>&1
|
||||||
|
python3 - "$W" >> "$OUT/suite-$LABEL.txt" <<'PY'
|
||||||
|
import glob,re,sys,collections
|
||||||
|
w=sys.argv[1]; t=f=e=s=0; causes=collections.Counter(); classes=set()
|
||||||
|
for p in sorted(glob.glob(w+'/target/surefire-reports/*.txt')):
|
||||||
|
x=open(p).read()
|
||||||
|
m=re.search(r'Tests run: (\d+), Failures: (\d+), Errors: (\d+), Skipped: (\d+)',x)
|
||||||
|
if not m: continue
|
||||||
|
a,b,c,d=map(int,m.groups()); t+=a;f+=b;e+=c;s+=d
|
||||||
|
for blk in re.split(r'\n(?=\S+ -- Time elapsed: [\d.]+ s <<< (?:ERROR|FAILURE)!)',x)[1:]:
|
||||||
|
cb=re.findall(r'Caused by: ([^\n]*)',blk)
|
||||||
|
first=blk.split('\n')[1] if '\n' in blk else ''
|
||||||
|
msg=(cb[-1] if cb else first)
|
||||||
|
msg=re.sub(r'@[0-9a-f]{5,}','@..',msg)[:170]
|
||||||
|
if 'failure threshold' in first and not cb: msg='(context failed earlier in the same JVM; see the first failure of that context)'
|
||||||
|
causes[msg]+=1; classes.add(p.split('/')[-1][:-4].split('.')[-1])
|
||||||
|
print(f'tests run: {t} failures: {f} errors: {e} skipped: {s}')
|
||||||
|
print(f'test classes with a problem: {len(classes)}')
|
||||||
|
print('distinct final causes (count = failing test methods):')
|
||||||
|
for msg,n in causes.most_common(): print(f' {n:>4} {msg}')
|
||||||
|
PY
|
||||||
|
fi
|
||||||
|
cat "$OUT/suite-$LABEL.txt"
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import org.hibernate.SessionFactory;
|
||||||
|
import org.hibernate.boot.MetadataSources;
|
||||||
|
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
|
||||||
|
|
||||||
|
/** Not part of the test run. Compiled against one Hibernate, then run on another to see linkage errors. */
|
||||||
|
public class BinaryProbe {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
var registry = new StandardServiceRegistryBuilder()
|
||||||
|
.applySetting("hibernate.connection.url", "jdbc:h2:mem:probe;DB_CLOSE_DELAY=-1")
|
||||||
|
.applySetting("hibernate.connection.username", "sa")
|
||||||
|
.applySetting("hibernate.hbm2ddl.auto", "create-drop").build();
|
||||||
|
try (SessionFactory sf = new MetadataSources(registry).addAnnotatedClass(com.ankurm.h8.Author.class)
|
||||||
|
.buildMetadata().buildSessionFactory()) {
|
||||||
|
try (var s = sf.openSession()) {
|
||||||
|
var q = s.createQuery("from Author", com.ankurm.h8.Author.class);
|
||||||
|
System.out.println("createQuery ran, rows = " + q.getResultList().size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import org.hibernate.Session;
|
||||||
|
import org.hibernate.query.Query;
|
||||||
|
|
||||||
|
/** Not built. Declaring the result of createQuery(hql, type) as a Hibernate Query is everywhere in old code. */
|
||||||
|
class QueryVariable {
|
||||||
|
|
||||||
|
int count(Session s) {
|
||||||
|
Query<com.ankurm.h8.Author> q = s.createQuery("from Author", com.ankurm.h8.Author.class);
|
||||||
|
return q.getResultList().size();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import org.hibernate.StatelessSession;
|
||||||
|
|
||||||
|
/** Not built. A common line in bulk-load code: keep the generated id that insert() returned. */
|
||||||
|
class StatelessInsertReturnsId {
|
||||||
|
|
||||||
|
Object load(StatelessSession ss) {
|
||||||
|
Object id = ss.insert(new com.ankurm.h8.Author("A", "[email protected]"));
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import jakarta.persistence.TypedQuery;
|
||||||
|
import org.hibernate.Session;
|
||||||
|
|
||||||
|
/** Not built. The same call assigned to the JPA interface instead. */
|
||||||
|
class TypedQueryVariable {
|
||||||
|
|
||||||
|
int count(Session s) {
|
||||||
|
TypedQuery<com.ankurm.h8.Author> q = s.createQuery("from Author", com.ankurm.h8.Author.class);
|
||||||
|
return q.getResultList().size();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import org.hibernate.ReplicationMode;
|
||||||
|
import org.hibernate.Session;
|
||||||
|
|
||||||
|
/** Not built. Session.replicate copied a detached graph between databases. */
|
||||||
|
class UsesReplicate {
|
||||||
|
|
||||||
|
void copy(Session s, Object detached) {
|
||||||
|
s.replicate(detached, ReplicationMode.OVERWRITE);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import org.hibernate.Session;
|
||||||
|
import org.hibernate.query.NativeQuery;
|
||||||
|
import org.hibernate.transform.Transformers;
|
||||||
|
|
||||||
|
/** Not built. Result transformers were the classic way to map a native query onto a DTO. */
|
||||||
|
class UsesTransformers {
|
||||||
|
|
||||||
|
public static class Dto {
|
||||||
|
public Integer id;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object rows(Session s) {
|
||||||
|
return s.createNativeQuery("select 1 as id", Object.class).unwrap(NativeQuery.class)
|
||||||
|
.setResultTransformer(Transformers.aliasToBean(Dto.class)).list();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.ankurm.h8;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.NamedQuery;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@NamedQuery(name = "Author.byName", query = "select a from Author a where a.name = :name")
|
||||||
|
public class Author {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue
|
||||||
|
Long id;
|
||||||
|
|
||||||
|
String name;
|
||||||
|
|
||||||
|
@Column(unique = true)
|
||||||
|
String email;
|
||||||
|
|
||||||
|
protected Author() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Author(String name, String email) {
|
||||||
|
this.name = name;
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.ankurm.h8;
|
||||||
|
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.ManyToOne;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
public class Child {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue
|
||||||
|
Long id;
|
||||||
|
|
||||||
|
String label;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
Parent parent;
|
||||||
|
|
||||||
|
protected Child() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Child(String label, Parent parent) {
|
||||||
|
this.label = label;
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.ankurm.h8;
|
||||||
|
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.NamedQuery;
|
||||||
|
|
||||||
|
/** Not registered by default: a named query that is an UPDATE. Hibernate 8 wants @NamedStatement for this. */
|
||||||
|
@Entity
|
||||||
|
@NamedQuery(name = "Mutating.rename", query = "update MutatingNamedQueryEntity m set m.name = :name")
|
||||||
|
public class MutatingNamedQueryEntity {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
Long id;
|
||||||
|
|
||||||
|
String name;
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.ankurm.h8;
|
||||||
|
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
public class Parent {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue
|
||||||
|
Long id;
|
||||||
|
|
||||||
|
String label;
|
||||||
|
|
||||||
|
protected Parent() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Parent(String label) {
|
||||||
|
this.label = label;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package com.ankurm.h8;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
import org.hibernate.SessionFactory;
|
||||||
|
import org.hibernate.boot.MetadataSources;
|
||||||
|
import org.hibernate.boot.registry.StandardServiceRegistry;
|
||||||
|
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
|
||||||
|
import org.hibernate.resource.jdbc.spi.StatementInspector;
|
||||||
|
|
||||||
|
/** Plain Hibernate bootstrap on an in-memory H2, no Spring, no persistence.xml. */
|
||||||
|
final class Db {
|
||||||
|
|
||||||
|
/** Every SQL string Hibernate prepares, in order. */
|
||||||
|
static final List<String> SQL = new ArrayList<>();
|
||||||
|
|
||||||
|
public static class Recorder implements StatementInspector {
|
||||||
|
@Override
|
||||||
|
public String inspect(String sql) {
|
||||||
|
SQL.add(sql);
|
||||||
|
return sql;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Db() {
|
||||||
|
}
|
||||||
|
|
||||||
|
static SessionFactory open(Class<?>... entities) {
|
||||||
|
return open(java.util.Map.of(), entities);
|
||||||
|
}
|
||||||
|
|
||||||
|
static SessionFactory open(java.util.Map<String, String> extra, Class<?>... entities) {
|
||||||
|
StandardServiceRegistryBuilder b = new StandardServiceRegistryBuilder()
|
||||||
|
.applySetting("hibernate.connection.url", "jdbc:h2:mem:" + UUID.randomUUID() + ";DB_CLOSE_DELAY=-1")
|
||||||
|
.applySetting("hibernate.connection.username", "sa")
|
||||||
|
.applySetting("hibernate.hbm2ddl.auto", "create-drop")
|
||||||
|
.applySetting("hibernate.session_factory.statement_inspector", Recorder.class.getName());
|
||||||
|
extra.forEach(b::applySetting);
|
||||||
|
StandardServiceRegistry registry = b.build();
|
||||||
|
MetadataSources sources = new MetadataSources(registry);
|
||||||
|
for (Class<?> e : entities) {
|
||||||
|
sources.addAnnotatedClass(e);
|
||||||
|
}
|
||||||
|
return sources.buildMetadata().buildSessionFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
static Throwable root(Throwable t) {
|
||||||
|
while (t.getCause() != null && t.getCause() != t) {
|
||||||
|
t = t.getCause();
|
||||||
|
}
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String describe(Throwable t) {
|
||||||
|
StringBuilder sb = new StringBuilder(t.getClass().getName());
|
||||||
|
Throwable c = t.getCause();
|
||||||
|
while (c != null && c != t) {
|
||||||
|
sb.append(" <- ").append(c.getClass().getName());
|
||||||
|
t = c;
|
||||||
|
c = c.getCause();
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package com.ankurm.h8;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import org.hibernate.SessionFactory;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
/** Same code on both versions; the transcript records what each throws or returns. */
|
||||||
|
class ExceptionContractsTest {
|
||||||
|
|
||||||
|
private static String attempt(Supplier<Object> action) {
|
||||||
|
try {
|
||||||
|
Object r = action.get();
|
||||||
|
return "returned " + r;
|
||||||
|
}
|
||||||
|
catch (RuntimeException ex) {
|
||||||
|
return "threw " + Db.describe(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void recordsExceptionContracts() {
|
||||||
|
Transcript t = new Transcript("01-exception-contracts");
|
||||||
|
try (SessionFactory sf = Db.open(Author.class)) {
|
||||||
|
// 1. execute a query whose named parameter was never bound
|
||||||
|
t.line("unbound parameter : " + attempt(() -> sf.fromSession(s -> s
|
||||||
|
.createQuery("select a from Author a where a.name = :name", Author.class).getResultList().size())));
|
||||||
|
// 2. bind a null
|
||||||
|
t.line("null parameter : " + attempt(() -> sf.fromSession(s -> s
|
||||||
|
.createQuery("select a from Author a where a.name = :name", Author.class)
|
||||||
|
.setParameter("name", (String) null).getResultList().size())));
|
||||||
|
// 3. bind a String to a Long
|
||||||
|
t.line("type mismatch : " + attempt(() -> sf.fromSession(s -> s
|
||||||
|
.createQuery("select a from Author a where a.id = :id", Author.class)
|
||||||
|
.setParameter("id", "not-a-number").getResultList().size())));
|
||||||
|
// 4. unique constraint violation on flush
|
||||||
|
t.line("unique violation : " + attempt(() -> sf.fromTransaction(s -> {
|
||||||
|
s.persist(new Author("A", "[email protected]"));
|
||||||
|
s.persist(new Author("B", "[email protected]"));
|
||||||
|
s.flush();
|
||||||
|
return "no exception";
|
||||||
|
})));
|
||||||
|
// 5. a parameter name that is not in the query
|
||||||
|
t.line("unknown parameter : " + attempt(() -> sf.fromSession(s -> s
|
||||||
|
.createQuery("select a from Author a where a.name = :name", Author.class)
|
||||||
|
.setParameter("nope", "x").getResultList().size())));
|
||||||
|
// 6. getSingleResult on no rows
|
||||||
|
List<Author> none = sf.fromSession(s -> s.createNamedQuery("Author.byName", Author.class)
|
||||||
|
.setParameter("name", "nobody").getResultList());
|
||||||
|
t.line("named query, no row: " + none.size() + " rows");
|
||||||
|
}
|
||||||
|
t.write();
|
||||||
|
assertThat(Transcript.LABEL).isNotBlank();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package com.ankurm.h8;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.hibernate.SessionFactory;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
/** Persist p1, c1, p2, c2 in that order and record the order in which Hibernate PREPARES its INSERT statements. */
|
||||||
|
class FlushOrderTest {
|
||||||
|
|
||||||
|
private static List<String> run(Map<String, String> settings) {
|
||||||
|
Db.SQL.clear();
|
||||||
|
try (SessionFactory sf = Db.open(settings, Parent.class, Child.class)) {
|
||||||
|
Db.SQL.clear();
|
||||||
|
sf.inTransaction(s -> {
|
||||||
|
Parent p1 = new Parent("p1");
|
||||||
|
Parent p2 = new Parent("p2");
|
||||||
|
s.persist(p1);
|
||||||
|
s.persist(new Child("c1", p1));
|
||||||
|
s.persist(p2);
|
||||||
|
s.persist(new Child("c2", p2));
|
||||||
|
});
|
||||||
|
return Db.SQL.stream().filter(q -> q.startsWith("insert")).map(q -> q.split(" ")[2]).toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void recordsInsertOrder() {
|
||||||
|
Transcript t = new Transcript("02-flush-order");
|
||||||
|
List<String> byDefault = run(Map.of());
|
||||||
|
t.line("default : " + byDefault);
|
||||||
|
List<String> legacy = run(Map.of("hibernate.flush.queue.type", "legacy"));
|
||||||
|
t.line("hibernate.flush.queue.type=legacy: " + legacy);
|
||||||
|
List<String> graph = run(Map.of("hibernate.flush.queue.type", "graph"));
|
||||||
|
t.line("hibernate.flush.queue.type=graph : " + graph);
|
||||||
|
t.write();
|
||||||
|
assertThat(byDefault).isNotEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.ankurm.h8;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
import org.hibernate.SessionFactory;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
/** A JPA @NamedQuery whose text is an UPDATE: accepted or rejected at start-up? */
|
||||||
|
class MutatingNamedQueryTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void recordsStartupOutcome() {
|
||||||
|
Transcript t = new Transcript("03-mutating-named-query");
|
||||||
|
String outcome;
|
||||||
|
try (SessionFactory sf = Db.open(MutatingNamedQueryEntity.class)) {
|
||||||
|
int rows = sf.fromTransaction(s -> s.createNamedQuery("Mutating.rename").setParameter("name", "x").executeUpdate());
|
||||||
|
outcome = "session factory built; createNamedQuery(..).executeUpdate() returned " + rows;
|
||||||
|
}
|
||||||
|
catch (RuntimeException ex) {
|
||||||
|
outcome = "threw " + Db.describe(ex) + " :: " + String.valueOf(Db.root(ex).getMessage()).lines().findFirst().orElse("");
|
||||||
|
}
|
||||||
|
t.line(outcome);
|
||||||
|
t.write();
|
||||||
|
assertThat(outcome).isNotBlank();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.ankurm.h8;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/** Collects lines and writes output/NN-name-<label>.txt (label = h8 or h7). */
|
||||||
|
final class Transcript {
|
||||||
|
|
||||||
|
static final String LABEL = System.getProperty("h.label", "h8");
|
||||||
|
static final String VERSION = System.getProperty("h.version", "?");
|
||||||
|
|
||||||
|
private final List<String> lines = new ArrayList<>();
|
||||||
|
private final Path file;
|
||||||
|
|
||||||
|
Transcript(String name) {
|
||||||
|
this.file = Path.of("output", name + "-" + LABEL + ".txt");
|
||||||
|
line("# " + name + " (Hibernate ORM " + VERSION + ", JDK " + Runtime.version().feature() + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
Transcript line(String s) {
|
||||||
|
lines.add(s);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void write() {
|
||||||
|
try {
|
||||||
|
Files.createDirectories(file.getParent());
|
||||||
|
Files.write(file, lines);
|
||||||
|
}
|
||||||
|
catch (IOException ex) {
|
||||||
|
throw new IllegalStateException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user