Skip to content

Commit

Permalink
Additional example docs for programmatic transactions and repositories (
Browse files Browse the repository at this point in the history
  • Loading branch information
radovanradic authored May 22, 2024
1 parent 696cc25 commit 28b4804
Show file tree
Hide file tree
Showing 10 changed files with 134 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@ class ProductManager {

private final Connection connection
private final TransactionOperations<Connection> transactionManager
private final ProductRepository productRepository

ProductManager(Connection connection,
TransactionOperations<Connection> transactionManager) { // <1>
TransactionOperations<Connection> transactionManager, // <1>
ProductRepository productRepository) {
this.connection = connection
this.transactionManager = transactionManager
this.productRepository = productRepository
}

Product save(String name, Manufacturer manufacturer) {
Expand Down Expand Up @@ -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);
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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[]
Expand All @@ -35,10 +34,13 @@ public interface ProductRepository extends CrudRepository<Product, Long> {
Single<Long> countDistinctByManufacturerName(String name);
// end::reactive[]

@Join("manufacturer")
Optional<Product> 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<Product> searchProducts(String name);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@ public class ProductManager {

private final Connection connection;
private final TransactionOperations<Connection> transactionManager;
private final ProductRepository productRepository;

public ProductManager(Connection connection,
TransactionOperations<Connection> transactionManager) { // <1>
TransactionOperations<Connection> transactionManager, // <1>
ProductRepository productRepository) {
this.connection = connection;
this.transactionManager = transactionManager;
this.productRepository = productRepository;
}

Product save(String name, Manufacturer manufacturer) {
Expand Down Expand Up @@ -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);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import io.reactivex.Single;

import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;

// tag::join[]
Expand Down Expand Up @@ -48,6 +49,9 @@ public interface ProductRepository extends CrudRepository<Product, Long> {
List<Product> searchProducts(String name);
// end::native[]

@Join("manufacturer")
Optional<Product> findByName(String name);

class Specifications {
// tag::typesafe[]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import java.sql.Connection
@Singleton
class ProductManager(
private val connection: Connection,
private val transactionManager: TransactionOperations<Connection> // <1>
private val transactionManager: TransactionOperations<Connection>, // <1>
private val productRepository: ProductRepository
) {

fun save(name: String, manufacturer: Manufacturer): Product {
Expand Down Expand Up @@ -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)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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[]
Expand Down Expand Up @@ -35,13 +36,17 @@ interface ProductRepository : CrudRepository<Product, Long> {
// 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<Product>
// end::native[]

@Join("manufacturer")
fun findByName(str: String): Optional<Product>

// tag::join[]
// tag::async[]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down

0 comments on commit 28b4804

Please sign in to comment.