diff --git a/doc-examples/jdbc-spring-template-example-groovy/build.gradle b/doc-examples/jdbc-spring-template-example-groovy/build.gradle new file mode 100644 index 0000000000..47d6bf7bbd --- /dev/null +++ b/doc-examples/jdbc-spring-template-example-groovy/build.gradle @@ -0,0 +1,23 @@ +plugins { + id "groovy" + id "io.micronaut.build.internal.data-example" +} + +micronaut { + version libs.versions.micronaut.platform.get() + runtime "netty" + testRuntime "spock" +} + +dependencies { + compileOnly projects.micronautDataProcessor + compileOnly mnValidation.micronaut.validation.processor + + implementation projects.micronautDataJdbc + implementation projects.micronautDataSpringJdbc + implementation mnValidation.micronaut.validation + + runtimeOnly mnSql.micronaut.jdbc.tomcat + runtimeOnly mnLogging.logback.classic + runtimeOnly mnSql.h2 +} diff --git a/doc-examples/jdbc-spring-template-example-groovy/gradle.properties b/doc-examples/jdbc-spring-template-example-groovy/gradle.properties new file mode 100644 index 0000000000..2f7c48213f --- /dev/null +++ b/doc-examples/jdbc-spring-template-example-groovy/gradle.properties @@ -0,0 +1 @@ +skipDocumentation=true diff --git a/doc-examples/jdbc-spring-template-example-groovy/src/main/groovy/example/AbstractBookRepository.groovy b/doc-examples/jdbc-spring-template-example-groovy/src/main/groovy/example/AbstractBookRepository.groovy new file mode 100644 index 0000000000..fa50422d03 --- /dev/null +++ b/doc-examples/jdbc-spring-template-example-groovy/src/main/groovy/example/AbstractBookRepository.groovy @@ -0,0 +1,33 @@ +package example + +import io.micronaut.context.annotation.Requires +import io.micronaut.core.annotation.NonNull +import io.micronaut.data.connection.jdbc.advice.DelegatingDataSource +import io.micronaut.data.jdbc.annotation.JdbcRepository +import io.micronaut.data.model.query.builder.sql.Dialect +import io.micronaut.data.repository.CrudRepository +import jakarta.transaction.Transactional +import jakarta.validation.Valid +import jakarta.validation.constraints.NotNull +import org.springframework.jdbc.core.JdbcTemplate + +import javax.sql.DataSource + +@Requires(property = 'spec.name', value = 'BookRepositorySpec') +// tag::clazz[] +@JdbcRepository(dialect = Dialect.H2) +abstract class AbstractBookRepository implements CrudRepository<@Valid Book, @NotNull Long> { + + private final JdbcTemplate jdbcTemplate; //<2> + + AbstractBookRepository(DataSource dataSource) { // <1> + this.jdbcTemplate = new JdbcTemplate(DelegatingDataSource.unwrapDataSource(dataSource)); //<2> + } + + @Transactional + List findByTitle(@NonNull @NotNull String title) { + return jdbcTemplate.queryForList('SELECT * FROM Book AS book WHERE book.title = ?', title) // <3> + .collect(m -> new Book(m.id as Long, m.title as String, m.pages as Integer)) + } +} +// end::clazz[] diff --git a/doc-examples/jdbc-spring-template-example-groovy/src/main/groovy/example/Book.groovy b/doc-examples/jdbc-spring-template-example-groovy/src/main/groovy/example/Book.groovy new file mode 100644 index 0000000000..03453ca610 --- /dev/null +++ b/doc-examples/jdbc-spring-template-example-groovy/src/main/groovy/example/Book.groovy @@ -0,0 +1,15 @@ +package example + +import groovy.transform.Canonical +import io.micronaut.core.annotation.Nullable +import io.micronaut.data.annotation.GeneratedValue +import io.micronaut.data.annotation.Id +import io.micronaut.data.annotation.MappedEntity + +@Canonical +@MappedEntity +class Book { + @Id @GeneratedValue @Nullable Long id + String title + int pages +} diff --git a/doc-examples/jdbc-spring-template-example-groovy/src/main/resources/application.yml b/doc-examples/jdbc-spring-template-example-groovy/src/main/resources/application.yml new file mode 100644 index 0000000000..25a69c4409 --- /dev/null +++ b/doc-examples/jdbc-spring-template-example-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;NON_KEYWORDS=USER + driverClassName: org.h2.Driver + username: sa + password: '' + schema-generate: CREATE_DROP + dialect: H2 diff --git a/doc-examples/jdbc-spring-template-example-groovy/src/main/resources/logback.xml b/doc-examples/jdbc-spring-template-example-groovy/src/main/resources/logback.xml new file mode 100644 index 0000000000..abf9f39e7e --- /dev/null +++ b/doc-examples/jdbc-spring-template-example-groovy/src/main/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-spring-template-example-groovy/src/test/groovy/example/BookRepositorySpec.groovy b/doc-examples/jdbc-spring-template-example-groovy/src/test/groovy/example/BookRepositorySpec.groovy new file mode 100644 index 0000000000..fc853990a2 --- /dev/null +++ b/doc-examples/jdbc-spring-template-example-groovy/src/test/groovy/example/BookRepositorySpec.groovy @@ -0,0 +1,44 @@ +package example + +import io.micronaut.context.annotation.Property +import io.micronaut.test.extensions.spock.annotation.MicronautTest +import jakarta.inject.Inject +import spock.lang.Specification + +@MicronautTest(transactional = false) +@Property(name = 'spec.name', value = 'BookRepositorySpec') +@Property(name = 'datasources.default.name', value = 'mydb') +@Property(name = 'datasources.default.transaction-manager', value = 'springJdbc') +@Property(name = 'jpa.default.properties.hibernate.hbm2ddl.auto', value = 'create-drop') +class BookRepositorySpec extends Specification { + + @Inject + AbstractBookRepository bookRepository + + void "test Books JdbcTemplate"() { + given: + bookRepository.saveAll([ + new Book(null, 'The Stand', 1000), + new Book(null, 'The Shining', 600), + new Book(null, 'The Power of the Dog', 500), + new Book(null, 'The Border', 700), + new Book(null, 'Along Came a Spider', 300), + new Book(null, 'Pet Cemetery', 400), + new Book(null, 'A Game of Thrones', 900), + new Book(null, 'A Clash of Kings', 1100) + ]) + + when: + List result = bookRepository.findByTitle('The Shining') + + then: + result.size() == 1 + + result[0].id != null + result[0].title == 'The Shining' + result[0].pages == 600 + + cleanup: + bookRepository.deleteAll() + } +} diff --git a/doc-examples/jdbc-spring-template-example-java/build.gradle b/doc-examples/jdbc-spring-template-example-java/build.gradle new file mode 100644 index 0000000000..ad3a42987e --- /dev/null +++ b/doc-examples/jdbc-spring-template-example-java/build.gradle @@ -0,0 +1,22 @@ +plugins { + id "io.micronaut.build.internal.data-example" +} + +micronaut { + version libs.versions.micronaut.platform.get() + runtime "netty" + testRuntime "junit5" +} + +dependencies { + annotationProcessor projects.micronautDataProcessor + annotationProcessor mnValidation.micronaut.validation.processor + + implementation projects.micronautDataJdbc + implementation projects.micronautDataSpringJdbc + implementation mnValidation.micronaut.validation + + runtimeOnly mnSql.micronaut.jdbc.tomcat + runtimeOnly mnLogging.logback.classic + runtimeOnly mnSql.h2 +} diff --git a/doc-examples/jdbc-spring-template-example-java/gradle.properties b/doc-examples/jdbc-spring-template-example-java/gradle.properties new file mode 100644 index 0000000000..2f7c48213f --- /dev/null +++ b/doc-examples/jdbc-spring-template-example-java/gradle.properties @@ -0,0 +1 @@ +skipDocumentation=true diff --git a/doc-examples/jdbc-spring-template-example-java/src/main/java/example/AbstractBookRepository.java b/doc-examples/jdbc-spring-template-example-java/src/main/java/example/AbstractBookRepository.java new file mode 100644 index 0000000000..5fd3ca6026 --- /dev/null +++ b/doc-examples/jdbc-spring-template-example-java/src/main/java/example/AbstractBookRepository.java @@ -0,0 +1,36 @@ +package example; + +import io.micronaut.context.annotation.Requires; +import io.micronaut.core.annotation.NonNull; +import io.micronaut.data.connection.jdbc.advice.DelegatingDataSource; +import io.micronaut.data.jdbc.annotation.JdbcRepository; +import io.micronaut.data.model.query.builder.sql.Dialect; +import io.micronaut.data.repository.CrudRepository; +import jakarta.transaction.Transactional; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import org.springframework.jdbc.core.JdbcTemplate; + +import javax.sql.DataSource; +import java.util.List; + +@Requires(property = "spec.name", value = "BookRepositoryTest") +// tag::clazz[] +@JdbcRepository(dialect = Dialect.H2) +public abstract class AbstractBookRepository implements CrudRepository<@Valid Book, @NotNull Long> { + + private final JdbcTemplate jdbcTemplate; //<2> + + public AbstractBookRepository(DataSource dataSource) { // <1> + this.jdbcTemplate = new JdbcTemplate(DelegatingDataSource.unwrapDataSource(dataSource)); //<2> + } + + @Transactional + public List findByTitle(@NonNull @NotNull String title) { + return jdbcTemplate.queryForList("SELECT * FROM Book AS book WHERE book.title = ?", title) // <3> + .stream() + .map(m -> new Book((Long) m.get("id"), (String) m.get("title"), (Integer) m.get("pages"))) + .toList(); + } +} +// end::clazz[] diff --git a/doc-examples/jdbc-spring-template-example-java/src/main/java/example/Book.java b/doc-examples/jdbc-spring-template-example-java/src/main/java/example/Book.java new file mode 100644 index 0000000000..697b8735d7 --- /dev/null +++ b/doc-examples/jdbc-spring-template-example-java/src/main/java/example/Book.java @@ -0,0 +1,15 @@ +package example; + +import io.micronaut.core.annotation.Nullable; +import io.micronaut.data.annotation.GeneratedValue; +import io.micronaut.data.annotation.Id; +import io.micronaut.data.annotation.MappedEntity; + +// tag::book[] +@MappedEntity +public record Book( + @Id @GeneratedValue @Nullable Long id, + String title, + int pages +) { } +// end::book[] diff --git a/doc-examples/jdbc-spring-template-example-java/src/main/resources/application.yml b/doc-examples/jdbc-spring-template-example-java/src/main/resources/application.yml new file mode 100644 index 0000000000..25a69c4409 --- /dev/null +++ b/doc-examples/jdbc-spring-template-example-java/src/main/resources/application.yml @@ -0,0 +1,8 @@ +datasources: + default: + url: jdbc:h2:mem:devDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE;NON_KEYWORDS=USER + driverClassName: org.h2.Driver + username: sa + password: '' + schema-generate: CREATE_DROP + dialect: H2 diff --git a/doc-examples/jdbc-spring-template-example-java/src/main/resources/logback.xml b/doc-examples/jdbc-spring-template-example-java/src/main/resources/logback.xml new file mode 100644 index 0000000000..abf9f39e7e --- /dev/null +++ b/doc-examples/jdbc-spring-template-example-java/src/main/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-spring-template-example-java/src/test/java/example/BookRepositoryTest.java b/doc-examples/jdbc-spring-template-example-java/src/test/java/example/BookRepositoryTest.java new file mode 100644 index 0000000000..0d17c7eec2 --- /dev/null +++ b/doc-examples/jdbc-spring-template-example-java/src/test/java/example/BookRepositoryTest.java @@ -0,0 +1,50 @@ +package example; + +import io.micronaut.context.annotation.Property; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import jakarta.inject.Inject; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@MicronautTest +@Property(name = "spec.name", value = "BookRepositoryTest") +@Property(name = "datasources.default.name", value = "mydb") +@Property(name = "datasources.default.transaction-manager", value = "springJdbc") +@Property(name = "jpa.default.properties.hibernate.hbm2ddl.auto", value = "create-drop") +class BookRepositoryTest { + + @Inject + AbstractBookRepository bookRepository; + + @AfterEach + void cleanup() { + bookRepository.deleteAll(); + } + + @Test + void testBooksJdbcTemplate() { + bookRepository.saveAll(Arrays.asList( + new Book(null,"The Stand", 1000), + new Book(null,"The Shining", 600), + new Book(null,"The Power of the Dog", 500), + new Book(null,"The Border", 700), + new Book(null,"Along Came a Spider", 300), + new Book(null,"Pet Cemetery", 400), + new Book(null,"A Game of Thrones", 900), + new Book(null,"A Clash of Kings", 1100) + )); + + List result = bookRepository.findByTitle("The Shining"); + assertEquals(1, result.size()); + + assertNotNull(result.get(0).id()); + assertEquals("The Shining", result.get(0).title()); + assertEquals(600, result.get(0).pages()); + } +} diff --git a/doc-examples/jdbc-spring-template-example-kotlin/build.gradle b/doc-examples/jdbc-spring-template-example-kotlin/build.gradle new file mode 100644 index 0000000000..75c01508e9 --- /dev/null +++ b/doc-examples/jdbc-spring-template-example-kotlin/build.gradle @@ -0,0 +1,25 @@ +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" +} + +micronaut { + version libs.versions.micronaut.platform.get() + runtime "netty" + testRuntime "junit5" +} + +dependencies { + kapt projects.micronautDataProcessor + kapt mnValidation.micronaut.validation.processor + + implementation projects.micronautDataJdbc + implementation projects.micronautDataSpringJdbc + implementation mnValidation.micronaut.validation + + runtimeOnly mnSql.micronaut.jdbc.tomcat + runtimeOnly mnLogging.logback.classic + runtimeOnly mnSql.h2 +} diff --git a/doc-examples/jdbc-spring-template-example-kotlin/gradle.properties b/doc-examples/jdbc-spring-template-example-kotlin/gradle.properties new file mode 100644 index 0000000000..2f7c48213f --- /dev/null +++ b/doc-examples/jdbc-spring-template-example-kotlin/gradle.properties @@ -0,0 +1 @@ +skipDocumentation=true diff --git a/doc-examples/jdbc-spring-template-example-kotlin/src/main/kotlin/example/AbstractBookRepository.kt b/doc-examples/jdbc-spring-template-example-kotlin/src/main/kotlin/example/AbstractBookRepository.kt new file mode 100644 index 0000000000..fb0f133f58 --- /dev/null +++ b/doc-examples/jdbc-spring-template-example-kotlin/src/main/kotlin/example/AbstractBookRepository.kt @@ -0,0 +1,25 @@ +package example + +import io.micronaut.context.annotation.Requires +import io.micronaut.data.connection.jdbc.advice.DelegatingDataSource +import io.micronaut.data.jdbc.annotation.JdbcRepository +import io.micronaut.data.model.query.builder.sql.Dialect +import io.micronaut.data.repository.CrudRepository +import jakarta.transaction.Transactional +import jakarta.validation.Valid +import javax.sql.DataSource +import org.springframework.jdbc.core.JdbcTemplate + +@Requires(property = "spec.name", value = "BookRepositoryTest") // tag::clazz[] +// tag::clazz[] +@JdbcRepository(dialect = Dialect.H2) +abstract class AbstractBookRepository(dataSource: DataSource) : CrudRepository<@Valid Book, Long> { // <1> + + private val jdbcTemplate: JdbcTemplate = JdbcTemplate(DelegatingDataSource.unwrapDataSource(dataSource)) //<2> + + @Transactional + open fun findByTitle(title: String) = jdbcTemplate + .queryForList("SELECT * FROM Book AS book WHERE book.title = ?", title) // <3> + .map { m -> Book(m["id"] as Long, m["title"] as String, m["pages"] as Int) } +} +// end::clazz[] diff --git a/doc-examples/jdbc-spring-template-example-kotlin/src/main/kotlin/example/Book.kt b/doc-examples/jdbc-spring-template-example-kotlin/src/main/kotlin/example/Book.kt new file mode 100644 index 0000000000..d9fe287341 --- /dev/null +++ b/doc-examples/jdbc-spring-template-example-kotlin/src/main/kotlin/example/Book.kt @@ -0,0 +1,14 @@ +package example + +import io.micronaut.data.annotation.GeneratedValue +import io.micronaut.data.annotation.Id +import io.micronaut.data.annotation.MappedEntity + +// tag::book[] +@MappedEntity +data class Book( + @field:Id @field:GeneratedValue val id: Long?, + val title: String, + val pages: Int +) +// end::book[] diff --git a/doc-examples/jdbc-spring-template-example-kotlin/src/main/resources/application.yml b/doc-examples/jdbc-spring-template-example-kotlin/src/main/resources/application.yml new file mode 100644 index 0000000000..25a69c4409 --- /dev/null +++ b/doc-examples/jdbc-spring-template-example-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;NON_KEYWORDS=USER + driverClassName: org.h2.Driver + username: sa + password: '' + schema-generate: CREATE_DROP + dialect: H2 diff --git a/doc-examples/jdbc-spring-template-example-kotlin/src/main/resources/logback.xml b/doc-examples/jdbc-spring-template-example-kotlin/src/main/resources/logback.xml new file mode 100644 index 0000000000..abf9f39e7e --- /dev/null +++ b/doc-examples/jdbc-spring-template-example-kotlin/src/main/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-spring-template-example-kotlin/src/test/kotlin/example/BookRepositoryTest.kt b/doc-examples/jdbc-spring-template-example-kotlin/src/test/kotlin/example/BookRepositoryTest.kt new file mode 100644 index 0000000000..ba6ba92265 --- /dev/null +++ b/doc-examples/jdbc-spring-template-example-kotlin/src/test/kotlin/example/BookRepositoryTest.kt @@ -0,0 +1,48 @@ +package example + +import io.micronaut.context.annotation.Property +import io.micronaut.test.extensions.junit5.annotation.MicronautTest +import jakarta.inject.Inject +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Test + +@MicronautTest +@Property(name = "spec.name", value = "BookRepositoryTest") +@Property(name = "datasources.default.name", value = "mydb") +@Property(name = "datasources.default.transaction-manager", value = "springJdbc") +@Property(name = "jpa.default.properties.hibernate.hbm2ddl.auto", value = "create-drop") +internal class BookRepositoryTest { + + @Inject + lateinit var bookRepository: AbstractBookRepository + + @AfterEach + fun cleanup() { + bookRepository.deleteAll() + } + + @Test + fun testBooksJdbcTemplate() { + bookRepository.saveAll( + listOf( + Book(null, "The Stand", 1000), + Book(null, "The Shining", 600), + Book(null, "The Power of the Dog", 500), + Book(null, "The Border", 700), + Book(null, "Along Came a Spider", 300), + Book(null, "Pet Cemetery", 400), + Book(null, "A Game of Thrones", 900), + Book(null, "A Clash of Kings", 1100) + ) + ) + + val result = bookRepository.findByTitle("The Shining") + assertEquals(1, result.size) + + assertNotNull(result[0].id) + assertEquals("The Shining", result[0].title) + assertEquals(600, result[0].pages) + } +} diff --git a/settings.gradle b/settings.gradle index a48d6b4c57..0b01b11af2 100644 --- a/settings.gradle +++ b/settings.gradle @@ -121,6 +121,10 @@ include 'doc-examples:azure-cosmos-example-kotlin' include 'doc-examples:jdbc-spring-tx-example-java' include 'doc-examples:hibernate-spring-tx-example-java' +include 'doc-examples:jdbc-spring-template-example-java' +include 'doc-examples:jdbc-spring-template-example-groovy' +include 'doc-examples:jdbc-spring-template-example-kotlin' + include 'doc-examples:jooq-r2dbc-postgres' include 'test-annotation-processor-java' diff --git a/src/main/docs/guide/dbc/dbcRepositories.adoc b/src/main/docs/guide/dbc/dbcRepositories.adoc index 79639112f9..f38cc5cb6f 100644 --- a/src/main/docs/guide/dbc/dbcRepositories.adoc +++ b/src/main/docs/guide/dbc/dbcRepositories.adoc @@ -34,4 +34,28 @@ As you can see from the above example, using abstract classes can be useful as i The example above uses the api:data.jdbc.runtime.JdbcOperations[] interface which simplifies executing JDBC queries within the context of transactions. -TIP: You could also inject whichever other tool you wish to use to handle more complex queries, such as QueryDSL, JOOQ, Spring JdbcTemplate etc. +You can also integrate whichever other tool you wish to use to handle more complex queries, such as QueryDSL, JOOQ, Spring JdbcTemplate etc. + +For example, to use link:https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/jdbc/core/JdbcTemplate.html[Spring JdbcTemplate], add the following dependencies: + +dependency:micronaut-data-jdbc[groupId="io.micronaut.data"] + +dependency:micronaut-data-spring-jdbc[groupId="io.micronaut.data"] + +The following code illustrates an example that integrates a `JdbcTemplate` instance as part of a api:data.jdbc.annotation.JdbcRepository[@JdbcRepository]. + +snippet::example.AbstractBookRepository[project-base="doc-examples/jdbc-spring-template-example",source="main",tags="clazz"] + +<1> Inject the `java.sql.DataSource` configured by the application. +<2> Instantiate a `JdbcTemplate` object using the injected `DataSource`. +<3> Now the `JdbcTemplate` API can be used to implement repository methods. + +In addition, the transaction manager for Spring JDBC needs to be set in application configuration. + +[configuration] +---- +datasources: + default: + transaction-manager: springJdbc +---- +