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.
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:
-
Some time on your hands
-
A decent text editor or IDE (e.g. IntelliJ IDEA)
-
JDK 21 or greater installed with
JAVA_HOMEconfigured appropriately -
Docker installed to run Oracle Database with Micronaut Test Resources.
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.
-
Download and unzip the source
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=maven \
--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
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc11</artifactId>
<scope>runtime</scope>
</dependency>Database Configuration
And the database configuration:
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:
-
PRIORITY_TXNS_HIGH_WAIT_TARGETsets how many seconds aHIGHtransaction waits before Oracle rolls back a lower-priority blocker. -
PRIORITY_TXNS_MEDIUM_WAIT_TARGETdoes the same forMEDIUMtransactions. -
PRIORITY_TXNS_MODEset toROLLBACKenables the rollback. The other modes only track or report the waits.
See Oracle’s Managing Transactions documentation for details.
For this guide, Micronaut Test Resources copies a startup script into the Oracle Database Free container:
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:
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:
imports
package example.micronautenum Status {
AVAILABLE,
RECONCILED,
CHECKED_OUT
}Repository
Create a repository that locks the item while a transaction works with it:
Transactions with Different Priorities
Create a service with a LOW-priority reconciliation and a HIGH-priority checkout:
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:
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:
Test
Add a test that runs a reconciliation and a checkout against the same item:
Testing the Application
To run the tests:
./mvnw testWhen 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/resetIn 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/reconcileWithin those 20 seconds, check out the item from a second terminal:
curl -X POST http://localhost:8080/inventory/checkoutAfter 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
Read more about transactions in Micronaut Data and Oracle priority transactions.
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). |