Skip to content

Commit

Permalink
[#1] Added reactive examples
Browse files Browse the repository at this point in the history
  • Loading branch information
lukaseder committed May 31, 2022
1 parent 5571c3a commit 6d35f57
Show file tree
Hide file tree
Showing 22 changed files with 995 additions and 27 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package org.jooq.demo.java;

import org.jooq.demo.AbstractDemo;
import org.junit.After;
import org.junit.Test;
import reactor.core.publisher.Flux;

import java.sql.SQLException;
import java.util.List;

import static org.jooq.Records.mapping;
import static org.jooq.demo.java.db.Tables.ACTOR;

public class Demo13Reactive extends AbstractDemo {

@Test
public void reactiveQuerying() {
record Actor(String firstName, String lastName) {}

Flux.from(ctx
.select(ACTOR.FIRST_NAME, ACTOR.LAST_NAME)
.from(ACTOR)
.orderBy(ACTOR.ACTOR_ID)
.limit(5))
.map(mapping(Actor::new))
.collectList()
.block()
.forEach(System.out::println);
}

@Test
public void reactiveTransactions() {
Flux.from(ctx

// Just like synchronous, JDBC based transactions, reactive transactions commit by default, and rollback
// on error. Nested transactions using SAVEPOINT are supported by default. See this blog for details:
// https://blog.jooq.org/nested-transactions-in-jooq/
.transactionPublisher(c -> Flux
.from(c.dsl()
.insertInto(ACTOR)
.columns(ACTOR.ACTOR_ID, ACTOR.FIRST_NAME, ACTOR.LAST_NAME)
.values(201L, "A", "A"))

// Within the transactional scope, the above record is visible, and we can log it
.thenMany(c.dsl()
.selectFrom(ACTOR)
.where(ACTOR.ACTOR_ID.eq(201L)))
.log()

// This should produces a constraint violation exception, rolling back the transaction
.thenMany(c.dsl()
.insertInto(ACTOR)
.columns(ACTOR.ACTOR_ID, ACTOR.FIRST_NAME, ACTOR.LAST_NAME)
.values(201L, "A", "A"))))

// Outside of the scope, we have committed or rollbacked the transaction, so on error, we can see the
// Rollback reason:
.collectList()
.doOnError(Throwable::printStackTrace)
.onErrorReturn(List.of())

// This record is visible only if the transaction has been committed:
.thenMany(ctx.select(ACTOR.ACTOR_ID).from(ACTOR).where(ACTOR.ACTOR_ID.eq(201L)))
.collectList()
.block()
.forEach(System.out::println);
}

@After
public void teardown() throws SQLException {
cleanup(ACTOR, ACTOR.ACTOR_ID);
super.teardown();
}
}
10 changes: 8 additions & 2 deletions jOOQ-demo-oss/jOOQ-demo-kotlin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,14 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-core</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-reactor</artifactId>
<version>1.6.2</version>
</dependency>
</dependencies>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package org.jooq.demo.kotlin

import kotlinx.coroutines.reactive.awaitFirstOrNull
import kotlinx.coroutines.runBlocking
import org.jooq.Records.mapping
import org.jooq.demo.AbstractDemo
import org.jooq.demo.kotlin.db.tables.records.ActorRecord
import org.jooq.demo.kotlin.db.tables.references.ACTOR
import org.junit.After
import org.junit.Test
import reactor.core.publisher.Flux


class Demo13Reactive : AbstractDemo() {

@Test
fun reactiveQuerying() {
data class Actor(val firstName: String?, val lastName: String?)

Flux.from(ctx
.select(ACTOR.FIRST_NAME, ACTOR.LAST_NAME)
.from(ACTOR)
.orderBy(ACTOR.ACTOR_ID)
.limit(5))
.map(mapping(::Actor))
.toIterable()
.forEach { println(it) }
}

@Test
fun reactiveTransactions() {
Flux.from(ctx

// Just like synchronous, JDBC based transactions, reactive transactions commit by default, and rollback
// on error. Nested transactions using SAVEPOINT are supported by default. See this blog for details:
// https://blog.jooq.org/nested-transactions-in-jooq/
.transactionPublisher { c -> Flux
.from(c.dsl()
.insertInto(ACTOR)
.columns(ACTOR.ACTOR_ID, ACTOR.FIRST_NAME, ACTOR.LAST_NAME)
.values(201L, "A", "A"))

// Within the transactional scope, the above record is visible, and we can log it
.thenMany(c.dsl()
.selectFrom(ACTOR)
.where(ACTOR.ACTOR_ID.eq(201L)))
.log()

// This should produces a constraint violation exception, rolling back the transaction
.thenMany(c.dsl()
.insertInto(ACTOR)
.columns(ACTOR.ACTOR_ID, ACTOR.FIRST_NAME, ACTOR.LAST_NAME)
.values(201L, "A", "A"))
})

// Outside of the scope, we have committed or rollbacked the transaction, so on error, we can see the
// Rollback reason:
.collectList()
.doOnError(Throwable::printStackTrace)
.onErrorReturn(listOf())

// This record is visible only if the transaction has been committed:
.thenMany(ctx
.select(ACTOR.ACTOR_ID)
.from(ACTOR)
.where(ACTOR.ACTOR_ID.eq(201L)))
.toIterable()
.forEach { println(it) }
}

@Test
fun coroutines() {
val actor: ActorRecord? = runBlocking {
findActor(1)
}

println(actor)
}

suspend fun findActor(id: Long): ActorRecord? {
return ctx
.selectFrom(ACTOR)
.where(ACTOR.ACTOR_ID.eq(id))
.awaitFirstOrNull()
}

@After
override fun teardown() {
cleanup(ACTOR, ACTOR.ACTOR_ID)
super.teardown()
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package org.jooq.demo.skala

import org.jooq.Records.{intoMap, mapping}
import org.jooq.Records.intoMap
import org.jooq.demo.AbstractDemo
import org.jooq.demo.AbstractDemo._
import org.jooq.demo.skala.db.Tables._
import org.jooq.impl.DSL._
import org.jooq.impl.SQLDataType.LOCALDATE
import org.jooq.scalaextensions.Conversions._
import org.jooq.{JSONFormat, Record1, Record2, Record3, Result, XMLFormat}
import org.jooq.{Field, JSONFormat, Record1, Record2, Record3, Result, XMLFormat}
import org.junit.Test

import java.math.BigDecimal
Expand Down Expand Up @@ -190,17 +190,17 @@ class Demo01Querying extends AbstractDemo {
case class Customer(firstName: String, lastName: String, country: Country)

title("Nesting is particularly useful when using ad-hoc converters")
val r: Result[Record3[String, String, Country]] = ctx
val r = ctx
.select(
CUSTOMER.FIRST_NAME,
CUSTOMER.LAST_NAME,
CUSTOMER.address.city.country.COUNTRY_.convertFrom(Country(_)))
.from(CUSTOMER)
.orderBy(1, 2)
.limit(5)
.fetch
.fetch(Customer)

r.map(_ => Customer(_, _, _)).forEach(println(_))
r.forEach(println(_))
}

@Test
Expand Down Expand Up @@ -266,15 +266,15 @@ class Demo01Querying extends AbstractDemo {
multiset(
select(row(FILM_ACTOR.actor.FIRST_NAME, FILM_ACTOR.actor.LAST_NAME).mapping(Name(_, _)))
.from(FILM_ACTOR)
.where(FILM_ACTOR.FILM_ID.eq(FILM.FILM_ID))).convertFrom(r => r.map(mapping[Name, Record1[Name], Actor](Actor(_)))),
.where(FILM_ACTOR.FILM_ID.eq(FILM.FILM_ID))).mapping(Actor),
multiset(
select(FILM_CATEGORY.category.NAME)
.from(FILM_CATEGORY)
.where(FILM_CATEGORY.FILM_ID.eq(FILM.FILM_ID))).convertFrom(r => r.map(mapping[String, Record1[String], Category](Category(_)))))
.where(FILM_CATEGORY.FILM_ID.eq(FILM.FILM_ID))).mapping(Category))
.from(FILM)
.orderBy(FILM.TITLE)
.limit(5)
.fetch(mapping[String, util.List[Actor], util.List[Category], Record3[String, util.List[Actor], util.List[Category]], Film](Film(_, _, _)))
.fetch(Film)

