From 150f833d455202c6d95c43157c2e6992581cb0e4 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Tue, 18 Oct 2022 15:26:21 +0200 Subject: [PATCH 1/3] Copy of the jpa sample but with kotlin --- data-jpa-kotlin/README.adoc | 1 + data-jpa-kotlin/build.gradle | 25 ++++++ .../data/jpa/DataJpaApplicationAotTests.java | 66 ++++++++++++++ .../java/com/example/data/jpa/kotlin/CLR.java | 85 +++++++++++++++++++ .../data/jpa/kotlin/DataJpaConfiguration.java | 10 +++ .../jpa/kotlin/DataJpaKotlinApplication.java | 14 +++ .../data/jpa/kotlin/AutorRepository.kt | 14 +++ .../example/data/jpa/kotlin/model/Autor.kt | 43 ++++++++++ .../com/example/data/jpa/kotlin/model/Book.kt | 42 +++++++++ .../src/main/resources/application.properties | 0 data-jpa-kotlin/src/main/resources/schema.sql | 12 +++ .../kotlin/DataJpaKotlinApplicationTests.java | 14 +++ settings.gradle | 1 + 13 files changed, 327 insertions(+) create mode 100644 data-jpa-kotlin/README.adoc create mode 100644 data-jpa-kotlin/build.gradle create mode 100644 data-jpa-kotlin/src/aotSmokeTest/java/com/example/data/jpa/DataJpaApplicationAotTests.java create mode 100644 data-jpa-kotlin/src/main/java/com/example/data/jpa/kotlin/CLR.java create mode 100644 data-jpa-kotlin/src/main/java/com/example/data/jpa/kotlin/DataJpaConfiguration.java create mode 100644 data-jpa-kotlin/src/main/java/com/example/data/jpa/kotlin/DataJpaKotlinApplication.java create mode 100644 data-jpa-kotlin/src/main/kotlin/com/example/data/jpa/kotlin/AutorRepository.kt create mode 100644 data-jpa-kotlin/src/main/kotlin/com/example/data/jpa/kotlin/model/Autor.kt create mode 100644 data-jpa-kotlin/src/main/kotlin/com/example/data/jpa/kotlin/model/Book.kt create mode 100644 data-jpa-kotlin/src/main/resources/application.properties create mode 100644 data-jpa-kotlin/src/main/resources/schema.sql create mode 100644 data-jpa-kotlin/src/test/java/com/example/data/jpa/kotlin/DataJpaKotlinApplicationTests.java diff --git a/data-jpa-kotlin/README.adoc b/data-jpa-kotlin/README.adoc new file mode 100644 index 00000000..eb652490 --- /dev/null +++ b/data-jpa-kotlin/README.adoc @@ -0,0 +1 @@ +Tests if Spring Data JPA with works with Kotlin diff --git a/data-jpa-kotlin/build.gradle b/data-jpa-kotlin/build.gradle new file mode 100644 index 00000000..a8fd6e9e --- /dev/null +++ b/data-jpa-kotlin/build.gradle @@ -0,0 +1,25 @@ +plugins { + id 'java' + id 'org.springframework.boot' + id 'org.springframework.aot.smoke-test' + id 'org.graalvm.buildtools.native' + id 'org.jetbrains.kotlin.jvm' + id 'org.jetbrains.kotlin.plugin.spring' version "1.7.10" +} + +repositories { + mavenLocal() +} + +dependencies { + implementation(platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)) + implementation("org.springframework.boot:spring-boot-starter-data-jpa") + implementation("org.jetbrains.kotlin:kotlin-reflect") + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") + 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/data-jpa-kotlin/src/aotSmokeTest/java/com/example/data/jpa/DataJpaApplicationAotTests.java b/data-jpa-kotlin/src/aotSmokeTest/java/com/example/data/jpa/DataJpaApplicationAotTests.java new file mode 100644 index 00000000..e2988add --- /dev/null +++ b/data-jpa-kotlin/src/aotSmokeTest/java/com/example/data/jpa/DataJpaApplicationAotTests.java @@ -0,0 +1,66 @@ +package com.example.data.jpa; + +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 DataJpaApplicationAotTests { + + @Test + void insert(AssertableOutput output) { + Awaitility.await().atMost(Duration.ofSeconds(10)).untilAsserted(() -> { + assertThat(output).hasSingleLineContaining("insertAuthors(): author1 = Author{name='Josh Long'}") + .hasSingleLineContaining("insertAuthors(): author2 = Author{name='Martin Kleppmann'}"); + }); + } + + @Test + void listAllAuthors(AssertableOutput output) { + Awaitility.await().atMost(Duration.ofSeconds(10)).untilAsserted(() -> { + assertThat(output).hasSingleLineContaining("listAllAuthors(): author = Author{name='Josh Long'") + .hasSingleLineContaining("Book{title='Cloud Native Java'}") + .hasSingleLineContaining("Book{title='Reactive Spring'}") + .hasSingleLineContaining("listAllAuthors(): author = Author{name='Martin Kleppmann'}") + .hasSingleLineContaining("Book{title='Designing Data Intensive Applications'}"); + }); + } + + @Test + void findById(AssertableOutput output) { + Awaitility.await().atMost(Duration.ofSeconds(10)).untilAsserted(() -> { + assertThat(output).hasSingleLineContaining("findById(): author1 = Author{name='Josh Long'}") + .hasSingleLineContaining("findById(): author2 = Author{name='Martin Kleppmann'}"); + }); + } + + @Test + void queryDerivedFromMethodName(AssertableOutput output) { + Awaitility.await().atMost(Duration.ofSeconds(10)).untilAsserted(() -> { + assertThat(output).hasSingleLineContaining("findByPartialName(): author1 = Author{name='Josh Long'}") + .hasSingleLineContaining("findByPartialName(): author2 = Author{name='Martin Kleppmann'}"); + }); + } + + @Test + void queryAnnotatedMethod(AssertableOutput output) { + Awaitility.await().atMost(Duration.ofSeconds(10)).untilAsserted(() -> { + assertThat(output).hasSingleLineContaining("queryFindByName(): author1 = Author{name='Josh Long'}") + .hasSingleLineContaining("queryFindByName(): author2 = Author{name='Martin Kleppmann'}"); + }); + } + + @Test + void deleteAll(AssertableOutput output) { + Awaitility.await().atMost(Duration.ofSeconds(10)).untilAsserted(() -> { + assertThat(output).hasSingleLineContaining("deleteAll(): count = 0"); + }); + } + +} diff --git a/data-jpa-kotlin/src/main/java/com/example/data/jpa/kotlin/CLR.java b/data-jpa-kotlin/src/main/java/com/example/data/jpa/kotlin/CLR.java new file mode 100644 index 00000000..91d4c9a3 --- /dev/null +++ b/data-jpa-kotlin/src/main/java/com/example/data/jpa/kotlin/CLR.java @@ -0,0 +1,85 @@ +package com.example.data.jpa.kotlin; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +import com.example.data.jpa.kotlin.model.Author; +import com.example.data.jpa.kotlin.model.Book; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Component +class CLR implements CommandLineRunner { + + private final AuthorRepository authorRepository; + + CLR(AuthorRepository authorRepository) { + this.authorRepository = authorRepository; + } + + @Override + @Transactional + public void run(String... args) { + var authors = insertAuthors(); + listAllAuthors(); + findById(authors); + findByPartialName(); + queryFindByName(); + deleteAll(); + } + + private void deleteAll() { + this.authorRepository.deleteAll(); + long count = this.authorRepository.count(); + System.out.printf("deleteAll(): count = %d%n", count); + } + + private void queryFindByName() { + Author author1 = this.authorRepository.queryFindByName("Josh Long").orElse(null); + Author author2 = this.authorRepository.queryFindByName("Martin Kleppmann").orElse(null); + + System.out.printf("queryFindByName(): author1 = %s%n", author1); + System.out.printf("queryFindByName(): author2 = %s%n", author2); + } + + private void findByPartialName() { + Author author1 = this.authorRepository.findByNameContainingIgnoreCase("sh lo").orElse(null); + Author author2 = this.authorRepository.findByNameContainingIgnoreCase("in kl").orElse(null); + + System.out.printf("findByPartialName(): author1 = %s%n", author1); + System.out.printf("findByPartialName(): author2 = %s%n", author2); + } + + private void findById(List authors) { + Author author1 = this.authorRepository.findById(authors.get(0).getId()).orElse(null); + Author author2 = this.authorRepository.findById(authors.get(1).getId()).orElse(null); + + System.out.printf("findById(): author1 = %s%n", author1); + System.out.printf("findById(): author2 = %s%n", author2); + } + + private void listAllAuthors() { + List authors = this.authorRepository.findAll(); + for (Author author : authors) { + System.out.printf("listAllAuthors(): author = %s%n", author); + for (Book book : author.getBooks()) { + System.out.printf("\t%s%n", book); + } + } + } + + private List insertAuthors() { + Author author1 = this.authorRepository.save(new Author(null, "Josh Long", + Set.of(new Book(null, "Reactive Spring"), new Book(null, "Cloud Native Java")))); + Author author2 = this.authorRepository.save( + new Author(null, "Martin Kleppmann", Set.of(new Book(null, "Designing Data Intensive Applications")))); + + System.out.printf("insertAuthors(): author1 = %s%n", author1); + System.out.printf("insertAuthors(): author2 = %s%n", author2); + + return Arrays.asList(author1, author2); + } + +} diff --git a/data-jpa-kotlin/src/main/java/com/example/data/jpa/kotlin/DataJpaConfiguration.java b/data-jpa-kotlin/src/main/java/com/example/data/jpa/kotlin/DataJpaConfiguration.java new file mode 100644 index 00000000..a3cf404e --- /dev/null +++ b/data-jpa-kotlin/src/main/java/com/example/data/jpa/kotlin/DataJpaConfiguration.java @@ -0,0 +1,10 @@ +package com.example.data.jpa.kotlin; + +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; + +@Configuration +@EnableJpaRepositories +class DataJpaConfiguration { + +} diff --git a/data-jpa-kotlin/src/main/java/com/example/data/jpa/kotlin/DataJpaKotlinApplication.java b/data-jpa-kotlin/src/main/java/com/example/data/jpa/kotlin/DataJpaKotlinApplication.java new file mode 100644 index 00000000..492ed5af --- /dev/null +++ b/data-jpa-kotlin/src/main/java/com/example/data/jpa/kotlin/DataJpaKotlinApplication.java @@ -0,0 +1,14 @@ +package com.example.data.jpa.kotlin; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class DataJpaKotlinApplication { + + public static void main(String[] args) throws InterruptedException { + SpringApplication.run(DataJpaKotlinApplication.class, args); + Thread.currentThread().join(); // To be able to measure memory consumption + } + +} diff --git a/data-jpa-kotlin/src/main/kotlin/com/example/data/jpa/kotlin/AutorRepository.kt b/data-jpa-kotlin/src/main/kotlin/com/example/data/jpa/kotlin/AutorRepository.kt new file mode 100644 index 00000000..67b403da --- /dev/null +++ b/data-jpa-kotlin/src/main/kotlin/com/example/data/jpa/kotlin/AutorRepository.kt @@ -0,0 +1,14 @@ +package com.example.data.jpa.kotlin + +import com.example.data.jpa.kotlin.model.Author +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query +import java.util.* + +interface AuthorRepository : JpaRepository { + + fun findByNameContainingIgnoreCase(partialName: String?): Optional? + + @Query("SELECT a FROM Author a WHERE a.name = :name") + fun queryFindByName(name: String?): Optional? +} diff --git a/data-jpa-kotlin/src/main/kotlin/com/example/data/jpa/kotlin/model/Autor.kt b/data-jpa-kotlin/src/main/kotlin/com/example/data/jpa/kotlin/model/Autor.kt new file mode 100644 index 00000000..2570cf01 --- /dev/null +++ b/data-jpa-kotlin/src/main/kotlin/com/example/data/jpa/kotlin/model/Autor.kt @@ -0,0 +1,43 @@ +package com.example.data.jpa.kotlin.model + +import jakarta.persistence.* +import java.util.* + +@Entity +class Author { + + @Id + @GeneratedValue + var id: Long? = null + var name: String? = null + + @OneToMany(cascade = [CascadeType.ALL]) + var books: Set? = null + + protected constructor() {} + + constructor(id: Long?, name: String?, books: Set?) { + this.id = id + this.name = name + this.books = books + } + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + if (other == null) { + return false + } + val author = other as Author + return id == author.id && name == author.name && books == author.books + } + + override fun hashCode(): Int { + return Objects.hash(id, name, books) + } + + override fun toString(): String { + return "Author{name='$name'}" + } +} diff --git a/data-jpa-kotlin/src/main/kotlin/com/example/data/jpa/kotlin/model/Book.kt b/data-jpa-kotlin/src/main/kotlin/com/example/data/jpa/kotlin/model/Book.kt new file mode 100644 index 00000000..798889f5 --- /dev/null +++ b/data-jpa-kotlin/src/main/kotlin/com/example/data/jpa/kotlin/model/Book.kt @@ -0,0 +1,42 @@ +package com.example.data.jpa.kotlin.model + +import jakarta.persistence.Entity +import jakarta.persistence.GeneratedValue +import jakarta.persistence.Id +import java.util.* + +@Entity +class Book { + + @Id + @GeneratedValue + var id: Long? = null + private set + var title: String? = null + + protected constructor() {} + + constructor(id: Long?, title: String?) { + this.id = id + this.title = title + } + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + if (other == null) { + return false + } + val book = other as Book + return id == book.id && title == book.title + } + + override fun hashCode(): Int { + return Objects.hash(id, title) + } + + override fun toString(): String { + return "Book{title='$title'}" + } +} diff --git a/data-jpa-kotlin/src/main/resources/application.properties b/data-jpa-kotlin/src/main/resources/application.properties new file mode 100644 index 00000000..e69de29b diff --git a/data-jpa-kotlin/src/main/resources/schema.sql b/data-jpa-kotlin/src/main/resources/schema.sql new file mode 100644 index 00000000..14e51639 --- /dev/null +++ b/data-jpa-kotlin/src/main/resources/schema.sql @@ -0,0 +1,12 @@ +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 +); diff --git a/data-jpa-kotlin/src/test/java/com/example/data/jpa/kotlin/DataJpaKotlinApplicationTests.java b/data-jpa-kotlin/src/test/java/com/example/data/jpa/kotlin/DataJpaKotlinApplicationTests.java new file mode 100644 index 00000000..a577e3a9 --- /dev/null +++ b/data-jpa-kotlin/src/test/java/com/example/data/jpa/kotlin/DataJpaKotlinApplicationTests.java @@ -0,0 +1,14 @@ +package com.example.data.jpa.kotlin; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class DataJpaKotlinApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/settings.gradle b/settings.gradle index 4f1198df..391c5062 100644 --- a/settings.gradle +++ b/settings.gradle @@ -58,6 +58,7 @@ include "data-cassandra-reactive" include "data-jdbc-h2" include "data-jdbc-postgresql" include "data-jpa" +include "data-jpa-kotlin" include "data-mongodb" include "data-mongodb-reactive" include "data-r2dbc" From e08720ffd8328d8918bc32b7ad4c7e49728fb366 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Wed, 19 Oct 2022 09:15:47 +0200 Subject: [PATCH 2/3] Update after review --- .../java/com/example/data/jpa/kotlin/CLR.java | 8 ++++---- .../example/data/jpa/kotlin/AutorRepository.kt | 4 ++-- .../com/example/data/jpa/kotlin/model/Autor.kt | 13 ++----------- .../com/example/data/jpa/kotlin/model/Book.kt | 15 +-------------- 4 files changed, 9 insertions(+), 31 deletions(-) diff --git a/data-jpa-kotlin/src/main/java/com/example/data/jpa/kotlin/CLR.java b/data-jpa-kotlin/src/main/java/com/example/data/jpa/kotlin/CLR.java index 91d4c9a3..58ab5008 100644 --- a/data-jpa-kotlin/src/main/java/com/example/data/jpa/kotlin/CLR.java +++ b/data-jpa-kotlin/src/main/java/com/example/data/jpa/kotlin/CLR.java @@ -37,16 +37,16 @@ class CLR implements CommandLineRunner { } private void queryFindByName() { - Author author1 = this.authorRepository.queryFindByName("Josh Long").orElse(null); - Author author2 = this.authorRepository.queryFindByName("Martin Kleppmann").orElse(null); + Author author1 = this.authorRepository.queryFindByName("Josh Long"); + Author author2 = this.authorRepository.queryFindByName("Martin Kleppmann"); System.out.printf("queryFindByName(): author1 = %s%n", author1); System.out.printf("queryFindByName(): author2 = %s%n", author2); } private void findByPartialName() { - Author author1 = this.authorRepository.findByNameContainingIgnoreCase("sh lo").orElse(null); - Author author2 = this.authorRepository.findByNameContainingIgnoreCase("in kl").orElse(null); + Author author1 = this.authorRepository.findByNameContainingIgnoreCase("sh lo"); + Author author2 = this.authorRepository.findByNameContainingIgnoreCase("in kl"); System.out.printf("findByPartialName(): author1 = %s%n", author1); System.out.printf("findByPartialName(): author2 = %s%n", author2); diff --git a/data-jpa-kotlin/src/main/kotlin/com/example/data/jpa/kotlin/AutorRepository.kt b/data-jpa-kotlin/src/main/kotlin/com/example/data/jpa/kotlin/AutorRepository.kt index 67b403da..4d3f4175 100644 --- a/data-jpa-kotlin/src/main/kotlin/com/example/data/jpa/kotlin/AutorRepository.kt +++ b/data-jpa-kotlin/src/main/kotlin/com/example/data/jpa/kotlin/AutorRepository.kt @@ -7,8 +7,8 @@ import java.util.* interface AuthorRepository : JpaRepository { - fun findByNameContainingIgnoreCase(partialName: String?): Optional? + fun findByNameContainingIgnoreCase(partialName: String): Author? @Query("SELECT a FROM Author a WHERE a.name = :name") - fun queryFindByName(name: String?): Optional? + fun queryFindByName(name: String): Author? } diff --git a/data-jpa-kotlin/src/main/kotlin/com/example/data/jpa/kotlin/model/Autor.kt b/data-jpa-kotlin/src/main/kotlin/com/example/data/jpa/kotlin/model/Autor.kt index 2570cf01..3a52e6e5 100644 --- a/data-jpa-kotlin/src/main/kotlin/com/example/data/jpa/kotlin/model/Autor.kt +++ b/data-jpa-kotlin/src/main/kotlin/com/example/data/jpa/kotlin/model/Autor.kt @@ -4,21 +4,12 @@ import jakarta.persistence.* import java.util.* @Entity -class Author { - - @Id - @GeneratedValue - var id: Long? = null - var name: String? = null +class Author(@Id @GeneratedValue var id: Long?, var name: String?) { @OneToMany(cascade = [CascadeType.ALL]) var books: Set? = null - protected constructor() {} - - constructor(id: Long?, name: String?, books: Set?) { - this.id = id - this.name = name + constructor(id: Long?, name: String?, books: Set?) : this(id, name) { this.books = books } diff --git a/data-jpa-kotlin/src/main/kotlin/com/example/data/jpa/kotlin/model/Book.kt b/data-jpa-kotlin/src/main/kotlin/com/example/data/jpa/kotlin/model/Book.kt index 798889f5..11d81381 100644 --- a/data-jpa-kotlin/src/main/kotlin/com/example/data/jpa/kotlin/model/Book.kt +++ b/data-jpa-kotlin/src/main/kotlin/com/example/data/jpa/kotlin/model/Book.kt @@ -6,20 +6,7 @@ import jakarta.persistence.Id import java.util.* @Entity -class Book { - - @Id - @GeneratedValue - var id: Long? = null - private set - var title: String? = null - - protected constructor() {} - - constructor(id: Long?, title: String?) { - this.id = id - this.title = title - } +class Book(@Id @GeneratedValue var id: Long?, var title: String?) { override fun equals(other: Any?): Boolean { if (this === other) { From a1ccfbed31825621a03f4d0dd7e84a05784ecb91 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Wed, 19 Oct 2022 09:20:18 +0200 Subject: [PATCH 3/3] Add postPersist method --- .../main/kotlin/com/example/data/jpa/kotlin/model/Autor.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/data-jpa-kotlin/src/main/kotlin/com/example/data/jpa/kotlin/model/Autor.kt b/data-jpa-kotlin/src/main/kotlin/com/example/data/jpa/kotlin/model/Autor.kt index 3a52e6e5..4c7d7ed8 100644 --- a/data-jpa-kotlin/src/main/kotlin/com/example/data/jpa/kotlin/model/Autor.kt +++ b/data-jpa-kotlin/src/main/kotlin/com/example/data/jpa/kotlin/model/Autor.kt @@ -13,6 +13,11 @@ class Author(@Id @GeneratedValue var id: Long?, var name: String?) { this.books = books } + @PostPersist + fun postPersist() { + println("Persisted Author $id") + } + override fun equals(other: Any?): Boolean { if (this === other) { return true