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,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