From 74d38db9c53135d89f8bef66fc97687f1704d545 Mon Sep 17 00:00:00 2001 From: Dean Wette Date: Fri, 3 May 2024 11:22:23 -0500 Subject: [PATCH] Refactor documentation for Java Records and Kotlin Data Classes to ensure asciidoc snippets have examples for all languages, and to eliminate embedded code in asciidoc (that is not compiled and untested). closes #2896 --- .../jdbc-example-records-groovy/build.gradle | 28 ++++ .../gradle.properties | 1 + .../src/main/groovy/example/Book.groovy | 29 ++++ .../src/main/groovy/example/BookDTO.groovy | 11 ++ .../main/groovy/example/BookRepository.groovy | 114 +++++++++++++++ .../src/main/resources/application.yml | 8 ++ .../groovy/example/BookRepositorySpec.groovy | 59 ++++++++ .../src/test/resources/logback.xml | 12 ++ .../jdbc-example-records-java/build.gradle | 1 + .../src/main/java/example/BookDTO.java | 4 +- .../jdbc-example-records-kotlin/build.gradle | 34 +++++ .../gradle.properties | 1 + .../src/main/kotlin/example/Book.kt | 14 ++ .../src/main/kotlin/example/BookDTO.kt | 9 ++ .../src/main/kotlin/example/BookRepository.kt | 123 ++++++++++++++++ .../src/main/resources/application.yml | 8 ++ .../test/kotlin/example/BookRepositorySpec.kt | 132 ++++++++++++++++++ .../src/test/resources/logback.xml | 12 ++ settings.gradle | 2 + src/main/docs/guide/dbc/javaRecords.adoc | 15 -- .../dbc/javaRecordsKotlinDataClasses.adoc | 15 ++ src/main/docs/guide/dbc/kotlinData.adoc | 28 ---- src/main/docs/guide/toc.yml | 3 +- 23 files changed, 616 insertions(+), 47 deletions(-) create mode 100644 doc-examples/jdbc-example-records-groovy/build.gradle create mode 100644 doc-examples/jdbc-example-records-groovy/gradle.properties create mode 100644 doc-examples/jdbc-example-records-groovy/src/main/groovy/example/Book.groovy create mode 100644 doc-examples/jdbc-example-records-groovy/src/main/groovy/example/BookDTO.groovy create mode 100644 doc-examples/jdbc-example-records-groovy/src/main/groovy/example/BookRepository.groovy create mode 100644 doc-examples/jdbc-example-records-groovy/src/main/resources/application.yml create mode 100644 doc-examples/jdbc-example-records-groovy/src/test/groovy/example/BookRepositorySpec.groovy create mode 100644 doc-examples/jdbc-example-records-groovy/src/test/resources/logback.xml create mode 100644 doc-examples/jdbc-example-records-kotlin/build.gradle create mode 100644 doc-examples/jdbc-example-records-kotlin/gradle.properties create mode 100644 doc-examples/jdbc-example-records-kotlin/src/main/kotlin/example/Book.kt create mode 100644 doc-examples/jdbc-example-records-kotlin/src/main/kotlin/example/BookDTO.kt create mode 100644 doc-examples/jdbc-example-records-kotlin/src/main/kotlin/example/BookRepository.kt create mode 100644 doc-examples/jdbc-example-records-kotlin/src/main/resources/application.yml create mode 100644 doc-examples/jdbc-example-records-kotlin/src/test/kotlin/example/BookRepositorySpec.kt create mode 100644 doc-examples/jdbc-example-records-kotlin/src/test/resources/logback.xml delete mode 100644 src/main/docs/guide/dbc/javaRecords.adoc create mode 100644 src/main/docs/guide/dbc/javaRecordsKotlinDataClasses.adoc delete mode 100644 src/main/docs/guide/dbc/kotlinData.adoc diff --git a/doc-examples/jdbc-example-records-groovy/build.gradle b/doc-examples/jdbc-example-records-groovy/build.gradle new file mode 100644 index 00000000000..41385f28866 --- /dev/null +++ b/doc-examples/jdbc-example-records-groovy/build.gradle @@ -0,0 +1,28 @@ +plugins { + id "groovy" + id "io.micronaut.build.internal.data-example" +} + +application { + mainClass = "example.Application" +} + +micronaut { + version libs.versions.micronaut.platform.get() + runtime "netty" + testRuntime "spock" +} + +dependencies { + compileOnly projects.micronautDataProcessor + + implementation projects.micronautDataJdbc + implementation mnSerde.micronaut.serde.support + implementation mn.micronaut.http.client + implementation mnValidation.micronaut.validation + implementation(libs.managed.jakarta.persistence.api) + + runtimeOnly mnSql.micronaut.jdbc.tomcat + runtimeOnly mnLogging.logback.classic + runtimeOnly mnSql.h2 +} diff --git a/doc-examples/jdbc-example-records-groovy/gradle.properties b/doc-examples/jdbc-example-records-groovy/gradle.properties new file mode 100644 index 00000000000..2f7c48213f8 --- /dev/null +++ b/doc-examples/jdbc-example-records-groovy/gradle.properties @@ -0,0 +1 @@ +skipDocumentation=true diff --git a/doc-examples/jdbc-example-records-groovy/src/main/groovy/example/Book.groovy b/doc-examples/jdbc-example-records-groovy/src/main/groovy/example/Book.groovy new file mode 100644 index 00000000000..88664db49f2 --- /dev/null +++ b/doc-examples/jdbc-example-records-groovy/src/main/groovy/example/Book.groovy @@ -0,0 +1,29 @@ + +package example + +import io.micronaut.core.annotation.Nullable +import io.micronaut.data.annotation.DateCreated +import io.micronaut.data.annotation.MappedEntity +import jakarta.persistence.* + +@MappedEntity // <1> +class Book { + @Id @GeneratedValue Long id // <2> + @DateCreated @Nullable Date dateCreated + + private String title + private int pages + + Book(String title, int pages) { + this.title = title + this.pages = pages + } + + String getTitle() { + return title + } + + int getPages() { + return pages + } +} diff --git a/doc-examples/jdbc-example-records-groovy/src/main/groovy/example/BookDTO.groovy b/doc-examples/jdbc-example-records-groovy/src/main/groovy/example/BookDTO.groovy new file mode 100644 index 00000000000..9b1087c0a46 --- /dev/null +++ b/doc-examples/jdbc-example-records-groovy/src/main/groovy/example/BookDTO.groovy @@ -0,0 +1,11 @@ + +package example + +import io.micronaut.serde.annotation.Serdeable + +@Serdeable +class BookDTO { + + String title + int pages +} diff --git a/doc-examples/jdbc-example-records-groovy/src/main/groovy/example/BookRepository.groovy b/doc-examples/jdbc-example-records-groovy/src/main/groovy/example/BookRepository.groovy new file mode 100644 index 00000000000..6aebd45bf09 --- /dev/null +++ b/doc-examples/jdbc-example-records-groovy/src/main/groovy/example/BookRepository.groovy @@ -0,0 +1,114 @@ + +// tag::repository[] +package example + +import io.micronaut.core.annotation.NonNull +import io.micronaut.data.annotation.* +import io.micronaut.data.annotation.sql.Procedure +import io.micronaut.data.jdbc.annotation.JdbcRepository +import io.micronaut.data.model.* +import io.micronaut.data.model.query.builder.sql.Dialect +import io.micronaut.data.repository.CrudRepository +import java.util.List + + +@JdbcRepository(dialect = Dialect.H2) // <1> +interface BookRepository extends CrudRepository { // <2> +// end::repository[] + + // tag::simple[] + Book findByTitle(String title); + + Book getByTitle(String title); + + Book retrieveByTitle(String title); + // end::simple[] + + // tag::greaterthan[] + List findByPagesGreaterThan(int pageCount); + // end::greaterthan[] + + // tag::logical[] + List findByPagesGreaterThanOrTitleLike(int pageCount, String title); + // end::logical[] + + // tag::simple-alt[] + // tag::repository[] + Book find(String title); + // end::simple-alt[] + // end::repository[] + + // tag::pageable[] + List findByPagesGreaterThan(int pageCount, Pageable pageable); + + Page findByTitleLike(String title, Pageable pageable); + + Slice list(Pageable pageable); + // end::pageable[] + + // tag::simple-projection[] + List findTitleByPagesGreaterThan(int pageCount); + // end::simple-projection[] + + // tag::top-projection[] + List findTop3ByTitleLike(String title); + // end::top-projection[] + + // tag::ordering[] + List listOrderByTitle(); + + List listOrderByTitleDesc(); + // end::ordering[] + + // tag::explicit[] + @Query("SELECT * FROM Book AS b WHERE b.title = :t ORDER BY b.title") + List listBooks(String t); + // end::explicit[] + + // tag::save[] + Book persist(Book entity); + // end::save[] + + // tag::save2[] + Book persist(String title, int pages); + // end::save2[] + + // tag::update[] + void update(@Id Long id, int pages); + + void update(@Id Long id, String title); + // end::update[] + + // tag::update2[] + void updateByTitle(String title, int pages); + // end::update2[] + + // tag::deleteall[] + void deleteAll(); + // end::deleteall[] + + // tag::deleteone[] + void delete(String title); + // end::deleteone[] + + // tag::deleteby[] + void deleteByTitleLike(String title); + // end::deleteby[] + + // tag::dto[] + BookDTO findOne(String title); + // end::dto[] + + // tag::native[] + @Query("select * from book b where b.title like :title limit 5") + List findBooks(String title); + // end::native[] + + // tag::procedure[] + @Procedure + Long calculateSum(@NonNull Long bookId); + // end::procedure[] + +// tag::repository[] +} +// end::repository[] diff --git a/doc-examples/jdbc-example-records-groovy/src/main/resources/application.yml b/doc-examples/jdbc-example-records-groovy/src/main/resources/application.yml new file mode 100644 index 00000000000..a38a6f00501 --- /dev/null +++ b/doc-examples/jdbc-example-records-groovy/src/main/resources/application.yml @@ -0,0 +1,8 @@ +datasources: + default: + url: jdbc:h2:mem:devDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE + driverClassName: org.h2.Driver + username: sa + password: '' + schema-generate: CREATE_DROP + dialect: H2 diff --git a/doc-examples/jdbc-example-records-groovy/src/test/groovy/example/BookRepositorySpec.groovy b/doc-examples/jdbc-example-records-groovy/src/test/groovy/example/BookRepositorySpec.groovy new file mode 100644 index 00000000000..7c7e1e26dc4 --- /dev/null +++ b/doc-examples/jdbc-example-records-groovy/src/test/groovy/example/BookRepositorySpec.groovy @@ -0,0 +1,59 @@ +package example + +import io.micronaut.context.BeanContext +import io.micronaut.test.extensions.spock.annotation.MicronautTest +import spock.lang.Shared +import spock.lang.Specification + +import jakarta.inject.Inject + +@MicronautTest +class BookRepositorySpec extends Specification { + + @Inject @Shared BookRepository bookRepository + + @Inject + BeanContext beanContext + + void 'test CRUD operations'() { + + when: "Create: Save a new book" + + // tag::save[] + Book book = new Book("The Stand", 1000) + bookRepository.save(book) + // end::save[] + Long id = book.id + + then: "An ID was assigned" + id != null + + when: "Read a book from the database" + // tag::read[] + book = bookRepository.findById(id).orElse(null) + // end::read[] + + then:"The book was read" + book != null + book.title == 'The Stand' + + // Check the count + bookRepository.count() == 1 + bookRepository.findAll().iterator().hasNext() + + when: "The book is updated" + // tag::update[] + bookRepository.update(book.getId(), "Changed") + // end::update[] + book = bookRepository.findById(id).orElse(null) + then: "The title was changed" + book.title == 'Changed' + + when: "The book is deleted" + // tag::delete[] + bookRepository.deleteById(id) + // end::delete[] + then:"It is gone" + bookRepository.count() == 0 + } +} diff --git a/doc-examples/jdbc-example-records-groovy/src/test/resources/logback.xml b/doc-examples/jdbc-example-records-groovy/src/test/resources/logback.xml new file mode 100644 index 00000000000..abf9f39e7ed --- /dev/null +++ b/doc-examples/jdbc-example-records-groovy/src/test/resources/logback.xml @@ -0,0 +1,12 @@ + + + + + %cyan(%d{HH:mm:ss.SSS}) %gray([%thread]) %highlight(%-5level) %magenta(%logger{36}) - %msg%n + + + + + + + diff --git a/doc-examples/jdbc-example-records-java/build.gradle b/doc-examples/jdbc-example-records-java/build.gradle index 812e9ec3419..78c83ed55f0 100644 --- a/doc-examples/jdbc-example-records-java/build.gradle +++ b/doc-examples/jdbc-example-records-java/build.gradle @@ -17,6 +17,7 @@ dependencies { annotationProcessor mnValidation.micronaut.validation implementation projects.micronautDataJdbc + implementation mnSerde.micronaut.serde.support implementation mn.micronaut.http.client implementation mnValidation.micronaut.validation implementation(libs.managed.jakarta.persistence.api) diff --git a/doc-examples/jdbc-example-records-java/src/main/java/example/BookDTO.java b/doc-examples/jdbc-example-records-java/src/main/java/example/BookDTO.java index a24e8c86a7e..ea11a131580 100644 --- a/doc-examples/jdbc-example-records-java/src/main/java/example/BookDTO.java +++ b/doc-examples/jdbc-example-records-java/src/main/java/example/BookDTO.java @@ -1,9 +1,9 @@ package example; -import io.micronaut.core.annotation.Introspected; +import io.micronaut.serde.annotation.Serdeable; -@Introspected +@Serdeable public class BookDTO { private String title; diff --git a/doc-examples/jdbc-example-records-kotlin/build.gradle b/doc-examples/jdbc-example-records-kotlin/build.gradle new file mode 100644 index 00000000000..37a4648830e --- /dev/null +++ b/doc-examples/jdbc-example-records-kotlin/build.gradle @@ -0,0 +1,34 @@ +plugins { + id "org.jetbrains.kotlin.jvm" + id "org.jetbrains.kotlin.kapt" + id "org.jetbrains.kotlin.plugin.allopen" + id "io.micronaut.build.internal.data-kotlin-example" +} + +application { + mainClass = "example.ApplicationKt" +} + +micronaut { + version libs.versions.micronaut.platform.get() + runtime "netty" + testRuntime "junit5" +} + +dependencies { + kapt projects.micronautDataProcessor + kapt mnValidation.micronaut.validation + + implementation mnSerde.micronaut.serde.support + implementation projects.micronautDataJdbc + implementation mn.micronaut.http.client + implementation mnValidation.micronaut.validation + implementation libs.managed.jakarta.persistence.api + + implementation libs.kotlin.coroutines + implementation libs.kotlin.coroutines.reactive + + runtimeOnly mnSql.micronaut.jdbc.tomcat + runtimeOnly mnLogging.logback.classic + runtimeOnly mnSql.h2 +} diff --git a/doc-examples/jdbc-example-records-kotlin/gradle.properties b/doc-examples/jdbc-example-records-kotlin/gradle.properties new file mode 100644 index 00000000000..2f7c48213f8 --- /dev/null +++ b/doc-examples/jdbc-example-records-kotlin/gradle.properties @@ -0,0 +1 @@ +skipDocumentation=true diff --git a/doc-examples/jdbc-example-records-kotlin/src/main/kotlin/example/Book.kt b/doc-examples/jdbc-example-records-kotlin/src/main/kotlin/example/Book.kt new file mode 100644 index 00000000000..dbf9efa6224 --- /dev/null +++ b/doc-examples/jdbc-example-records-kotlin/src/main/kotlin/example/Book.kt @@ -0,0 +1,14 @@ +package example + +import io.micronaut.core.annotation.Nullable +import io.micronaut.data.annotation.* +import java.util.* + +@MappedEntity // (1) +data class Book(@Id + @field:Id @GeneratedValue var id: Long?, // (2) + @DateCreated @Nullable var dateCreated: Date? = null, + var title: String, + var pages: Int = 0) { + constructor(title: String, pages: Int) : this(null, null, title, pages) +} diff --git a/doc-examples/jdbc-example-records-kotlin/src/main/kotlin/example/BookDTO.kt b/doc-examples/jdbc-example-records-kotlin/src/main/kotlin/example/BookDTO.kt new file mode 100644 index 00000000000..a19827e2456 --- /dev/null +++ b/doc-examples/jdbc-example-records-kotlin/src/main/kotlin/example/BookDTO.kt @@ -0,0 +1,9 @@ +package example + +import io.micronaut.serde.annotation.Serdeable + +@Serdeable +data class BookDTO( + var title: String, + var pages: Int +) diff --git a/doc-examples/jdbc-example-records-kotlin/src/main/kotlin/example/BookRepository.kt b/doc-examples/jdbc-example-records-kotlin/src/main/kotlin/example/BookRepository.kt new file mode 100644 index 00000000000..8e5e9f646d0 --- /dev/null +++ b/doc-examples/jdbc-example-records-kotlin/src/main/kotlin/example/BookRepository.kt @@ -0,0 +1,123 @@ + +// tag::repository[] +package example + +import io.micronaut.context.annotation.Executable +import io.micronaut.core.annotation.NonNull +import io.micronaut.data.annotation.Id +import io.micronaut.data.annotation.Query +import io.micronaut.data.annotation.sql.Procedure +import io.micronaut.data.jdbc.annotation.JdbcRepository +import io.micronaut.data.model.Page +import io.micronaut.data.model.Pageable +import io.micronaut.data.model.Slice +import io.micronaut.data.model.query.builder.sql.Dialect +import io.micronaut.data.repository.CrudRepository +import jakarta.transaction.Transactional + +@JdbcRepository(dialect = Dialect.H2) // <1> +interface BookRepository : CrudRepository { // <2> +// end::repository[] + + // tag::simple[] + fun findByTitle(title: String): Book + + fun getByTitle(title: String): Book + + fun retrieveByTitle(title: String): Book + // end::simple[] + + // tag::simple-alt[] + // tag::repository[] + @Executable + fun find(title: String): Book + // end::simple-alt[] + // end::repository[] + + // tag::greaterthan[] + fun findByPagesGreaterThan(pageCount: Int): List + // end::greaterthan[] + + // tag::logical[] + fun findByPagesGreaterThanOrTitleLike(pageCount: Int, title: String): List + // end::logical[] + + // tag::pageable[] + fun findByPagesGreaterThan(pageCount: Int, pageable: Pageable): List + + fun findByTitleLike(title: String, pageable: Pageable): Page + + fun list(pageable: Pageable): Slice + // end::pageable[] + + // tag::simple-projection[] + fun findTitleByPagesGreaterThan(pageCount: Int): List + // end::simple-projection[] + + // tag::top-projection[] + fun findTop3ByTitleLike(title: String): List + // end::top-projection[] + + // tag::ordering[] + fun listOrderByTitle(): List + + fun listOrderByTitleDesc(): List + // end::ordering[] + + // tag::explicit[] + @Query("SELECT * FROM book as b WHERE b.title = :t ORDER BY b.title") + fun listBooks(t: String): List + // end::explicit[] + + // tag::save[] + fun persist(entity: Book): Book + // end::save[] + + // tag::save2[] + fun persist(title: String, pages: Int): Book + // end::save2[] + + // tag::update[] + fun update(@Id id: Long?, pages: Int) + + fun update(@Id id: Long?, title: String) + // end::update[] + + // tag::update2[] + fun updateByTitle(title: String, pages: Int) + // end::update2[] + + // tag::deleteall[] + override fun deleteAll() + // end::deleteall[] + + @Transactional(Transactional.TxType.MANDATORY) + fun deleteAllRequiresTx() { + deleteAll() + } + + // tag::deleteone[] + fun delete(title: String) + // end::deleteone[] + + // tag::deleteby[] + fun deleteByTitleLike(title: String) + // end::deleteby[] + + // tag::dto[] + fun findOne(title: String): BookDTO + // end::dto[] + + // tag::native[] + @Query("select * from book b where b.title like :title limit 5") + fun findBooks(title: String): List + // end::native[] + + // tag::procedure[] + @Procedure + fun calculateSum(bookId: @NonNull Long): Long + // end::procedure[] + +// tag::repository[] +} +// end::repository[] diff --git a/doc-examples/jdbc-example-records-kotlin/src/main/resources/application.yml b/doc-examples/jdbc-example-records-kotlin/src/main/resources/application.yml new file mode 100644 index 00000000000..a38a6f00501 --- /dev/null +++ b/doc-examples/jdbc-example-records-kotlin/src/main/resources/application.yml @@ -0,0 +1,8 @@ +datasources: + default: + url: jdbc:h2:mem:devDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE + driverClassName: org.h2.Driver + username: sa + password: '' + schema-generate: CREATE_DROP + dialect: H2 diff --git a/doc-examples/jdbc-example-records-kotlin/src/test/kotlin/example/BookRepositorySpec.kt b/doc-examples/jdbc-example-records-kotlin/src/test/kotlin/example/BookRepositorySpec.kt new file mode 100644 index 00000000000..192c0d60910 --- /dev/null +++ b/doc-examples/jdbc-example-records-kotlin/src/test/kotlin/example/BookRepositorySpec.kt @@ -0,0 +1,132 @@ +package example + +import io.micronaut.context.BeanContext +import io.micronaut.data.annotation.Query +import io.micronaut.data.model.Pageable +import io.micronaut.test.extensions.junit5.annotation.MicronautTest +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import java.util.* +import jakarta.inject.Inject +import org.junit.jupiter.api.BeforeEach + +@MicronautTest +class BookRepositorySpec { + + // tag::inject[] + @Inject + lateinit var bookRepository: BookRepository + // end::inject[] + + // tag::metadata[] + @Inject + lateinit var beanContext: BeanContext + + @BeforeEach + fun cleanup() { + bookRepository.deleteAll() + } + + @Test + fun testAnnotationMetadata() { + val query = beanContext.getBeanDefinition(BookRepository::class.java) // <1> + .getRequiredMethod("find", String::class.java) // <2> + .annotationMetadata + .stringValue(Query::class.java) // <3> + .orElse(null) + + + assertEquals( // <4> + "SELECT book_.`id`,book_.`date_created`,book_.`title`,book_.`pages` FROM `book` book_ WHERE (book_.`title` = ?)", + query + ) + + } + // end::metadata[] + + @Test + fun testCrud() { + assertNotNull(bookRepository) + + // Create: Save a new book + // tag::save[] + var book = Book(0, null, "The Stand", 1000) + bookRepository.save(book) + // end::save[] + + val id = book.id + assertNotNull(id) + + // Read: Read a book from the database + // tag::read[] + book = bookRepository.findById(id).orElse(null) + // end::read[] + assertNotNull(book) + assertEquals("The Stand", book.title) + + // Check the count + assertEquals(1, bookRepository.count()) + assertTrue(bookRepository.findAll().iterator().hasNext()) + + // Update: Update the book and save it again + // tag::update[] + bookRepository.update(book.id, "Changed") + // end::update[] + book = bookRepository.findById(id).orElse(null) + assertEquals("Changed", book.title) + + // Delete: Delete the book + // tag::delete[] + bookRepository.deleteById(id) + // end::delete[] + assertEquals(0, bookRepository.count()) + } + + @Test + fun testPageable() { + // tag::saveall[] + bookRepository.saveAll(Arrays.asList( + Book(0, null,"The Stand", 1000), + Book(0, null,"The Shining", 600), + Book(0, null,"The Power of the Dog", 500), + Book(0, null,"The Border", 700), + Book(0, null,"Along Came a Spider", 300), + Book(0, null,"Pet Cemetery", 400), + Book(0, null,"A Game of Thrones", 900), + Book(0, null,"A Clash of Kings", 1100) + )) + // end::saveall[] + + // tag::pageable[] + val slice = bookRepository.list(Pageable.from(0, 3)) + val resultList = bookRepository.findByPagesGreaterThan(500, Pageable.from(0, 3)) + val page = bookRepository.findByTitleLike("The%", Pageable.from(0, 3)) + // end::pageable[] + + assertEquals( + 3, + slice.numberOfElements + ) + assertEquals( + 3, + resultList.size + ) + assertEquals( + 3, + page.numberOfElements + ) + assertEquals( + 4, + page.totalSize + ) + + } + + @Test + fun testDto() { + bookRepository.save(Book(0, null, "The Shining", 400)) + val bookDTO = bookRepository.findOne("The Shining") + + assertEquals("The Shining", bookDTO.title) + } +} diff --git a/doc-examples/jdbc-example-records-kotlin/src/test/resources/logback.xml b/doc-examples/jdbc-example-records-kotlin/src/test/resources/logback.xml new file mode 100644 index 00000000000..abf9f39e7ed --- /dev/null +++ b/doc-examples/jdbc-example-records-kotlin/src/test/resources/logback.xml @@ -0,0 +1,12 @@ + + + + + %cyan(%d{HH:mm:ss.SSS}) %gray([%thread]) %highlight(%-5level) %magenta(%logger{36}) - %msg%n + + + + + + + diff --git a/settings.gradle b/settings.gradle index b15908736ea..569aff82cd1 100644 --- a/settings.gradle +++ b/settings.gradle @@ -91,6 +91,8 @@ include 'doc-examples:jdbc-example-java' include 'doc-examples:jdbc-example-groovy' include 'doc-examples:jdbc-example-kotlin' include 'doc-examples:jdbc-example-records-java' +include 'doc-examples:jdbc-example-records-groovy' +include 'doc-examples:jdbc-example-records-kotlin' include 'doc-examples:jdbc-multitenancy-datasource-example-java' include 'doc-examples:jdbc-multitenancy-schema-example-java' diff --git a/src/main/docs/guide/dbc/javaRecords.adoc b/src/main/docs/guide/dbc/javaRecords.adoc deleted file mode 100644 index c5fcb692bf3..00000000000 --- a/src/main/docs/guide/dbc/javaRecords.adoc +++ /dev/null @@ -1,15 +0,0 @@ -Since 2.3.0, Micronaut Data JDBC / R2DBC has support for using Java 16 records to model entities. - -The following record class demonstrates this capability: - -snippet::example.Book[project-base="doc-examples/jdbc-example-records", source="main"] - -<1> The ann:data.annotation.MappedEntity[] annotation is used on the record -<2> The database identifier is annotated with ann:data.annotation.Id[] and ann:data.annotation.GeneratedValue[] plus marked as `@Nullable` - -Since records are immutable constructor arguments that are generated values need to be marked as `@Nullable` and you should pass `null` for those arguments. The following presents an example: - -snippet::example.BookRepositorySpec[project-base="doc-examples/jdbc-example-records", tags="save", indent="0"] - -It is important to note that the returned instance is not the same as the instance passed to the `save` method. When a write operation is performed Micronaut Data will use a copy-constructor approach to populate the database identifier and return a new instance from the `save` method. - diff --git a/src/main/docs/guide/dbc/javaRecordsKotlinDataClasses.adoc b/src/main/docs/guide/dbc/javaRecordsKotlinDataClasses.adoc new file mode 100644 index 00000000000..81393757894 --- /dev/null +++ b/src/main/docs/guide/dbc/javaRecordsKotlinDataClasses.adoc @@ -0,0 +1,15 @@ +Since 2.3.0, Micronaut Data JDBC / R2DBC has support for using Java 16 records to model entities. For Kotlin projects Micronaut Data JDBC / R2DBC supports using immutable data classes as model entities. With Groovy POGOs there is not a need for using Java records. + +The following example illustrates demonstrates these capabilities: + +snippet::example.Book[project-base="doc-examples/jdbc-example-records", source="main"] + +<1> The ann:data.annotation.MappedEntity[] annotation is used on the Java record. For Kotlin a data class is used instead, and for Groovy a standard POGO. +<2> The database identifier is annotated with ann:data.annotation.Id[] and ann:data.annotation.GeneratedValue[] plus marked as `@Nullable` + +Since records are immutable constructor arguments that are generated values need to be marked as `@Nullable` and you should pass `null` for those arguments. Kotlin data classes are implemented similarly: to modify an entity a copy-constructor is used and every modification means a new entity instance. Groovy uses an idiomatic POGO. The following example illustrates this: + +snippet::example.BookRepositorySpec[project-base="doc-examples/jdbc-example-records", tags="save", indent="0"] + +NOTE: It is important to keep in mind that for Java records and Kotlin data classes returned instances are not the same objects passed to the `save` method. When a write operation is performed Micronaut Data uses a copy-constructor approach to populate the database identifier and return a new instance from the `save` method. + diff --git a/src/main/docs/guide/dbc/kotlinData.adoc b/src/main/docs/guide/dbc/kotlinData.adoc deleted file mode 100644 index a809939f623..00000000000 --- a/src/main/docs/guide/dbc/kotlinData.adoc +++ /dev/null @@ -1,28 +0,0 @@ -Micronaut Data JDBC / R2DBC supports using immutable Kotlin data classes as model entities. -The implementation is the same as for Java 16 records: to modify an entity a copy-constructor will be used and every modification means a new entity instance. - -[source, kotlin] -.src/main/kotlin/example/Student.kt ----- -package example - -import io.micronaut.data.annotation.GeneratedValue -import io.micronaut.data.annotation.Id -import io.micronaut.data.annotation.MappedEntity -import io.micronaut.data.annotation.Relation - -@MappedEntity -data class Student( - @field:Id @GeneratedValue - val id: Long?, - val name: String, - @Relation(value = Relation.Kind.MANY_TO_MANY, cascade = [Relation.Cascade.PERSIST]) - val courses: List, - @Relation(value = Relation.Kind.ONE_TO_MANY, mappedBy = "student") - val ratings: List -) { - constructor(name: String, items: List) : this(null, name, items, emptyList()) -} ----- - -NOTE: Generated values and relations that cannot be created during the entity initialization should be declared as nullable. diff --git a/src/main/docs/guide/toc.yml b/src/main/docs/guide/toc.yml index 6108eccca6d..43ea2a8be61 100644 --- a/src/main/docs/guide/toc.yml +++ b/src/main/docs/guide/toc.yml @@ -87,8 +87,7 @@ dbc: mappedPropertyAlias: Using @MappedProperty alias sqlJsonType: JSON Column Support sqlJsonView: JSON View - javaRecords: Support for Java 16 Records - kotlinData: Support for Kotlin immutable data classes + javaRecordsKotlinDataClasses: Support for Java Records and Kotlin data classes dbcDataTypes: Data Types dbcAttributeConverter: Using Attribute Converter dbcJoinQueries: Join Queries