Groovy / Gradle

Prioritize Oracle Transactions with Micronaut Data JDBC

Build an inventory example where an Oracle HIGH-priority checkout can roll back conflicting LOW-priority background work.

Radovan Radic
On this guide
In this section

Getting Started

In this guide, we will create a Micronaut application written in Groovy.

In this guide, you will build a small inventory application with Micronaut Data JDBC and Oracle Database. A slow stock reconciliation is background work, so it runs with LOW priority. A customer checkout needs the same last item and runs with HIGH priority. When Oracle priority transactions are enabled, Oracle rolls back the reconciliation once the checkout has waited long enough, so the customer does not wait for background work.

You will use @OracleTransactional to declare the priority of each transaction, and handle OracleTransactionPriorityException, which Micronaut Data throws when Oracle rolls back a lower-priority transaction.

What you will need

To complete this guide, you will need the following:

Solution

We recommend that you follow the instructions in the next sections and create the application step by step. However, you can go right to the completed example.

Writing the Application

Create an application using the Micronaut Command Line Interface or with Micronaut Launch.

mn create-app example.micronaut.micronautguide \
    --features=data-jdbc,oracle,serialization-jackson,validation \
    --build=gradle \
    --lang=groovy \
    --test=spock
Note
If you don’t specify the --build argument, Gradle with the Kotlin DSL is used as the build tool.
If you don’t specify the --lang argument, Java is used as the language.
If you don’t specify the --test argument, JUnit is used for Java and Kotlin, and Spock is used for Groovy.

The previous command creates a Micronaut application with the default package example.micronaut in a directory named micronautguide.

If you use Micronaut Launch, select Micronaut Application as application type and add data-jdbc, oracle, serialization-jackson, and validation features.

Note
If you have an existing Micronaut application and want to add the functionality described here, you can view the dependency and configuration changes from the specified features, and apply those changes to your application.

Oracle Driver

Add also the Oracle Driver

build.gradle
runtimeOnly("com.oracle.database.jdbc:ojdbc11")

Database Configuration

And the database configuration:

groovy/src/main/resources/application.properties

Configure Oracle Priority Transactions

Transaction priority is an Oracle Database feature. A database administrator decides how long a higher-priority transaction waits for a lower-priority one that holds a row lock, and whether Oracle then rolls back the blocker:

See Oracle’s Managing Transactions documentation for details.

For this guide, Micronaut Test Resources copies a startup script into the Oracle Database Free container:

groovy/src/main/resources/application.properties
src/main/resources/oracle/priority-txns.sql

In other environments, a database administrator applies the same ALTER SYSTEM statements.

Tip
If you change the script while the Test Resources service is running, stop it with ./gradlew stopTestResourcesService so the next run starts a new container.

Inventory Item

Create an entity for the inventory item:

groovy/src/main/groovy/example/micronaut/InventoryItem.groovy
imports
package example.micronaut

import groovy.transform.CompileStatic
import io.micronaut.data.annotation.Id
import io.micronaut.data.annotation.MappedEntity
import io.micronaut.serde.annotation.Serdeable
@CompileStatic
@Serdeable
@MappedEntity('inventory_item')
class InventoryItem {

    @Id
    Long id

    String name

    int availableQuantity

    Status status

    InventoryItem() {
    }

    InventoryItem(Long id, String name, int availableQuantity, Status status) {
        this.id = id
        this.name = name
        this.availableQuantity = availableQuantity
        this.status = status
    }

    InventoryItem withStatus(Status status) {
        new InventoryItem(id, name, availableQuantity, status)
    }
}

Create the item status:

groovy/src/main/groovy/example/micronaut/Status.groovy
imports
package example.micronaut
enum Status {
    AVAILABLE,
    RECONCILED,
    CHECKED_OUT
}

Repository

Create a repository that locks the item while a transaction works with it:

groovy/src/main/groovy/example/micronaut/InventoryItemRepository.groovy

Transactions with Different Priorities

Create a service with a LOW-priority reconciliation and a HIGH-priority checkout:

groovy/src/main/groovy/example/micronaut/InventoryService.groovy

A priority rollback is a failed transaction; it is not retried automatically. The reconciliation read the item before checkout changed it, so replaying the same work could overwrite the checkout. To retry, run the operation in a new transaction that reads the item again and decides whether the work is still needed.

Controller

Expose the operations as HTTP endpoints:

groovy/src/main/groovy/example/micronaut/InventoryController.groovy

Handle a Priority Rollback

Without a handler, an OracleTransactionPriorityException results in a 500 Internal Server Error response. A priority rollback is an expected outcome of contention, so report it as a conflict instead:

groovy/src/main/groovy/example/micronaut/TransactionPriorityExceptionHandler.groovy

Test

Add a test that runs a reconciliation and a checkout against the same item:

groovy/src/test/groovy/example/micronaut/InventoryControllerSpec.groovy

Testing the Application

To run the tests:

./gradlew test

Then open build/reports/tests/test/index.html in a browser to see the results.

When you run the tests, Micronaut Test Resources starts an Oracle Database Free container and runs priority-txns.sql before the application connects.

Run the Application

Start the application with ./gradlew run or ./mvnw mn:run, and reset the item:

curl -X POST http://localhost:8080/inventory/reset

In one terminal, start a reconciliation. The request stays open for 20 seconds while it holds the item lock:

curl -i -X POST http://localhost:8080/inventory/reconcile

Within those 20 seconds, check out the item from a second terminal:

curl -X POST http://localhost:8080/inventory/checkout

After about five seconds, the checkout responds with the item in the CHECKED_OUT status, and the reconciliation responds with 409 Conflict.

If you start the checkout without a running reconciliation, it completes immediately. If you run only the reconciliation, it commits after the count and responds with the RECONCILED status.

Micronaut Test Resources Goals

  • zero-configuration: without adding any configuration, test resources should be spawned and the application configured to use them. Configuration is only required for advanced use cases.

  • classpath isolation: use of test resources shouldn’t leak into your application classpath, nor your test classpath

  • compatible with GraalVM native: if you build a native binary, or run tests in native mode, test resources should be available

  • easy to use: the Micronaut build plugins for Gradle and Maven should handle the complexity of figuring out the dependencies for you

  • extensible: you can implement your own test resources, in case the built-in ones do not cover your use case

  • technology agnostic: while lots of test resources use Testcontainers under the hood, you can use any other technology to create resources

Next Steps

License

Note
All guides are released with an Apache License 2.0 for the code and a Creative Commons Attribution 4.0 license for the writing and media (images).