result.forEach { film =>
println("Film %s with categories %s and actors %s ".formatted(film.title, film.categories, film.actors))
Expand All @@ -287,24 +287,19 @@ class Demo01Querying extends AbstractDemo {
title("Arbitrary nested data structures are possible")
case class Film(title: String, revenue: util.Map[LocalDate, BigDecimal])

// TODO: The amount of type witnesses required in the Scala version of this method are unwieldy
// I currently do not know if this is a limitation of the Scala / Java interop or me misunderstanding which
// implicit conversions should be used here... Please feel free to suggest improvements!
val result: Result[Record2[String, util.Map[LocalDate, BigDecimal]]] = ctx
val result = ctx
.select(
FILM.TITLE,
multiset(
select(PAYMENT.PAYMENT_DATE.cast(LOCALDATE), sum(PAYMENT.AMOUNT))
.from(PAYMENT)
.groupBy(PAYMENT.PAYMENT_DATE.cast(LOCALDATE))
.orderBy(PAYMENT.PAYMENT_DATE.cast(LOCALDATE))).convertFrom(r => r.collect(intoMap[LocalDate, BigDecimal, Record2[LocalDate, BigDecimal]]())))
.orderBy(PAYMENT.PAYMENT_DATE.cast(LOCALDATE))).intoMap())
.from(FILM)
.orderBy(FILM.TITLE)
.fetch
.fetch(Film)

