From 28b4804d714b7ddace2ad8010638abb3a131d236 Mon Sep 17 00:00:00 2001 From: Radovan Radic Date: Wed, 22 May 2024 11:39:20 +0200 Subject: [PATCH] Additional example docs for programmatic transactions and repositories (#2946) --- .../main/groovy/example/ProductManager.groovy | 30 ++++++++++++++++++- .../groovy/example/ProductRepository.groovy | 10 ++++--- .../groovy/example/ProductManagerSpec.groovy | 18 +++++++++++ .../src/main/java/example/ProductManager.java | 30 ++++++++++++++++++- .../main/java/example/ProductRepository.java | 4 +++ .../test/java/example/ProductManagerSpec.java | 13 ++++++++ .../src/main/kotlin/example/ProductManager.kt | 15 +++++++++- .../main/kotlin/example/ProductRepository.kt | 11 +++++-- .../test/kotlin/example/ProductManagerSpec.kt | 10 +++++++ .../programmaticTransactions.adoc | 4 ++- 10 files changed, 134 insertions(+), 11 deletions(-) diff --git a/doc-examples/jdbc-example-groovy/src/main/groovy/example/ProductManager.groovy b/doc-examples/jdbc-example-groovy/src/main/groovy/example/ProductManager.groovy index 38fb2b4bb5..7f629e3b25 100644 --- a/doc-examples/jdbc-example-groovy/src/main/groovy/example/ProductManager.groovy +++ b/doc-examples/jdbc-example-groovy/src/main/groovy/example/ProductManager.groovy @@ -12,11 +12,14 @@ class ProductManager { private final Connection connection private final TransactionOperations transactionManager + private final ProductRepository productRepository ProductManager(Connection connection, - TransactionOperations transactionManager) { // <1> + TransactionOperations transactionManager, // <1> + ProductRepository productRepository) { this.connection = connection this.transactionManager = transactionManager + this.productRepository = productRepository } Product save(String name, Manufacturer manufacturer) { @@ -46,4 +49,29 @@ class ProductManager { } } } + + /** + * Creates new product using transaction operations and product repository. + * + * @param name the product name + * @param manufacturer the manufacturer + * @return the created product instance + */ + Product saveUsingRepo(String name, Manufacturer manufacturer) { + return transactionManager.executeWrite(status -> { // <4> + return productRepository.save(new Product(name, manufacturer)); + }) + } + + /** + * Finds product by name using transaction manager and product repository. + * + * @param name the product name + * @return found product or null if none product found matching by name + */ + Product findUsingRepo(String name) { + return transactionManager.executeRead(status -> { // <5> + return productRepository.findByName(name).orElse(null); + }) + } } diff --git a/doc-examples/jdbc-example-groovy/src/main/groovy/example/ProductRepository.groovy b/doc-examples/jdbc-example-groovy/src/main/groovy/example/ProductRepository.groovy index c814aa279c..c15c136bc1 100644 --- a/doc-examples/jdbc-example-groovy/src/main/groovy/example/ProductRepository.groovy +++ b/doc-examples/jdbc-example-groovy/src/main/groovy/example/ProductRepository.groovy @@ -8,7 +8,6 @@ import io.micronaut.data.repository.CrudRepository; import io.reactivex.Maybe; import io.reactivex.Single; -import java.util.List; import java.util.concurrent.CompletableFuture; // tag::join[] @@ -35,10 +34,13 @@ public interface ProductRepository extends CrudRepository { Single countDistinctByManufacturerName(String name); // end::reactive[] + @Join("manufacturer") + Optional findByName(String name) + // tag::native[] - @Query("""SELECT *, m_.name as m_name, m_.id as m_id - FROM product p - INNER JOIN manufacturer m_ ON p.manufacturer_id = m_.id + @Query("""SELECT *, m_.name as m_name, m_.id as m_id + FROM product p + INNER JOIN manufacturer m_ ON p.manufacturer_id = m_.id WHERE p.name like :name limit 5""") @Join(value = "manufacturer", alias = "m_") List searchProducts(String name); diff --git a/doc-examples/jdbc-example-groovy/src/test/groovy/example/ProductManagerSpec.groovy b/doc-examples/jdbc-example-groovy/src/test/groovy/example/ProductManagerSpec.groovy index 04a6b54171..5da22c32ee 100644 --- a/doc-examples/jdbc-example-groovy/src/test/groovy/example/ProductManagerSpec.groovy +++ b/doc-examples/jdbc-example-groovy/src/test/groovy/example/ProductManagerSpec.groovy @@ -37,4 +37,22 @@ class ProductManagerSpec extends Specification { vr.name == "VR" } + void "test product manager using repo"() { + given: + Manufacturer intel = manufacturerRepository.save("Intel") + + when: + productManager.save("Processor", intel) + + then: + def product = productManager.findUsingRepo("Processor") + product.name == "Processor" + + when: + product = productManager.findUsingRepo("NonExistingProduct") + + then: + !product + } + } diff --git a/doc-examples/jdbc-example-java/src/main/java/example/ProductManager.java b/doc-examples/jdbc-example-java/src/main/java/example/ProductManager.java index 4e894ba83b..fd1facb85d 100644 --- a/doc-examples/jdbc-example-java/src/main/java/example/ProductManager.java +++ b/doc-examples/jdbc-example-java/src/main/java/example/ProductManager.java @@ -12,11 +12,14 @@ public class ProductManager { private final Connection connection; private final TransactionOperations transactionManager; + private final ProductRepository productRepository; public ProductManager(Connection connection, - TransactionOperations transactionManager) { // <1> + TransactionOperations transactionManager, // <1> + ProductRepository productRepository) { this.connection = connection; this.transactionManager = transactionManager; + this.productRepository = productRepository; } Product save(String name, Manufacturer manufacturer) { @@ -44,4 +47,29 @@ Product find(String name) { } }); } + + /** + * Creates new product using transaction operations and product repository. + * + * @param name the product name + * @param manufacturer the manufacturer + * @return the created product instance + */ + Product saveUsingRepo(String name, Manufacturer manufacturer) { + return transactionManager.executeWrite(status -> { // <4> + return productRepository.save(new Product(name, manufacturer)); + }); + } + + /** + * Finds product by name using transaction manager and product repository. + * + * @param name the product name + * @return found product or null if none product found matching by name + */ + Product findUsingRepo(String name) { + return transactionManager.executeRead(status -> { // <5> + return productRepository.findByName(name).orElse(null); + }); + } } diff --git a/doc-examples/jdbc-example-java/src/main/java/example/ProductRepository.java b/doc-examples/jdbc-example-java/src/main/java/example/ProductRepository.java index 428bf0b701..9f6bbf7441 100644 --- a/doc-examples/jdbc-example-java/src/main/java/example/ProductRepository.java +++ b/doc-examples/jdbc-example-java/src/main/java/example/ProductRepository.java @@ -10,6 +10,7 @@ import io.reactivex.Single; import java.util.List; +import java.util.Optional; import java.util.concurrent.CompletableFuture; // tag::join[] @@ -48,6 +49,9 @@ public interface ProductRepository extends CrudRepository { List searchProducts(String name); // end::native[] + @Join("manufacturer") + Optional findByName(String name); + class Specifications { // tag::typesafe[] diff --git a/doc-examples/jdbc-example-java/src/test/java/example/ProductManagerSpec.java b/doc-examples/jdbc-example-java/src/test/java/example/ProductManagerSpec.java index 5cae08267c..9b6af1269a 100644 --- a/doc-examples/jdbc-example-java/src/test/java/example/ProductManagerSpec.java +++ b/doc-examples/jdbc-example-java/src/test/java/example/ProductManagerSpec.java @@ -7,6 +7,7 @@ import org.junit.jupiter.api.TestInstance; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; @MicronautTest(transactional = false) @TestInstance(TestInstance.Lifecycle.PER_CLASS) @@ -36,4 +37,16 @@ void testProductManager() { assertEquals("VR", product.getName()); } + @Test + void testProductManagerUsingRepo() { + Manufacturer intel = manufacturerRepository.save("Intel"); + productManager.saveUsingRepo("Processor", intel); + + Product product = productManager.findUsingRepo("Processor"); + assertEquals("Processor", product.getName()); + + product = productManager.findUsingRepo("NonExistingProduct"); + assertNull(product); + } + } diff --git a/doc-examples/jdbc-example-kotlin/src/main/kotlin/example/ProductManager.kt b/doc-examples/jdbc-example-kotlin/src/main/kotlin/example/ProductManager.kt index 1b8a2936f8..fc40470f8d 100644 --- a/doc-examples/jdbc-example-kotlin/src/main/kotlin/example/ProductManager.kt +++ b/doc-examples/jdbc-example-kotlin/src/main/kotlin/example/ProductManager.kt @@ -8,7 +8,8 @@ import java.sql.Connection @Singleton class ProductManager( private val connection: Connection, - private val transactionManager: TransactionOperations // <1> + private val transactionManager: TransactionOperations, // <1> + private val productRepository: ProductRepository ) { fun save(name: String, manufacturer: Manufacturer): Product { @@ -38,4 +39,16 @@ class ProductManager( } } } + + fun saveUsingRepo(name: String, manufacturer: Manufacturer): Product { + return transactionManager.executeWrite { // <4> + productRepository.save(Product(0, name, manufacturer)) + } + } + + fun findUsingRepo(name: String): Product? { + return transactionManager.executeRead { status -> // <5> + productRepository.findByName(name).orElse(null) + } + } } diff --git a/doc-examples/jdbc-example-kotlin/src/main/kotlin/example/ProductRepository.kt b/doc-examples/jdbc-example-kotlin/src/main/kotlin/example/ProductRepository.kt index 3df67db139..f7f5e1dc42 100644 --- a/doc-examples/jdbc-example-kotlin/src/main/kotlin/example/ProductRepository.kt +++ b/doc-examples/jdbc-example-kotlin/src/main/kotlin/example/ProductRepository.kt @@ -7,6 +7,7 @@ import io.micronaut.data.model.query.builder.sql.Dialect import io.micronaut.data.repository.CrudRepository import io.reactivex.Maybe import io.reactivex.Single +import java.util.Optional import java.util.concurrent.CompletableFuture // tag::join[] // tag::async[] @@ -35,13 +36,17 @@ interface ProductRepository : CrudRepository { // end::reactive[] // tag::native[] - @Query("""SELECT *, m_.name as m_name, m_.id as m_id - FROM product p - INNER JOIN manufacturer m_ ON p.manufacturer_id = m_.id + @Query("""SELECT *, m_.name as m_name, m_.id as m_id + FROM product p + INNER JOIN manufacturer m_ ON p.manufacturer_id = m_.id WHERE p.name like :name limit 5""") @Join(value = "manufacturer", alias = "m_") fun searchProducts(name: String): List // end::native[] + + @Join("manufacturer") + fun findByName(str: String): Optional + // tag::join[] // tag::async[] } diff --git a/doc-examples/jdbc-example-kotlin/src/test/kotlin/example/ProductManagerSpec.kt b/doc-examples/jdbc-example-kotlin/src/test/kotlin/example/ProductManagerSpec.kt index 87482a8990..e07515b7e3 100644 --- a/doc-examples/jdbc-example-kotlin/src/test/kotlin/example/ProductManagerSpec.kt +++ b/doc-examples/jdbc-example-kotlin/src/test/kotlin/example/ProductManagerSpec.kt @@ -32,4 +32,14 @@ internal class ProductManagerSpec { val (_, name) = productManager.find("VR") Assertions.assertEquals("VR", name) } + + @Test + fun testProductManagerUsingRepo() { + val intel = manufacturerRepository.save("Intel") + productManager.save("Processor", intel) + var product = productManager.findUsingRepo("Processor") + Assertions.assertEquals("Processor", product?.name) + product = productManager.findUsingRepo("NonExistingProduct") + Assertions.assertNull(product) + } } diff --git a/src/main/docs/guide/shared/transactions/programmaticTransactions.adoc b/src/main/docs/guide/shared/transactions/programmaticTransactions.adoc index eef3e7bdc1..a89606db5a 100644 --- a/src/main/docs/guide/shared/transactions/programmaticTransactions.adoc +++ b/src/main/docs/guide/shared/transactions/programmaticTransactions.adoc @@ -14,9 +14,11 @@ The following presents an example: snippet::example.ProductManager[project-base="doc-examples/jdbc-example", source="main", indent="0"] -<1> The constructor is injected with the api:transaction.TransactionOperations[] and a contextual-connection-aware `Connection` +<1> The constructor is injected with the api:transaction.TransactionOperations[] and a contextual-connection-aware `Connection`. Additional parameter is productRepository to demonstrate that Micronaut Data JDBC repository can also work with programmatic transactions. <2> The `save` method uses the `executeWrite` method to execute a write transaction within the context of the passed lambda. <3> The `find` method uses the `executeRead` method to execute a read-only transaction within the context of the passed lambda. This example is accessing the connection using the status provided by the transaction manager. +<4> The `saveUsingRepo` method uses `executeWrite` method to execute a write transaction within the context of the passed lambda and saves data using Micronaut Data JDBC repository. Please note that Micronaut Data JPA Repository can be used the same way. +<5> The `findUsingRepo` uses the `executeRead` method to execute a read-only transaction within the context of the passed lambda and finds data using Micronaut Data JDBC repository. Note that it is important that you always use the injected connection as Micronaut Data makes available a transaction-aware implementation that uses the connection associated with the underlying transaction.