diff --git a/hibernate/README.adoc b/hibernate/README.adoc new file mode 100644 index 00000000..32642f98 --- /dev/null +++ b/hibernate/README.adoc @@ -0,0 +1 @@ +Tests if Spring ORM - Hibernate works diff --git a/hibernate/build.gradle b/hibernate/build.gradle new file mode 100644 index 00000000..0e90ba2b --- /dev/null +++ b/hibernate/build.gradle @@ -0,0 +1,20 @@ +plugins { + id 'java' + id 'org.springframework.boot' + id 'org.springframework.aot.smoke-test' + id 'org.graalvm.buildtools.native' +} + +dependencies { + implementation(platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)) + implementation("org.springframework.boot:spring-boot-starter-jdbc") + implementation("org.springframework:spring-orm") + implementation("jakarta.persistence:jakarta.persistence-api") + implementation("org.hibernate:hibernate-core:6.1.2.Final") + runtimeOnly("com.h2database:h2") + + testImplementation("org.springframework.boot:spring-boot-starter-test") + + aotSmokeTestImplementation(project(":aot-smoke-test-support")) + aotSmokeTestImplementation("org.awaitility:awaitility:4.2.0") +} diff --git a/hibernate/src/aotSmokeTest/java/com/example/spring/orm/HibernateApplicationAotTests.java b/hibernate/src/aotSmokeTest/java/com/example/spring/orm/HibernateApplicationAotTests.java new file mode 100644 index 00000000..4c4a989b --- /dev/null +++ b/hibernate/src/aotSmokeTest/java/com/example/spring/orm/HibernateApplicationAotTests.java @@ -0,0 +1,26 @@ +package com.example.spring.orm; + +import java.time.Duration; + +import org.awaitility.Awaitility; +import org.junit.jupiter.api.Test; + +import org.springframework.aot.smoketest.support.assertj.AssertableOutput; +import org.springframework.aot.smoketest.support.junit.AotSmokeTest; + +import static org.assertj.core.api.Assertions.assertThat; + +@AotSmokeTest +class HibernateApplicationAotTests { + + @Test + void entityGraph(AssertableOutput output) { + Awaitility.await().atMost(Duration.ofSeconds(10)).untilAsserted(() -> { + assertThat(output) + .hasLineContaining("left join (book_authors a1_0 join author a1_1 on a1_1.id=a1_0.authors_id)") + .hasSingleLineContaining( + "namedEntityGraph: Book{title='Spring in Action', authors=[Author{name='Craig Walls'}]}"); + }); + } + +} diff --git a/hibernate/src/main/java/com/example/spring/orm/CLR.java b/hibernate/src/main/java/com/example/spring/orm/CLR.java new file mode 100644 index 00000000..5d8e858b --- /dev/null +++ b/hibernate/src/main/java/com/example/spring/orm/CLR.java @@ -0,0 +1,61 @@ +package com.example.spring.orm; + +import java.util.Collections; +import java.util.List; + +import com.example.spring.orm.model.Author; +import com.example.spring.orm.model.Book; +import jakarta.persistence.EntityGraph; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import org.hibernate.Session; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Component +@Transactional +public class CLR implements CommandLineRunner { + + @PersistenceContext + private EntityManager entityManager; + + @Override + public void run(String... args) { + var books = insertBooks(); + entityGraph(books.iterator().next().getId()); + } + + public void entityGraph(Long id) { + + EntityGraph entityGraph = entityManager.getEntityGraph("Book.authors"); + + System.out.println("entityGraph: " + entityGraph); + + Book result = entityManager.find(Book.class, id, + Collections.singletonMap("jakarta.persistence.fetchgraph", entityGraph)); + System.out.println("namedEntityGraph: " + result); + } + + public List insertBooks() { + + Book book = new Book(null, "Spring in Action"); + Author author = new Author(null, "Craig Walls", Collections.emptySet()); + book.getAuthors().add(author); + + entityManager.persist(book); + System.out.println("book: " + book); + + entityManager.flush(); + entityManager.getEntityManagerFactory().getCache().evictAll(); + + Session session = entityManager.unwrap(Session.class); + + if (session != null) { + session.clear(); // internal cache clear + } + + return Collections.singletonList(book); + } + +} diff --git a/hibernate/src/main/java/com/example/spring/orm/HibernateApplication.java b/hibernate/src/main/java/com/example/spring/orm/HibernateApplication.java new file mode 100644 index 00000000..cb152b4f --- /dev/null +++ b/hibernate/src/main/java/com/example/spring/orm/HibernateApplication.java @@ -0,0 +1,16 @@ +package com.example.spring.orm; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +@SpringBootApplication +@EnableTransactionManagement +public class HibernateApplication { + + public static void main(String[] args) throws InterruptedException { + SpringApplication.run(HibernateApplication.class, args); + Thread.currentThread().join(); // To be able to measure memory consumption + } + +} diff --git a/hibernate/src/main/java/com/example/spring/orm/model/Author.java b/hibernate/src/main/java/com/example/spring/orm/model/Author.java new file mode 100644 index 00000000..9c3afe0f --- /dev/null +++ b/hibernate/src/main/java/com/example/spring/orm/model/Author.java @@ -0,0 +1,78 @@ +package com.example.spring.orm.model; + +import java.util.Objects; +import java.util.Set; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; +import jakarta.persistence.PreRemove; + +@Entity +public class Author { + + @Id + @GeneratedValue + private Long id; + + private String name; + + @OneToMany(cascade = CascadeType.ALL) + private Set books; + + protected Author() { + } + + public Author(Long id, String name, Set books) { + this.id = id; + this.name = name; + this.books = books; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getBooks() { + return books; + } + + @PreRemove + public void preRemove() { + System.out.println("Pre remove author " + id); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Author author = (Author) o; + return Objects.equals(id, author.id) && Objects.equals(name, author.name) + && Objects.equals(books, author.books); + } + + @Override + public int hashCode() { + return Objects.hash(id, name, books); + } + + @Override + public String toString() { + return "Author{" + "name='" + name + '\'' + '}'; + } + +} diff --git a/hibernate/src/main/java/com/example/spring/orm/model/Book.java b/hibernate/src/main/java/com/example/spring/orm/model/Book.java new file mode 100644 index 00000000..5aed8b0c --- /dev/null +++ b/hibernate/src/main/java/com/example/spring/orm/model/Book.java @@ -0,0 +1,79 @@ +package com.example.spring.orm.model; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.NamedAttributeNode; +import jakarta.persistence.NamedEntityGraph; +import jakarta.persistence.OneToMany; + +@Entity +@NamedEntityGraph(name = "Book.authors", attributeNodes = @NamedAttributeNode("authors")) +public class Book { + + @Id + @GeneratedValue + private Long id; + + private String title; + + @OneToMany(cascade = CascadeType.ALL) + private Set authors = new HashSet<>(); + + protected Book() { + } + + public Book(Long id, String title) { + this.id = id; + this.title = title; + } + + public Long getId() { + return id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public Set getAuthors() { + return authors; + } + + public void setAuthors(Set authors) { + this.authors = authors; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Book book = (Book) o; + return Objects.equals(id, book.id) && Objects.equals(title, book.title); + } + + @Override + public int hashCode() { + return Objects.hash(id, title); + } + + @Override + public String toString() { + return "Book{" + "title='" + title + '\'' + ", authors=" + authors + '}'; + } + +} diff --git a/hibernate/src/main/resources/application.properties b/hibernate/src/main/resources/application.properties new file mode 100644 index 00000000..15fa2dbb --- /dev/null +++ b/hibernate/src/main/resources/application.properties @@ -0,0 +1 @@ +logging.level.org.hibernate.SQL=debug diff --git a/hibernate/src/main/resources/schema.sql b/hibernate/src/main/resources/schema.sql new file mode 100644 index 00000000..d863aaa6 --- /dev/null +++ b/hibernate/src/main/resources/schema.sql @@ -0,0 +1,17 @@ +create table author +( + id bigint auto_increment primary key, + name varchar not null +); + +create table book +( + id bigint auto_increment primary key, + author bigint not null references author (id), + title varchar not null +); + +create table book_authors ( + book_id bigint, + authors_id bigint +) diff --git a/hibernate/src/test/java/com/example/spring/orm/HibernateTests.java b/hibernate/src/test/java/com/example/spring/orm/HibernateTests.java new file mode 100644 index 00000000..542c320d --- /dev/null +++ b/hibernate/src/test/java/com/example/spring/orm/HibernateTests.java @@ -0,0 +1,14 @@ +package com.example.spring.orm; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class HibernateTests { + + @Test + void contextLoads() { + + } + +} diff --git a/settings.gradle b/settings.gradle index b9fe58a3..076f0c9e 100644 --- a/settings.gradle +++ b/settings.gradle @@ -71,6 +71,7 @@ include "flyway" include "freemarker-webflux" include "freemarker-webmvc" include "hateoas" +include "hibernate" include "integration" include "kotlin-functional" include "jdbc-h2"