Add migration-5-to-7 module: Hibernate 5.6.15 vs 7.4.14 jar diff, compile checks, schema defaults and cheat-sheet snippets
Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01YPgGH6aMbSLANL3E2HHmS6
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
# migration-5-to-7
|
||||
|
||||
Hibernate ORM **5.6.15.Final** (`org.hibernate:hibernate-core`, `javax.persistence`) against **7.4.14.Final**
|
||||
(`org.hibernate.orm:hibernate-core`, `jakarta.persistence`), on in-memory and file H2 2.4.240. Companion to the
|
||||
Hibernate 5 to 7 migration guide and cheat sheet on [ankurm.com](https://ankurm.com).
|
||||
|
||||
| path | what it is |
|
||||
|---|---|
|
||||
| `h5/` | the "before" side: the same probes on Hibernate 5.6.15 |
|
||||
| `h7/` | the "after" side: the probes plus the cheat-sheet snippets (`CheatSheetTest`) on Hibernate 7.4.14 |
|
||||
| `src/compat/` | Hibernate 5 style and Hibernate 7 style sources that are only compiled, never run |
|
||||
| `scripts/api_diff.py` | jar diff of the two `hibernate-core` jars (`javax.persistence` read as `jakarta.persistence`) |
|
||||
| `scripts/compile-old-apis.sh` | compiles `src/compat` against both versions |
|
||||
| `scripts/schema_diff.py` | lines up the two `06-schema-*` files |
|
||||
| `scripts/run-all.sh` | everything above; rewrites `output/` |
|
||||
|
||||
Needs JDK 17 or newer (built with 25), Maven 3.9, Python 3 and access to Maven Central. Hibernate 5.6.15 pulls Byte Buddy
|
||||
1.12.18, which does not know a current JDK, so `h5/pom.xml` pins a newer Byte Buddy.
|
||||
|
||||
`output/02-removed-api.txt` lists exact signatures that disappeared (generics and `throws` ignored, internal and `spi`
|
||||
packages left out). Parameter type changes such as `Serializable` to `Object` count as removed there, so treat it as a list to
|
||||
check, not a list of deletions; the compile checks in `output/03` to `output/09` are the arbiter.
|
||||
@@ -0,0 +1,64 @@
|
||||
<?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>migration-h5</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<name>migration-h5</name>
|
||||
<description>The "before" side: Hibernate ORM 5.6.15.Final with javax.persistence, run against in-memory H2</description>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.release>17</maven.compiler.release>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<hibernate.version>5.6.15.Final</hibernate.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<!-- Hibernate 5.6.15 pulls Byte Buddy 1.12.18, which does not know the JDK this runs on; a current one does -->
|
||||
<dependency>
|
||||
<groupId>net.bytebuddy</groupId>
|
||||
<artifactId>byte-buddy</artifactId>
|
||||
<version>1.17.7</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<!-- note the group id: org.hibernate here, org.hibernate.orm from 6.0 on -->
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-core</artifactId>
|
||||
<version>${hibernate.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.1.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>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.ankurm.migration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.criterion.Restrictions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** The same probes as the Hibernate 7 module, run on 5.6.15.Final. Output goes to ../output. */
|
||||
class BaselineTest {
|
||||
|
||||
private static final String V = "5.6.15";
|
||||
|
||||
@Test
|
||||
void schemaDefaults() {
|
||||
try (Db db = new Db(Probe.class); Transcript t = new Transcript("06-schema-" + V)) {
|
||||
t.line("Hibernate " + V + ", H2, @GeneratedValue with no strategy");
|
||||
t.line("-- sequences (name, increment)");
|
||||
List<String> seq = db.jdbc("select sequence_name, increment from information_schema.sequences order by 1");
|
||||
seq.forEach(t::line);
|
||||
t.line("-- columns of PROBE (name, type)");
|
||||
List<String> cols = db.jdbc("select column_name, data_type from information_schema.columns where table_name = 'PROBE' order by column_name");
|
||||
cols.forEach(t::line);
|
||||
assertThat(seq).isNotEmpty();
|
||||
assertThat(cols).hasSize(11);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void legacyQueries() {
|
||||
try (Db db = new Db(Probe.class); Transcript t = new Transcript("07-legacy-queries-" + V)) {
|
||||
t.line("Hibernate " + V);
|
||||
try (Session s = db.sessionFactory.openSession()) {
|
||||
s.beginTransaction();
|
||||
Serializable id = s.save(new Probe("alpha"));
|
||||
t.line("session.save(...) returned: " + id + " (" + id.getClass().getSimpleName() + ")");
|
||||
s.getTransaction().commit();
|
||||
assertThat(id).isNotNull();
|
||||
}
|
||||
try (Session s = db.sessionFactory.openSession()) {
|
||||
String ordinal = outcome(() -> s.createQuery("from Probe p where p.name = ?1").setParameter(1, "alpha").list());
|
||||
t.line("HQL with ?1 : " + ordinal);
|
||||
String bare = outcome(() -> s.createQuery("from Probe p where p.name = ?").setParameter(0, "alpha").list());
|
||||
t.line("HQL with a bare ? : " + bare);
|
||||
String enumInt = outcome(() -> s.createQuery("from Probe p where p.byOrdinal = 0").list());
|
||||
t.line("HQL enum compared with 0 : " + enumInt);
|
||||
String criteria = outcome(() -> s.createCriteria(Probe.class).add(Restrictions.eq("name", "alpha")).list());
|
||||
t.line("session.createCriteria(...) : " + criteria);
|
||||
assertThat(ordinal).startsWith("1 row");
|
||||
assertThat(bare).startsWith("failed");
|
||||
assertThat(enumInt).startsWith("0 row");
|
||||
assertThat(criteria).startsWith("1 row");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static String outcome(java.util.function.Supplier<List<?>> query) {
|
||||
try {
|
||||
return query.get().size() + " row(s)";
|
||||
} catch (RuntimeException e) {
|
||||
String msg = String.valueOf(e.getMessage()).lines().findFirst().orElse("");
|
||||
return "failed: " + e.getClass().getSimpleName() + ": " + (msg.length() > 140 ? msg.substring(0, 140) + "..." : msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.ankurm.migration;
|
||||
|
||||
import org.hibernate.SessionFactory;
|
||||
import org.hibernate.boot.MetadataSources;
|
||||
import org.hibernate.boot.registry.StandardServiceRegistry;
|
||||
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/** Plain-Hibernate bootstrap against in-memory H2. */
|
||||
final class Db implements AutoCloseable {
|
||||
|
||||
private static final AtomicInteger SEQ = new AtomicInteger();
|
||||
final SessionFactory sessionFactory;
|
||||
private final String url;
|
||||
|
||||
Db(Class<?>... entities) {
|
||||
this.url = "jdbc:h2:mem:mig" + SEQ.incrementAndGet() + ";DB_CLOSE_DELAY=-1";
|
||||
StandardServiceRegistry registry = new StandardServiceRegistryBuilder()
|
||||
.applySetting("hibernate.connection.url", url)
|
||||
.applySetting("hibernate.connection.username", "sa")
|
||||
.applySetting("hibernate.connection.password", "")
|
||||
.applySetting("hibernate.hbm2ddl.auto", "create-drop")
|
||||
.build();
|
||||
MetadataSources sources = new MetadataSources(registry);
|
||||
for (Class<?> e : entities) {
|
||||
sources.addAnnotatedClass(e);
|
||||
}
|
||||
this.sessionFactory = sources.buildMetadata().buildSessionFactory();
|
||||
}
|
||||
|
||||
/** Raw JDBC against the same in-memory database; columns joined with a space. */
|
||||
List<String> jdbc(String query) {
|
||||
List<String> rows = new ArrayList<>();
|
||||
try (Connection c = DriverManager.getConnection(url, "sa", "");
|
||||
Statement st = c.createStatement();
|
||||
ResultSet rs = st.executeQuery(query)) {
|
||||
int n = rs.getMetaData().getColumnCount();
|
||||
while (rs.next()) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 1; i <= n; i++) {
|
||||
if (i > 1) {
|
||||
sb.append(' ');
|
||||
}
|
||||
sb.append(rs.getString(i));
|
||||
}
|
||||
rows.add(sb.toString());
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
sessionFactory.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.ankurm.migration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.SessionFactory;
|
||||
import org.hibernate.boot.MetadataSources;
|
||||
import org.hibernate.boot.registry.StandardServiceRegistry;
|
||||
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** Creates the schema Hibernate 5.6.15 generates, in an H2 file database that the Hibernate 7 module opens later. */
|
||||
class LegacySchemaTest {
|
||||
|
||||
@Test
|
||||
void createSchemaFile() throws Exception {
|
||||
Path dir = Path.of("..", "target", "legacy-db");
|
||||
Files.createDirectories(dir);
|
||||
for (var f : dir.toFile().listFiles()) {
|
||||
f.delete();
|
||||
}
|
||||
StandardServiceRegistry registry = new StandardServiceRegistryBuilder()
|
||||
.applySetting("hibernate.connection.url", "jdbc:h2:file:" + dir.toAbsolutePath().normalize() + "/probe")
|
||||
.applySetting("hibernate.connection.username", "sa")
|
||||
.applySetting("hibernate.connection.password", "")
|
||||
.applySetting("hibernate.hbm2ddl.auto", "create")
|
||||
.build();
|
||||
try (SessionFactory sf = new MetadataSources(registry).addAnnotatedClass(Probe.class).buildMetadata().buildSessionFactory();
|
||||
Session s = sf.openSession()) {
|
||||
s.beginTransaction();
|
||||
s.persist(new Probe("legacy row"));
|
||||
s.getTransaction().commit();
|
||||
}
|
||||
assertThat(Files.exists(dir.resolve("probe.mv.db"))).isTrue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.ankurm.migration;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.EnumType;
|
||||
import javax.persistence.Enumerated;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
/** One field of each type whose default column mapping is worth comparing, and a generated id. */
|
||||
@Entity
|
||||
public class Probe {
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
Long id;
|
||||
|
||||
String name;
|
||||
boolean active;
|
||||
Instant createdAt;
|
||||
LocalDateTime occurredAt;
|
||||
UUID uuid;
|
||||
Duration span;
|
||||
BigDecimal price;
|
||||
byte[] payload;
|
||||
|
||||
@Enumerated(EnumType.ORDINAL)
|
||||
Colour byOrdinal;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
Colour byName;
|
||||
|
||||
public enum Colour { RED, GREEN }
|
||||
|
||||
public Probe() {
|
||||
}
|
||||
|
||||
Probe(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.ankurm.migration;
|
||||
|
||||
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 them to output/NN-name.txt when closed. The tests assert what they print. */
|
||||
final class Transcript implements AutoCloseable {
|
||||
|
||||
private final Path file;
|
||||
private final List<String> lines = new ArrayList<>();
|
||||
|
||||
Transcript(String name) {
|
||||
this.file = Path.of("..", "output", name + ".txt");
|
||||
}
|
||||
|
||||
Transcript line(String s) {
|
||||
lines.add(s);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
Files.createDirectories(file.getParent());
|
||||
Files.write(file, lines);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?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>migration-h7</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<name>migration-h7</name>
|
||||
<description>The "after" side: Hibernate ORM 7.4.14.Final with jakarta.persistence, run against in-memory H2</description>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.release>17</maven.compiler.release>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<hibernate.version>7.4.14.Final</hibernate.version>
|
||||
</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>3.2.0</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.1.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>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.ankurm.migration;
|
||||
|
||||
import jakarta.persistence.Embeddable;
|
||||
|
||||
/** A Java record used directly as an embeddable. */
|
||||
@Embeddable
|
||||
public record Address(String street, String city) {
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.ankurm.migration;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
import org.hibernate.annotations.NaturalId;
|
||||
|
||||
@Entity
|
||||
public class Author {
|
||||
@Id
|
||||
@GeneratedValue
|
||||
Long id;
|
||||
|
||||
@NaturalId
|
||||
String email;
|
||||
|
||||
String name;
|
||||
|
||||
public Author() {
|
||||
}
|
||||
|
||||
Author(String email, String name) {
|
||||
this.email = email;
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.ankurm.migration;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
|
||||
@Entity
|
||||
public class Book {
|
||||
@Id
|
||||
@GeneratedValue
|
||||
Long id;
|
||||
|
||||
String title;
|
||||
Integer pages;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
Author author;
|
||||
|
||||
public Book() {
|
||||
}
|
||||
|
||||
Book(String title, Integer pages, Author author) {
|
||||
this.title = title;
|
||||
this.pages = pages;
|
||||
this.author = author;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
package com.ankurm.migration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.Hibernate;
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.StatelessSession;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** Snippets for the cheat sheet. Each test prints what it did to ../output/1N-cheat-*.txt and asserts the same values. */
|
||||
class CheatSheetTest {
|
||||
|
||||
@Test
|
||||
void sessionApi() {
|
||||
try (Db db = new Db(Author.class, Book.class); Transcript t = new Transcript("10-cheat-session-api")) {
|
||||
Long id;
|
||||
try (Session s = db.sessionFactory.openSession()) {
|
||||
s.beginTransaction();
|
||||
Author a = new Author("[email protected]", "Ann");
|
||||
s.persist(a);
|
||||
id = a.id;
|
||||
s.getTransaction().commit();
|
||||
t.line("persist(a) -> id assigned on the object: " + id);
|
||||
}
|
||||
try (Session s = db.sessionFactory.openSession()) {
|
||||
db.clear();
|
||||
Author found = s.find(Author.class, id);
|
||||
t.line("find(Author, id) -> " + found.name + " (" + db.sql.size() + " select)");
|
||||
assertThat(found.name).isEqualTo("Ann");
|
||||
assertThat(db.sql).hasSize(1);
|
||||
}
|
||||
try (Session s = db.sessionFactory.openSession()) {
|
||||
db.clear();
|
||||
Author ref = s.getReference(Author.class, id);
|
||||
t.line("getReference(Author, id) -> initialized: " + Hibernate.isInitialized(ref) + " (" + db.sql.size() + " selects)");
|
||||
assertThat(Hibernate.isInitialized(ref)).isFalse();
|
||||
assertThat(db.sql).isEmpty();
|
||||
}
|
||||
try (Session s = db.sessionFactory.openSession()) {
|
||||
Author detached = new Author("[email protected]", "Ann Smith");
|
||||
detached.id = id;
|
||||
s.beginTransaction();
|
||||
Author managed = s.merge(detached);
|
||||
t.line("merge(detached) -> returns a different instance: " + (managed != detached) + ", name now " + managed.name);
|
||||
s.getTransaction().commit();
|
||||
assertThat(managed).isNotSameAs(detached);
|
||||
assertThat(managed.name).isEqualTo("Ann Smith");
|
||||
}
|
||||
try (Session s = db.sessionFactory.openSession()) {
|
||||
s.beginTransaction();
|
||||
Author extra = new Author("[email protected]", "Bob");
|
||||
s.persist(extra);
|
||||
s.getTransaction().commit();
|
||||
Long bobId = extra.id;
|
||||
List<Author> many = s.findMultiple(Author.class, List.of(id, bobId, 999L));
|
||||
t.line("findMultiple(ids 3) -> " + many.size() + " entries, missing id gives: " + many.get(2));
|
||||
assertThat(many).hasSize(3);
|
||||
assertThat(many.get(2)).isNull();
|
||||
}
|
||||
try (Session s = db.sessionFactory.openSession()) {
|
||||
s.beginTransaction();
|
||||
Author a = s.find(Author.class, id);
|
||||
s.remove(a);
|
||||
s.getTransaction().commit();
|
||||
t.line("remove(a) -> find afterwards: " + s.find(Author.class, id));
|
||||
assertThat(s.find(Author.class, id)).isNull();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void hql() {
|
||||
try (Db db = new Db(Author.class, Book.class); Transcript t = new Transcript("11-cheat-hql")) {
|
||||
try (Session s = db.sessionFactory.openSession()) {
|
||||
s.beginTransaction();
|
||||
Author ann = new Author("[email protected]", "Ann");
|
||||
Author bob = new Author("[email protected]", "Bob");
|
||||
s.persist(ann);
|
||||
s.persist(bob);
|
||||
s.persist(new Book("Alpha", 100, ann));
|
||||
s.persist(new Book("Beta", 300, ann));
|
||||
s.persist(new Book("Gamma", null, bob));
|
||||
s.getTransaction().commit();
|
||||
}
|
||||
try (Session s = db.sessionFactory.openSession()) {
|
||||
List<String> named = s.createSelectionQuery("select b.title from Book b where b.pages > :min order by b.title", String.class)
|
||||
.setParameter("min", 150).getResultList();
|
||||
t.line("named parameter : " + named);
|
||||
assertThat(named).containsExactly("Beta");
|
||||
|
||||
List<String> ordinal = s.createSelectionQuery("select b.title from Book b where b.pages > ?1 order by b.title", String.class)
|
||||
.setParameter(1, 50).getResultList();
|
||||
t.line("?1 parameter : " + ordinal);
|
||||
assertThat(ordinal).containsExactly("Alpha", "Beta");
|
||||
|
||||
List<String> page = s.createSelectionQuery("select b.title from Book b order by b.title limit 2 offset 1", String.class).getResultList();
|
||||
t.line("limit 2 offset 1 in HQL : " + page);
|
||||
assertThat(page).containsExactly("Beta", "Gamma");
|
||||
|
||||
List<String> nulls = s.createSelectionQuery("select b.title from Book b order by b.pages desc nulls last", String.class).getResultList();
|
||||
t.line("order by ... nulls last : " + nulls);
|
||||
assertThat(nulls).containsExactly("Beta", "Alpha", "Gamma");
|
||||
|
||||
Book none = s.createSelectionQuery("from Book b where b.title = 'Nope'", Book.class).getSingleResultOrNull();
|
||||
t.line("getSingleResultOrNull() : " + none);
|
||||
assertThat(none).isNull();
|
||||
|
||||
List<Book> fetched = s.createSelectionQuery("select b from Book b join fetch b.author where b.title = 'Alpha'", Book.class).getResultList();
|
||||
t.line("join fetch : author initialized = " + Hibernate.isInitialized(fetched.get(0).author));
|
||||
assertThat(Hibernate.isInitialized(fetched.get(0).author)).isTrue();
|
||||
|
||||
Long count = s.createSelectionQuery("select count(b) from Book b where b.author.name = :n", Long.class).setParameter("n", "Ann").getSingleResult();
|
||||
t.line("count over an association : " + count);
|
||||
assertThat(count).isEqualTo(2L);
|
||||
}
|
||||
try (Session s = db.sessionFactory.openSession()) {
|
||||
s.beginTransaction();
|
||||
int updated = s.createMutationQuery("update Book b set b.pages = b.pages + 1 where b.pages is not null").executeUpdate();
|
||||
s.getTransaction().commit();
|
||||
t.line("createMutationQuery update: " + updated + " rows");
|
||||
assertThat(updated).isEqualTo(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void statelessSession() {
|
||||
try (Db db = new Db(Author.class); Transcript t = new Transcript("12-cheat-stateless-session")) {
|
||||
try (StatelessSession s = db.sessionFactory.openStatelessSession()) {
|
||||
s.beginTransaction();
|
||||
for (int i = 1; i <= 3; i++) {
|
||||
s.insert(new Author("user" + i + "@example.com", "User " + i));
|
||||
}
|
||||
s.getTransaction().commit();
|
||||
t.line("insert x3 (no persistence context) done");
|
||||
}
|
||||
try (StatelessSession s = db.sessionFactory.openStatelessSession()) {
|
||||
Author a1 = s.get(Author.class, 1L);
|
||||
Author a1again = s.get(Author.class, 1L);
|
||||
t.line("get twice -> same instance: " + (a1 == a1again) + " (a stateless session has no first-level cache)");
|
||||
assertThat(a1).isNotNull();
|
||||
assertThat(a1).isNotSameAs(a1again);
|
||||
t.line("rows in table: " + db.jdbc("select count(*) from Author"));
|
||||
assertThat(db.jdbc("select count(*) from Author")).containsExactly("3");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void naturalId() {
|
||||
try (Db db = new Db(Author.class); Transcript t = new Transcript("13-cheat-natural-id")) {
|
||||
try (Session s = db.sessionFactory.openSession()) {
|
||||
s.beginTransaction();
|
||||
s.persist(new Author("[email protected]", "Ann"));
|
||||
s.getTransaction().commit();
|
||||
}
|
||||
try (Session s = db.sessionFactory.openSession()) {
|
||||
db.clear();
|
||||
Author a = s.bySimpleNaturalId(Author.class).load("[email protected]");
|
||||
t.line("bySimpleNaturalId(...).load -> " + a.name);
|
||||
t.line("sql: " + db.sql.get(0));
|
||||
assertThat(a.name).isEqualTo("Ann");
|
||||
assertThat(db.sql.get(0)).contains("email");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void softDelete() {
|
||||
try (Db db = new Db(Note.class); Transcript t = new Transcript("14-cheat-soft-delete")) {
|
||||
Long id;
|
||||
try (Session s = db.sessionFactory.openSession()) {
|
||||
s.beginTransaction();
|
||||
Note n = new Note("remember");
|
||||
s.persist(n);
|
||||
id = n.id;
|
||||
s.getTransaction().commit();
|
||||
}
|
||||
try (Session s = db.sessionFactory.openSession()) {
|
||||
s.beginTransaction();
|
||||
db.clear();
|
||||
s.remove(s.find(Note.class, id));
|
||||
s.getTransaction().commit();
|
||||
t.line("remove(note) ran: " + db.sql.get(db.sql.size() - 1));
|
||||
assertThat(db.sql.get(db.sql.size() - 1)).startsWith("update");
|
||||
}
|
||||
try (Session s = db.sessionFactory.openSession()) {
|
||||
t.line("find afterwards : " + s.find(Note.class, id));
|
||||
t.line("row still in the table: " + db.jdbc("select * from Note"));
|
||||
assertThat(s.find(Note.class, id)).isNull();
|
||||
assertThat(db.jdbc("select count(*) from Note")).containsExactly("1");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void recordEmbeddable() {
|
||||
try (Db db = new Db(Shop.class); Transcript t = new Transcript("15-cheat-record-embeddable")) {
|
||||
Long id;
|
||||
try (Session s = db.sessionFactory.openSession()) {
|
||||
s.beginTransaction();
|
||||
Shop shop = new Shop("Corner", new Address("1 Main St", "Pune"));
|
||||
s.persist(shop);
|
||||
id = shop.id;
|
||||
s.getTransaction().commit();
|
||||
}
|
||||
try (Session s = db.sessionFactory.openSession()) {
|
||||
Shop shop = s.find(Shop.class, id);
|
||||
t.line("loaded: " + shop.address);
|
||||
t.line("columns: " + db.jdbc("select column_name from information_schema.columns where table_name = 'SHOP' order by column_name"));
|
||||
assertThat(shop.address).isEqualTo(new Address("1 Main St", "Pune"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.ankurm.migration;
|
||||
|
||||
import org.hibernate.SessionFactory;
|
||||
import org.hibernate.resource.jdbc.spi.StatementInspector;
|
||||
import org.hibernate.boot.MetadataSources;
|
||||
import org.hibernate.boot.registry.StandardServiceRegistry;
|
||||
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/** Plain-Hibernate bootstrap against in-memory H2. */
|
||||
final class Db implements AutoCloseable {
|
||||
|
||||
private static final AtomicInteger SEQ = new AtomicInteger();
|
||||
final List<String> sql = java.util.Collections.synchronizedList(new ArrayList<>());
|
||||
final SessionFactory sessionFactory;
|
||||
private final String url;
|
||||
|
||||
Db(Class<?>... entities) {
|
||||
this.url = "jdbc:h2:mem:mig" + SEQ.incrementAndGet() + ";DB_CLOSE_DELAY=-1";
|
||||
StandardServiceRegistry registry = new StandardServiceRegistryBuilder()
|
||||
.applySetting("jakarta.persistence.jdbc.url", url)
|
||||
.applySetting("jakarta.persistence.jdbc.user", "sa")
|
||||
.applySetting("jakarta.persistence.jdbc.password", "")
|
||||
.applySetting("hibernate.hbm2ddl.auto", "create-drop")
|
||||
.applySetting("hibernate.session_factory.statement_inspector", (StatementInspector) x -> {
|
||||
sql.add(x);
|
||||
return x;
|
||||
})
|
||||
.build();
|
||||
MetadataSources sources = new MetadataSources(registry);
|
||||
for (Class<?> e : entities) {
|
||||
sources.addAnnotatedClass(e);
|
||||
}
|
||||
this.sessionFactory = sources.buildMetadata().buildSessionFactory();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
sql.clear();
|
||||
}
|
||||
|
||||
/** Raw JDBC against the same in-memory database; columns joined with a space. */
|
||||
List<String> jdbc(String query) {
|
||||
List<String> rows = new ArrayList<>();
|
||||
try (Connection c = DriverManager.getConnection(url, "sa", "");
|
||||
Statement st = c.createStatement();
|
||||
ResultSet rs = st.executeQuery(query)) {
|
||||
int n = rs.getMetaData().getColumnCount();
|
||||
while (rs.next()) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 1; i <= n; i++) {
|
||||
if (i > 1) {
|
||||
sb.append(' ');
|
||||
}
|
||||
sb.append(rs.getString(i));
|
||||
}
|
||||
rows.add(sb.toString());
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
sessionFactory.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.ankurm.migration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.Session;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** The same probes as the Hibernate 5 module, run on 7.4.14.Final. Output goes to ../output. */
|
||||
class MigrationProbeTest {
|
||||
|
||||
private static final String V = "7.4.14";
|
||||
|
||||
@Test
|
||||
void schemaDefaults() {
|
||||
try (Db db = new Db(Probe.class); Transcript t = new Transcript("06-schema-" + V)) {
|
||||
t.line("Hibernate " + V + ", H2, @GeneratedValue with no strategy");
|
||||
t.line("-- sequences (name, increment)");
|
||||
List<String> seq = db.jdbc("select sequence_name, increment from information_schema.sequences order by 1");
|
||||
seq.forEach(t::line);
|
||||
t.line("-- columns of PROBE (name, type)");
|
||||
List<String> cols = db.jdbc("select column_name, data_type from information_schema.columns where table_name = 'PROBE' order by column_name");
|
||||
cols.forEach(t::line);
|
||||
assertThat(seq).isNotEmpty();
|
||||
assertThat(cols).hasSize(11);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void legacyQueries() {
|
||||
try (Db db = new Db(Probe.class); Transcript t = new Transcript("07-legacy-queries-" + V)) {
|
||||
t.line("Hibernate " + V);
|
||||
try (Session s = db.sessionFactory.openSession()) {
|
||||
s.beginTransaction();
|
||||
Probe p = new Probe("alpha");
|
||||
s.persist(p);
|
||||
t.line("session.persist(...) assigned the id: " + p.id + " (" + p.id.getClass().getSimpleName() + ")");
|
||||
s.getTransaction().commit();
|
||||
assertThat(p.id).isNotNull();
|
||||
}
|
||||
try (Session s = db.sessionFactory.openSession()) {
|
||||
String ordinal = outcome(() -> s.createQuery("from Probe p where p.name = ?1").setParameter(1, "alpha").list());
|
||||
t.line("HQL with ?1 : " + ordinal);
|
||||
String bare = outcome(() -> s.createQuery("from Probe p where p.name = ?").setParameter(0, "alpha").list());
|
||||
t.line("HQL with a bare ? : " + bare);
|
||||
String enumInt = outcome(() -> s.createQuery("from Probe p where p.byOrdinal = 0").list());
|
||||
t.line("HQL enum compared with 0 : " + enumInt);
|
||||
assertThat(ordinal).startsWith("1 row");
|
||||
assertThat(bare).startsWith("failed");
|
||||
assertThat(enumInt).startsWith("0 row");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static String outcome(java.util.function.Supplier<List<?>> query) {
|
||||
try {
|
||||
return query.get().size() + " row(s)";
|
||||
} catch (RuntimeException e) {
|
||||
String msg = String.valueOf(e.getMessage()).lines().findFirst().orElse("");
|
||||
return "failed: " + e.getClass().getSimpleName() + ": " + (msg.length() > 140 ? msg.substring(0, 140) + "..." : msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.ankurm.migration;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
import org.hibernate.annotations.SoftDelete;
|
||||
|
||||
@Entity
|
||||
@SoftDelete
|
||||
public class Note {
|
||||
@Id
|
||||
@GeneratedValue
|
||||
Long id;
|
||||
|
||||
String text;
|
||||
|
||||
public Note() {
|
||||
}
|
||||
|
||||
Note(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.ankurm.migration;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EnumType;
|
||||
import jakarta.persistence.Enumerated;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
/** One field of each type whose default column mapping is worth comparing, and a generated id. */
|
||||
@Entity
|
||||
public class Probe {
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
Long id;
|
||||
|
||||
String name;
|
||||
boolean active;
|
||||
Instant createdAt;
|
||||
LocalDateTime occurredAt;
|
||||
UUID uuid;
|
||||
Duration span;
|
||||
BigDecimal price;
|
||||
byte[] payload;
|
||||
|
||||
@Enumerated(EnumType.ORDINAL)
|
||||
Colour byOrdinal;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
Colour byName;
|
||||
|
||||
public enum Colour { RED, GREEN }
|
||||
|
||||
public Probe() {
|
||||
}
|
||||
|
||||
Probe(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.ankurm.migration;
|
||||
|
||||
import jakarta.persistence.Embedded;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
|
||||
@Entity
|
||||
public class Shop {
|
||||
@Id
|
||||
@GeneratedValue
|
||||
Long id;
|
||||
|
||||
String name;
|
||||
|
||||
@Embedded
|
||||
Address address;
|
||||
|
||||
public Shop() {
|
||||
}
|
||||
|
||||
Shop(String name, Address address) {
|
||||
this.name = name;
|
||||
this.address = address;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.ankurm.migration;
|
||||
|
||||
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 them to output/NN-name.txt when closed. The tests assert what they print. */
|
||||
final class Transcript implements AutoCloseable {
|
||||
|
||||
private final Path file;
|
||||
private final List<String> lines = new ArrayList<>();
|
||||
|
||||
Transcript(String name) {
|
||||
this.file = Path.of("..", "output", name + ".txt");
|
||||
}
|
||||
|
||||
Transcript line(String s) {
|
||||
lines.add(s);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
Files.createDirectories(file.getParent());
|
||||
Files.write(file, lines);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.ankurm.migration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import org.hibernate.SessionFactory;
|
||||
import org.hibernate.boot.MetadataSources;
|
||||
import org.hibernate.boot.registry.StandardServiceRegistry;
|
||||
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** Opens the database the Hibernate 5.6.15 module created, with the same entity, and asks Hibernate 7 to validate it. */
|
||||
class ValidateLegacySchemaTest {
|
||||
|
||||
@Test
|
||||
void validateAndRunAgainstSchemaCreatedByHibernate5() throws Exception {
|
||||
Path dir = Path.of("..", "target", "legacy-db");
|
||||
assumeTrue(Files.exists(dir.resolve("probe.mv.db")), "run the h5 module first (scripts/run-all.sh does)");
|
||||
String url = "jdbc:h2:file:" + dir.toAbsolutePath().normalize() + "/probe";
|
||||
try (Transcript t = new Transcript("08-validate-against-5.6.15-schema-7.4.14")) {
|
||||
t.line("Hibernate 7.4.14 opening the H2 file database written by Hibernate 5.6.15 (same Probe entity)");
|
||||
String validate = open(url, "validate");
|
||||
t.line("hbm2ddl.auto=validate: " + validate);
|
||||
assertThat(validate).startsWith("failed");
|
||||
String none = open(url, "none");
|
||||
t.line("hbm2ddl.auto=none : " + none);
|
||||
}
|
||||
}
|
||||
|
||||
private static String open(String url, String mode) {
|
||||
StandardServiceRegistry registry = new StandardServiceRegistryBuilder()
|
||||
.applySetting("jakarta.persistence.jdbc.url", url)
|
||||
.applySetting("jakarta.persistence.jdbc.user", "sa")
|
||||
.applySetting("jakarta.persistence.jdbc.password", "")
|
||||
.applySetting("hibernate.hbm2ddl.auto", mode)
|
||||
.build();
|
||||
try (SessionFactory sf = new MetadataSources(registry).addAnnotatedClass(Probe.class).buildMetadata().buildSessionFactory()) {
|
||||
try (var s = sf.openSession()) {
|
||||
Probe p = s.createSelectionQuery("from Probe", Probe.class).getResultList().get(0);
|
||||
return "opened; loaded the row written by 5.6.15: name=" + p.name + ", uuid=" + p.uuid;
|
||||
} catch (RuntimeException e) {
|
||||
return "opened, but the first query failed: " + e.getClass().getSimpleName() + ": " + first(e);
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
return "failed: " + e.getClass().getSimpleName() + ": " + first(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String first(Throwable e) {
|
||||
String m = String.valueOf(e.getMessage()).lines().findFirst().orElse("");
|
||||
return m.length() > 220 ? m.substring(0, 220) + "..." : m;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
5.x 7.x
|
||||
coordinates org.hibernate:hibernate-core:5.6.15.Final org.hibernate.orm:hibernate-core:7.4.14.Final
|
||||
class files Java 8 Java 17
|
||||
persistence javax.persistence (JPA 2.2) jakarta.persistence (Jakarta Persistence 3.2)
|
||||
@@ -0,0 +1,15 @@
|
||||
== hibernate-core: public classes missing: 828, members removed from classes that remain: 2735, new classes: 5879
|
||||
of the missing classes: 59 were deprecated in 5.6.15, 137 have a class of the same simple name somewhere in 7.4.14
|
||||
packages with the most missing classes:
|
||||
75 org.hibernate.dialect
|
||||
74 org.hibernate.type
|
||||
62 org.hibernate.type.descriptor.java
|
||||
59 org.hibernate.criterion
|
||||
53 org.hibernate.cfg
|
||||
36 org.hibernate.type.descriptor.sql
|
||||
35 org.hibernate.annotations
|
||||
27 org.hibernate.dialect.function
|
||||
25 org.hibernate.sql.ordering.antlr
|
||||
23 org.hibernate.sql
|
||||
23 org.hibernate.tool.hbm2ddl
|
||||
22 org.hibernate.cfg.annotations
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,2 @@
|
||||
Hibernate 5.6.15, compiling OldHibernate.java
|
||||
javac exit status: 0
|
||||
@@ -0,0 +1,63 @@
|
||||
Hibernate 7.4.14, compiling OldHibernate.java
|
||||
src/compat/OldHibernate.java:4: error: cannot find symbol
|
||||
import org.hibernate.Criteria;
|
||||
^
|
||||
symbol: class Criteria
|
||||
location: package org.hibernate
|
||||
src/compat/OldHibernate.java:8: error: package org.hibernate.criterion does not exist
|
||||
import org.hibernate.criterion.Restrictions;
|
||||
^
|
||||
src/compat/OldHibernate.java:15: error: cannot find symbol
|
||||
return s.save(o);
|
||||
^
|
||||
symbol: method save(Object)
|
||||
location: variable s of type Session
|
||||
src/compat/OldHibernate.java:19: error: cannot find symbol
|
||||
s.update(o);
|
||||
^
|
||||
symbol: method update(Object)
|
||||
location: variable s of type Session
|
||||
src/compat/OldHibernate.java:23: error: cannot find symbol
|
||||
s.saveOrUpdate(o);
|
||||
^
|
||||
symbol: method saveOrUpdate(Object)
|
||||
location: variable s of type Session
|
||||
src/compat/OldHibernate.java:27: error: cannot find symbol
|
||||
s.delete(o);
|
||||
^
|
||||
symbol: method delete(Object)
|
||||
location: variable s of type Session
|
||||
src/compat/OldHibernate.java:31: error: incompatible types: void cannot be converted to Object
|
||||
return s.load(Object.class, 1L);
|
||||
^
|
||||
src/compat/OldHibernate.java:35: error: cannot find symbol
|
||||
Criteria c = s.createCriteria(Object.class).add(Restrictions.eq("name", "x"));
|
||||
^
|
||||
symbol: class Criteria
|
||||
location: class OldHibernate
|
||||
src/compat/OldHibernate.java:35: error: cannot find symbol
|
||||
Criteria c = s.createCriteria(Object.class).add(Restrictions.eq("name", "x"));
|
||||
^
|
||||
symbol: method createCriteria(Class<Object>)
|
||||
location: variable s of type Session
|
||||
src/compat/OldHibernate.java:35: error: cannot find symbol
|
||||
Criteria c = s.createCriteria(Object.class).add(Restrictions.eq("name", "x"));
|
||||
^
|
||||
symbol: variable Restrictions
|
||||
location: class OldHibernate
|
||||
src/compat/OldHibernate.java:40: error: cannot find symbol
|
||||
return s.createSQLQuery("select 1").list();
|
||||
^
|
||||
symbol: method createSQLQuery(String)
|
||||
location: variable s of type Session
|
||||
src/compat/OldHibernate.java:44: error: incompatible types: FlushMode cannot be converted to FlushModeType
|
||||
s.setFlushMode(FlushMode.AUTO);
|
||||
^
|
||||
src/compat/OldHibernate.java:48: error: cannot find symbol
|
||||
return sf.getClassMetadata(Object.class);
|
||||
^
|
||||
symbol: method getClassMetadata(Class<Object>)
|
||||
location: variable sf of type SessionFactory
|
||||
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
|
||||
13 errors
|
||||
javac exit status: 1
|
||||
@@ -0,0 +1,2 @@
|
||||
Hibernate 5.6.15, compiling NewHibernate.java
|
||||
javac exit status: 0
|
||||
@@ -0,0 +1,6 @@
|
||||
Hibernate 7.4.14, compiling NewHibernate.java
|
||||
src/compat/NewHibernate.java:34: warning: [deprecation] createNativeQuery(String) in Session has been deprecated
|
||||
return s.createNativeQuery("select 1").getResultList();
|
||||
^
|
||||
1 warning
|
||||
javac exit status: 0
|
||||
@@ -0,0 +1,2 @@
|
||||
Hibernate 5.6.15, compiling OldAnnotations.java
|
||||
javac exit status: 0
|
||||
@@ -0,0 +1,27 @@
|
||||
Hibernate 7.4.14, compiling OldAnnotations.java
|
||||
src/compat/OldAnnotations.java:3: error: cannot find symbol
|
||||
import org.hibernate.annotations.Where;
|
||||
^
|
||||
symbol: class Where
|
||||
location: package org.hibernate.annotations
|
||||
src/compat/OldAnnotations.java:7: error: cannot find symbol
|
||||
@Where(clause = "deleted = false")
|
||||
^
|
||||
symbol: class Where
|
||||
src/compat/OldAnnotations.java:10: error: cannot find symbol
|
||||
@Type(type = "yes_no")
|
||||
^
|
||||
symbol: method type()
|
||||
location: @interface Type
|
||||
src/compat/OldAnnotations.java:10: error: annotation @Type is missing a default value for the element 'value'
|
||||
@Type(type = "yes_no")
|
||||
^
|
||||
src/compat/OldAnnotations.java:13: warning: [removal] GenericGenerator in org.hibernate.annotations has been deprecated and marked for removal
|
||||
@GenericGenerator(name = "g", strategy = "increment")
|
||||
^
|
||||
src/compat/OldAnnotations.java:13: warning: [removal] strategy() in GenericGenerator has been deprecated and marked for removal
|
||||
@GenericGenerator(name = "g", strategy = "increment")
|
||||
^
|
||||
4 errors
|
||||
2 warnings
|
||||
javac exit status: 1
|
||||
@@ -0,0 +1,32 @@
|
||||
Hibernate 5.6.15, compiling NewAnnotations.java
|
||||
src/compat/NewAnnotations.java:1: error: cannot find symbol
|
||||
import org.hibernate.annotations.JdbcTypeCode;
|
||||
^
|
||||
symbol: class JdbcTypeCode
|
||||
location: package org.hibernate.annotations
|
||||
src/compat/NewAnnotations.java:2: error: cannot find symbol
|
||||
import org.hibernate.annotations.SQLRestriction;
|
||||
^
|
||||
symbol: class SQLRestriction
|
||||
location: package org.hibernate.annotations
|
||||
src/compat/NewAnnotations.java:3: error: cannot find symbol
|
||||
import org.hibernate.type.SqlTypes;
|
||||
^
|
||||
symbol: class SqlTypes
|
||||
location: package org.hibernate.type
|
||||
src/compat/NewAnnotations.java:6: error: cannot find symbol
|
||||
@SQLRestriction("deleted = false")
|
||||
^
|
||||
symbol: class SQLRestriction
|
||||
src/compat/NewAnnotations.java:9: error: cannot find symbol
|
||||
@JdbcTypeCode(SqlTypes.CHAR)
|
||||
^
|
||||
symbol: class JdbcTypeCode
|
||||
location: class NewAnnotations
|
||||
src/compat/NewAnnotations.java:9: error: cannot find symbol
|
||||
@JdbcTypeCode(SqlTypes.CHAR)
|
||||
^
|
||||
symbol: variable SqlTypes
|
||||
location: class NewAnnotations
|
||||
6 errors
|
||||
javac exit status: 1
|
||||
@@ -0,0 +1,2 @@
|
||||
Hibernate 7.4.14, compiling NewAnnotations.java
|
||||
javac exit status: 0
|
||||
@@ -0,0 +1,15 @@
|
||||
Hibernate 5.6.15, H2, @GeneratedValue with no strategy
|
||||
-- sequences (name, increment)
|
||||
HIBERNATE_SEQUENCE 1
|
||||
-- columns of PROBE (name, type)
|
||||
ACTIVE BOOLEAN
|
||||
BYNAME CHARACTER VARYING
|
||||
BYORDINAL INTEGER
|
||||
CREATEDAT TIMESTAMP
|
||||
ID BIGINT
|
||||
NAME CHARACTER VARYING
|
||||
OCCURREDAT TIMESTAMP
|
||||
PAYLOAD BINARY VARYING
|
||||
PRICE NUMERIC
|
||||
SPAN BIGINT
|
||||
UUID BINARY
|
||||
@@ -0,0 +1,15 @@
|
||||
Hibernate 7.4.14, H2, @GeneratedValue with no strategy
|
||||
-- sequences (name, increment)
|
||||
PROBE_SEQ 50
|
||||
-- columns of PROBE (name, type)
|
||||
ACTIVE BOOLEAN
|
||||
BYNAME ENUM
|
||||
BYORDINAL TINYINT
|
||||
CREATEDAT TIMESTAMP WITH TIME ZONE
|
||||
ID BIGINT
|
||||
NAME CHARACTER VARYING
|
||||
OCCURREDAT TIMESTAMP
|
||||
PAYLOAD BINARY VARYING
|
||||
PRICE NUMERIC
|
||||
SPAN NUMERIC
|
||||
UUID UUID
|
||||
@@ -0,0 +1,15 @@
|
||||
Hibernate 5.6.15 Hibernate 7.4.14
|
||||
-- id sequences (name, increment)
|
||||
HIBERNATE_SEQUENCE 1 PROBE_SEQ 50
|
||||
-- column types in H2
|
||||
ACTIVE BOOLEAN BOOLEAN (same)
|
||||
BYNAME CHARACTER VARYING ENUM <-- differs
|
||||
BYORDINAL INTEGER TINYINT <-- differs
|
||||
CREATEDAT TIMESTAMP TIMESTAMP WITH TIME ZONE <-- differs
|
||||
ID BIGINT BIGINT (same)
|
||||
NAME CHARACTER VARYING CHARACTER VARYING (same)
|
||||
OCCURREDAT TIMESTAMP TIMESTAMP (same)
|
||||
PAYLOAD BINARY VARYING BINARY VARYING (same)
|
||||
PRICE NUMERIC NUMERIC (same)
|
||||
SPAN BIGINT NUMERIC <-- differs
|
||||
UUID BINARY UUID <-- differs
|
||||
@@ -0,0 +1,6 @@
|
||||
Hibernate 5.6.15
|
||||
session.save(...) returned: 1 (Long)
|
||||
HQL with ?1 : 1 row(s)
|
||||
HQL with a bare ? : failed: IllegalArgumentException: org.hibernate.QueryException: Legacy-style query parameters (`?`) are no longer supported; use JPA-style ordinal parameters (e.g., `?1`) ins...
|
||||
HQL enum compared with 0 : 0 row(s)
|
||||
session.createCriteria(...) : 1 row(s)
|
||||
@@ -0,0 +1,5 @@
|
||||
Hibernate 7.4.14
|
||||
session.persist(...) assigned the id: 1 (Long)
|
||||
HQL with ?1 : 1 row(s)
|
||||
HQL with a bare ? : failed: IllegalArgumentException: org.hibernate.query.ParameterLabelException: Unlabeled ordinal parameter ('?' rather than ?1)
|
||||
HQL enum compared with 0 : 0 row(s)
|
||||
@@ -0,0 +1,3 @@
|
||||
Hibernate 7.4.14 opening the H2 file database written by Hibernate 5.6.15 (same Probe entity)
|
||||
hbm2ddl.auto=validate: failed: SchemaManagementException: Schema validation: wrong column type encountered in column [span] in table [Probe]; found [bigint (Types#BIGINT)], but expecting [numeric(21,0) (Types#NUMERIC)]
|
||||
hbm2ddl.auto=none : opened; loaded the row written by 5.6.15: name=legacy row, uuid=null
|
||||
@@ -0,0 +1,10 @@
|
||||
Hibernate 5.6.15, compiling JakartaNamespace.java
|
||||
src/compat/JakartaNamespace.java:1: error: package jakarta.persistence does not exist
|
||||
import jakarta.persistence.Entity;
|
||||
^
|
||||
src/compat/JakartaNamespace.java:3: error: cannot find symbol
|
||||
@Entity
|
||||
^
|
||||
symbol: class Entity
|
||||
2 errors
|
||||
javac exit status: 1
|
||||
@@ -0,0 +1,2 @@
|
||||
Hibernate 7.4.14, compiling JakartaNamespace.java
|
||||
javac exit status: 0
|
||||
@@ -0,0 +1,2 @@
|
||||
Hibernate 5.6.15, compiling JavaxNamespace.java
|
||||
javac exit status: 0
|
||||
@@ -0,0 +1,10 @@
|
||||
Hibernate 7.4.14, compiling JavaxNamespace.java
|
||||
src/compat/JavaxNamespace.java:1: error: package javax.persistence does not exist
|
||||
import javax.persistence.Entity;
|
||||
^
|
||||
src/compat/JavaxNamespace.java:3: error: cannot find symbol
|
||||
@Entity
|
||||
^
|
||||
symbol: class Entity
|
||||
2 errors
|
||||
javac exit status: 1
|
||||
@@ -0,0 +1,6 @@
|
||||
persist(a) -> id assigned on the object: 1
|
||||
find(Author, id) -> Ann (1 select)
|
||||
getReference(Author, id) -> initialized: false (0 selects)
|
||||
merge(detached) -> returns a different instance: true, name now Ann Smith
|
||||
findMultiple(ids 3) -> 3 entries, missing id gives: null
|
||||
remove(a) -> find afterwards: null
|
||||
@@ -0,0 +1,8 @@
|
||||
named parameter : [Beta]
|
||||
?1 parameter : [Alpha, Beta]
|
||||
limit 2 offset 1 in HQL : [Beta, Gamma]
|
||||
order by ... nulls last : [Beta, Alpha, Gamma]
|
||||
getSingleResultOrNull() : null
|
||||
join fetch : author initialized = true
|
||||
count over an association : 2
|
||||
createMutationQuery update: 2 rows
|
||||
@@ -0,0 +1,3 @@
|
||||
insert x3 (no persistence context) done
|
||||
get twice -> same instance: false (a stateless session has no first-level cache)
|
||||
rows in table: [3]
|
||||
@@ -0,0 +1,2 @@
|
||||
bySimpleNaturalId(...).load -> Ann
|
||||
sql: select a1_0.id,a1_0.email,a1_0.name from Author a1_0 where a1_0.email=?
|
||||
@@ -0,0 +1,3 @@
|
||||
remove(note) ran: update Note set deleted=true where id=? and deleted=false
|
||||
find afterwards : null
|
||||
row still in the table: [TRUE 1 remember]
|
||||
@@ -0,0 +1,2 @@
|
||||
loaded: Address[street=1 Main St, city=Pune]
|
||||
columns: [CITY, ID, NAME, STREET]
|
||||
@@ -0,0 +1,170 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compare the public API of hibernate-core 5.6.15.Final (org.hibernate) with 7.4.14.Final (org.hibernate.orm).
|
||||
|
||||
javax.persistence is read as jakarta.persistence, so the namespace move does not drown the real changes.
|
||||
Writes output/01-versions-and-baseline.txt, output/02-removed-api.txt and output/02-removed-api-summary.txt.
|
||||
Needs a JDK (javap) and access to Maven Central.
|
||||
"""
|
||||
import collections
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
import subprocess
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import zipfile
|
||||
|
||||
ROOT = pathlib.Path(__file__).resolve().parent.parent
|
||||
JARS = ROOT / "target" / "jars"
|
||||
OUT = ROOT / "output"
|
||||
JAVAP = os.path.join(os.environ["JAVA_HOME"], "bin", "javap") if os.environ.get("JAVA_HOME") else "javap"
|
||||
|
||||
OLD = ("org/hibernate", "5.6.15.Final")
|
||||
NEW = ("org/hibernate/orm", "7.4.14.Final")
|
||||
|
||||
|
||||
def fetch(url, dest):
|
||||
if dest.exists():
|
||||
return
|
||||
for _ in range(10):
|
||||
try:
|
||||
with urllib.request.urlopen(url) as r:
|
||||
dest.write_bytes(r.read())
|
||||
return
|
||||
except urllib.error.HTTPError as e:
|
||||
if e.code != 429:
|
||||
raise
|
||||
time.sleep(5)
|
||||
raise RuntimeError("rate limited: " + url)
|
||||
|
||||
|
||||
def jar(side):
|
||||
group, v = side
|
||||
dest = JARS / f"hibernate-core-{v}.jar"
|
||||
fetch(f"https://repo1.maven.org/maven2/{group}/hibernate-core/{v}/hibernate-core-{v}.jar", dest)
|
||||
return dest
|
||||
|
||||
|
||||
def classes(path):
|
||||
with zipfile.ZipFile(path) as z:
|
||||
return sorted(n[:-6].replace("/", ".") for n in z.namelist()
|
||||
if n.endswith(".class") and not n.endswith("module-info.class") and "META-INF" not in n)
|
||||
|
||||
|
||||
def major(path):
|
||||
with zipfile.ZipFile(path) as z:
|
||||
name = next(n for n in z.namelist() if n.endswith(".class") and "META-INF" not in n and not n.endswith("module-info.class"))
|
||||
return int.from_bytes(z.read(name)[6:8], "big")
|
||||
|
||||
|
||||
def jakarta(text):
|
||||
return text.replace("javax.persistence", "jakarta.persistence").replace("javax.transaction", "jakarta.transaction")
|
||||
|
||||
|
||||
def norm(sig):
|
||||
sig = jakarta(sig)
|
||||
sig = re.sub(r"\s+throws\s+.*$", "", sig)
|
||||
prev = None
|
||||
while prev != sig:
|
||||
prev = sig
|
||||
sig = re.sub(r"<[^<>]*>", "", sig)
|
||||
return " ".join(sig.split())
|
||||
|
||||
|
||||
MEMBER = re.compile(r"^ ((?:public|protected)[^;]*);$")
|
||||
|
||||
|
||||
def dump(path):
|
||||
names = [c for c in classes(path) if ".internal." not in c and ".spi." not in c and not c.startswith("org.hibernate.sql.ast")
|
||||
and not c.startswith("org.hibernate.sql.results") and not c.startswith("org.hibernate.query.sqm.tree")
|
||||
and not c.startswith("org.hibernate.grammars")]
|
||||
result = {}
|
||||
for i in range(0, len(names), 150):
|
||||
chunk = names[i:i + 150]
|
||||
p = subprocess.run([JAVAP, "-v", "-protected", "-cp", str(path)] + chunk, capture_output=True, text=True)
|
||||
for block in re.split(r"(?m)^Classfile ", p.stdout)[1:]:
|
||||
lines = block.splitlines()
|
||||
cls = jakarta(re.search(r"!/(.*)\.class", lines[0]).group(1).replace("/", "."))
|
||||
public, seen_flags, in_pool, cur = False, False, False, None
|
||||
members = {}
|
||||
cls_dep = bool(re.search(r"^Deprecated: true", block, re.M))
|
||||
for line in lines[1:]:
|
||||
if not seen_flags and line.startswith(" flags:"):
|
||||
public = "ACC_PUBLIC" in line
|
||||
seen_flags = True
|
||||
if line.startswith("Constant pool:"):
|
||||
in_pool = True
|
||||
continue
|
||||
if in_pool:
|
||||
if line.startswith("{"):
|
||||
in_pool = False
|
||||
continue
|
||||
m = MEMBER.match(line)
|
||||
if m:
|
||||
cur = " ".join(m.group(1).split())
|
||||
members[cur] = False
|
||||
continue
|
||||
if line.strip() == "Deprecated: true" and cur is not None:
|
||||
members[cur] = True
|
||||
if line.startswith("}"):
|
||||
cur = None
|
||||
result[cls] = (public, cls_dep, members)
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
JARS.mkdir(parents=True, exist_ok=True)
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
old, new = jar(OLD), jar(NEW)
|
||||
mo, mn = major(old), major(new)
|
||||
versions = [
|
||||
f"{'':<12}{'5.x':<46}{'7.x'}",
|
||||
f"{'coordinates':<12}{'org.hibernate:hibernate-core:5.6.15.Final':<46}org.hibernate.orm:hibernate-core:7.4.14.Final",
|
||||
f"{'class files':<12}{'Java ' + str(mo - 44):<46}Java {mn - 44}",
|
||||
f"{'persistence':<12}{'javax.persistence (JPA 2.2)':<46}jakarta.persistence (Jakarta Persistence 3.2)",
|
||||
]
|
||||
do, dn = dump(old), dump(new)
|
||||
co = {jakarta(c) for c in classes(old)}
|
||||
cn = {jakarta(c) for c in classes(new)}
|
||||
simple_new = collections.defaultdict(list)
|
||||
for c in cn:
|
||||
simple_new[c.split(".")[-1]].append(c)
|
||||
removed_cls, removed_mem = [], []
|
||||
for c in sorted(co - cn):
|
||||
if c in do and do[c][0]:
|
||||
removed_cls.append((c, do[c][1], simple_new.get(c.split(".")[-1], [])))
|
||||
for c in sorted(do):
|
||||
if c in dn and do[c][0]:
|
||||
new_norm = {norm(x) for x in dn[c][2]}
|
||||
for sig, dep in do[c][2].items():
|
||||
if norm(sig) not in new_norm:
|
||||
removed_mem.append((c, jakarta(sig), dep))
|
||||
lines = [f"== hibernate-core: public classes missing: {len(removed_cls)}, members removed from classes that remain: {len(removed_mem)}, "
|
||||
f"new classes: {len(cn - co)}"]
|
||||
for c, dep, same_name in removed_cls:
|
||||
if same_name:
|
||||
note = "same simple name exists in " + ", ".join(sorted(p.rsplit('.', 1)[0] for p in same_name)[:2])
|
||||
else:
|
||||
note = "no class with this name"
|
||||
lines.append(f" class {c} [{'deprecated in 5.6' if dep else 'NOT deprecated in 5.6'}; {note}]")
|
||||
for c, sig, dep in removed_mem:
|
||||
lines.append(f" member {c}: {sig} [{'deprecated in 5.6' if dep else 'NOT deprecated in 5.6'}]")
|
||||
(OUT / "01-versions-and-baseline.txt").write_text("\n".join(versions) + "\n")
|
||||
(OUT / "02-removed-api.txt").write_text("\n".join(lines) + "\n")
|
||||
# summary: counts per package, and how many of the missing classes have a namesake elsewhere
|
||||
per_pkg = collections.Counter(c.rsplit(".", 1)[0] for c, _, _ in removed_cls)
|
||||
dep_n = sum(1 for _, d, _ in removed_cls if d)
|
||||
ren_n = sum(1 for _, _, s in removed_cls if s)
|
||||
summary = [lines[0],
|
||||
f" of the missing classes: {dep_n} were deprecated in 5.6.15, {ren_n} have a class of the same simple name somewhere in 7.4.14",
|
||||
" packages with the most missing classes:"]
|
||||
for pkg, n in per_pkg.most_common(12):
|
||||
summary.append(f" {n:>4} {pkg}")
|
||||
(OUT / "02-removed-api-summary.txt").write_text("\n".join(summary) + "\n")
|
||||
print("\n".join(versions))
|
||||
print("\n".join(summary))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env bash
|
||||
# Compiles src/compat/*.java against Hibernate 5.6.15 (h5/) and 7.4.14 (h7/) and records javac's answer in output/.
|
||||
set -u
|
||||
cd "$(dirname "$0")/.."
|
||||
mkdir -p target output
|
||||
compile() { # version dir file outfile
|
||||
local v=$1 dir=$2 f=$3 out=$4
|
||||
rm -rf target/classes-$f-$v; mkdir -p target/classes-$f-$v
|
||||
{
|
||||
echo "Hibernate $v, compiling $f.java"
|
||||
javac -Xlint:deprecation -proc:none -d target/classes-$f-$v -cp "$(cat target/cp-$v.txt)" src/compat/$f.java 2>&1 | grep -v "^Picked up"
|
||||
echo "javac exit status: ${PIPESTATUS[0]}"
|
||||
} >> $out
|
||||
}
|
||||
for pair in "h5:5.6.15" "h7:7.4.14"; do
|
||||
dir=${pair%%:*}; v=${pair##*:}
|
||||
(cd $dir && mvn -q -B dependency:build-classpath -Dmdep.outputFile=../target/cp-$v.txt >/dev/null) || { echo "classpath failed for $v"; exit 1; }
|
||||
for spec in "OldHibernate:03-old-hibernate-apis" "NewHibernate:04-new-hibernate-apis" "OldAnnotations:05-old-annotations" "NewAnnotations:06-new-annotations" "JavaxNamespace:09-namespace-javax" "JakartaNamespace:09-namespace-jakarta"; do
|
||||
f=${spec%%:*}; n=${spec##*:}
|
||||
out=output/$n-$v.txt; : > $out
|
||||
compile $v $dir $f $out
|
||||
done
|
||||
done
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
# Rewrites everything in output/. Needs JDK 17+, Maven 3.9, Python 3 and internet access to Maven Central.
|
||||
set -e
|
||||
cd "$(dirname "$0")/.."
|
||||
python3 scripts/api_diff.py
|
||||
(cd h5 && mvn -q -B clean test)
|
||||
(cd h7 && mvn -q -B clean test)
|
||||
python3 scripts/schema_diff.py
|
||||
./scripts/compile-old-apis.sh
|
||||
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Lines up output/06-schema-5.6.15.txt and output/06-schema-7.4.14.txt side by side into output/06-schema-diff.txt."""
|
||||
import pathlib
|
||||
|
||||
OUT = pathlib.Path(__file__).resolve().parent.parent / "output"
|
||||
|
||||
|
||||
def read(version):
|
||||
seq, cols, section = {}, {}, None
|
||||
for line in (OUT / f"06-schema-{version}.txt").read_text().splitlines():
|
||||
if line.startswith("-- sequences"):
|
||||
section = "seq"
|
||||
elif line.startswith("-- columns"):
|
||||
section = "col"
|
||||
elif section == "seq":
|
||||
name, inc = line.rsplit(" ", 1)
|
||||
seq[name] = inc
|
||||
elif section == "col":
|
||||
name, typ = line.split(" ", 1)
|
||||
cols[name] = typ
|
||||
return seq, cols
|
||||
|
||||
|
||||
s5, c5 = read("5.6.15")
|
||||
s7, c7 = read("7.4.14")
|
||||
rows = [f"{'':<14}{'Hibernate 5.6.15':<26}{'Hibernate 7.4.14':<26}", "-- id sequences (name, increment)"]
|
||||
rows.append(f"{'':<14}{', '.join(f'{k} {v}' for k, v in s5.items()):<26}{', '.join(f'{k} {v}' for k, v in s7.items())}")
|
||||
rows.append("-- column types in H2")
|
||||
for name in sorted(set(c5) | set(c7)):
|
||||
a, b = c5.get(name, "-"), c7.get(name, "-")
|
||||
rows.append(f"{name:<14}{a:<26}{b:<26}{'(same)' if a == b else '<-- differs'}")
|
||||
(OUT / "06-schema-diff.txt").write_text("\n".join(rows) + "\n")
|
||||
print("\n".join(rows))
|
||||
@@ -0,0 +1,5 @@
|
||||
import jakarta.persistence.Entity;
|
||||
|
||||
@Entity
|
||||
class JakartaNamespace {
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import javax.persistence.Entity;
|
||||
|
||||
@Entity
|
||||
class JavaxNamespace {
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import org.hibernate.annotations.JdbcTypeCode;
|
||||
import org.hibernate.annotations.SQLRestriction;
|
||||
import org.hibernate.type.SqlTypes;
|
||||
|
||||
/** The Hibernate 6 and 7 spelling of the same ideas. This does not compile on 5.6.15. */
|
||||
@SQLRestriction("deleted = false")
|
||||
class NewAnnotations {
|
||||
|
||||
@JdbcTypeCode(SqlTypes.CHAR)
|
||||
boolean flag;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.FlushMode;
|
||||
import org.hibernate.Session;
|
||||
|
||||
/** The replacements for OldHibernate that exist on both 5.6.15 and 7.4.14, so they can be made before the upgrade. */
|
||||
class NewHibernate {
|
||||
|
||||
void persist(Session s, Object o) {
|
||||
s.persist(o);
|
||||
}
|
||||
|
||||
Object merge(Session s, Object o) {
|
||||
return s.merge(o);
|
||||
}
|
||||
|
||||
void remove(Session s, Object o) {
|
||||
s.remove(o);
|
||||
}
|
||||
|
||||
Object find(Session s) {
|
||||
return s.find(Object.class, 1L);
|
||||
}
|
||||
|
||||
Object reference(Session s) {
|
||||
return s.getReference(Object.class, 1L);
|
||||
}
|
||||
|
||||
List<Object> query(Session s) {
|
||||
return s.createQuery("from Object o where o.name = :n", Object.class).setParameter("n", "x").getResultList();
|
||||
}
|
||||
|
||||
List<?> nativeQuery(Session s) {
|
||||
return s.createNativeQuery("select 1").getResultList();
|
||||
}
|
||||
|
||||
void flushMode(Session s) {
|
||||
s.setHibernateFlushMode(FlushMode.AUTO);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import org.hibernate.annotations.GenericGenerator;
|
||||
import org.hibernate.annotations.Type;
|
||||
import org.hibernate.annotations.Where;
|
||||
|
||||
/** Hibernate 5 annotation attributes. The annotations are applied to a plain class, so no persistence import is needed. */
|
||||
@SuppressWarnings("deprecation")
|
||||
@Where(clause = "deleted = false")
|
||||
class OldAnnotations {
|
||||
|
||||
@Type(type = "yes_no")
|
||||
boolean flag;
|
||||
|
||||
@GenericGenerator(name = "g", strategy = "increment")
|
||||
Long id;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.Criteria;
|
||||
import org.hibernate.FlushMode;
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.SessionFactory;
|
||||
import org.hibernate.criterion.Restrictions;
|
||||
|
||||
/** Hibernate 5 style calls that use no persistence annotations. Compiled by scripts/compile-old-apis.sh against 5.6.15 and 7.4.14. */
|
||||
@SuppressWarnings({"deprecation", "rawtypes"})
|
||||
class OldHibernate {
|
||||
|
||||
Serializable save(Session s, Object o) {
|
||||
return s.save(o);
|
||||
}
|
||||
|
||||
void update(Session s, Object o) {
|
||||
s.update(o);
|
||||
}
|
||||
|
||||
void saveOrUpdate(Session s, Object o) {
|
||||
s.saveOrUpdate(o);
|
||||
}
|
||||
|
||||
void delete(Session s, Object o) {
|
||||
s.delete(o);
|
||||
}
|
||||
|
||||
Object load(Session s) {
|
||||
return s.load(Object.class, 1L);
|
||||
}
|
||||
|
||||
List criteria(Session s) {
|
||||
Criteria c = s.createCriteria(Object.class).add(Restrictions.eq("name", "x"));
|
||||
return c.list();
|
||||
}
|
||||
|
||||
List sql(Session s) {
|
||||
return s.createSQLQuery("select 1").list();
|
||||
}
|
||||
|
||||
void flushMode(Session s) {
|
||||
s.setFlushMode(FlushMode.AUTO);
|
||||
}
|
||||
|
||||
Object metadata(SessionFactory sf) {
|
||||
return sf.getClassMetadata(Object.class);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user