result
.map(mapping[String, util.Map[LocalDate, BigDecimal], Record2[String, util.Map[LocalDate, BigDecimal]], Film](Film(_, _)))
.forEach { film: Film =>
result.forEach { film: Film =>
println("")
println("Film %s with revenue: ".formatted(film.title))
film.revenue.forEach((d: LocalDate, r: BigDecimal) => println(" %s: %s".formatted(d, r)))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.jooq.demo.skala

import org.jooq.demo.AbstractDemo
import org.jooq.demo.AbstractDemo._
import org.jooq.demo.skala.db.Tables.ACTOR
import org.jooq.demo.skala.db.tables.daos.ActorDao
import org.jooq.demo.skala.db.tables.pojos.Actor
import org.junit.{After, Test}


class Demo09DAOs extends AbstractDemo {

@Test
def pojos(): Unit = {
title("jOOQ's code generator produces a POJO for every table")
val actors = ctx
.selectFrom(ACTOR)
.where(ACTOR.ACTOR_ID.lt(4L))
.fetchInto(classOf[Actor])
actors.forEach(println(_))
}

@Test
def daos(): Unit = {
title("Daos further simplify CRUD when working with jOOQ")
val dao = new ActorDao(ctx.configuration)

dao.insert(
Actor(201L, "John", "Doe", null),
Actor(202L, "Jane", "Smith", null))
dao.fetchByActorId(201L, 202L).forEach(println(_))
}

@After
override def teardown(): Unit = {
cleanup(ACTOR, ACTOR.ACTOR_ID)
super.teardown()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package org.jooq.demo.skala

import org.jooq.{ExecuteListener, VisitListener}
import org.jooq.demo.AbstractDemo
import org.jooq.demo.AbstractDemo._
import org.jooq.demo.skala.db.Tables.ACTOR
import org.jooq.impl.DSL.using
import org.jooq.scalaextensions.Conversions._
import org.junit.Test


class Demo10SPIs extends AbstractDemo {

@Test
def executeListener(): Unit = {
title("The ExecuteListener SPI allows for intercepting the execution lifecycle")
val c = ctx.configuration
.derive(ExecuteListener
.onRenderEnd(e => e.sql("/* some telemetry comment */ " + e.sql))
.onExecuteStart(e => println("Executing: " + e.sql))
.onRecordEnd(e => println("Fetched record: " + e.record.formatJSON)))
.dsl

c.select(ACTOR.FIRST_NAME, ACTOR.LAST_NAME)
.from(ACTOR)
.where(ACTOR.ACTOR_ID < 4L)
.fetch
}

@Test def visitListener(): Unit = {
title("The VisitListener SPI allows for intercepting the SQL rendering process")
val c = ctx.configuration
.derive(VisitListener.onVisitStart(vc => println("Visiting: " + using(ctx.family).render(vc.queryPart))))
.dsl

c.select(ACTOR.FIRST_NAME, ACTOR.LAST_NAME)
.from(ACTOR)
.where(ACTOR.ACTOR_ID < 4L)
.fetch
}
// There are many more SPIs, check out Configuration::derive methods!
}
Loading

0 comments on commit 6d35f57

Please sign in to comment.