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