Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright 2017-2026 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.micronaut

import groovy.transform.CompileStatic
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.http.annotation.Post
import io.micronaut.http.annotation.QueryValue
import io.micronaut.scheduling.TaskExecutors
import io.micronaut.scheduling.annotation.ExecuteOn
import jakarta.validation.constraints.Max
import jakarta.validation.constraints.Min

import java.time.Duration

@CompileStatic
@ExecuteOn(TaskExecutors.BLOCKING) // <1>
@Controller('/inventory')
class InventoryController {

private final InventoryService inventoryService

InventoryController(InventoryService inventoryService) {
this.inventoryService = inventoryService
}

@Get
InventoryItem item() {
inventoryService.find()
}

@Post('/reset')
InventoryItem reset() {
inventoryService.reset()
}

@Post('/reconcile')
InventoryItem reconcile(@QueryValue(defaultValue = '20') @Min(1L) @Max(120L) int countSeconds) { // <2>
inventoryService.reconcile(Duration.ofSeconds(countSeconds))
}

@Post('/checkout')
InventoryItem checkout() {
inventoryService.checkout()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright 2017-2026 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2017-2026 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.micronaut

import io.micronaut.data.jdbc.annotation.JdbcRepository
import io.micronaut.data.model.query.builder.sql.Dialect
import io.micronaut.data.repository.CrudRepository

@JdbcRepository(dialect = Dialect.ORACLE) // <1>
interface InventoryItemRepository extends CrudRepository<InventoryItem, Long> {

Optional<InventoryItem> findByIdForUpdate(Long id) // <2>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright 2017-2026 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.micronaut

import groovy.transform.CompileStatic
import io.micronaut.transaction.annotation.OracleTransactional
import io.micronaut.transaction.annotation.Transactional
import jakarta.inject.Singleton

import java.time.Duration

@CompileStatic
@Singleton
class InventoryService {

static final long DEMO_ITEM_ID = 1L

private final InventoryItemRepository inventoryItemRepository

InventoryService(InventoryItemRepository inventoryItemRepository) {
this.inventoryItemRepository = inventoryItemRepository
}

@Transactional
InventoryItem reset() {
InventoryItem item = new InventoryItem(DEMO_ITEM_ID, 'Last available item', 1, Status.AVAILABLE)
inventoryItemRepository.existsById(DEMO_ITEM_ID)
? inventoryItemRepository.update(item)
: inventoryItemRepository.save(item)
}

@Transactional(readOnly = true)
InventoryItem find() {
inventoryItemRepository.findById(DEMO_ITEM_ID).orElseThrow()
}

@OracleTransactional(priority = OracleTransactional.Priority.LOW) // <1>
InventoryItem reconcile(Duration countDuration) {
InventoryItem item = inventoryItemRepository.findByIdForUpdate(DEMO_ITEM_ID).orElseThrow() // <2>
countStock(countDuration) // <3>
inventoryItemRepository.update(item.withStatus(Status.RECONCILED)) // <4>
}

@OracleTransactional(priority = OracleTransactional.Priority.HIGH) // <5>
InventoryItem checkout() {
InventoryItem item = inventoryItemRepository.findByIdForUpdate(DEMO_ITEM_ID).orElseThrow() // <6>
inventoryItemRepository.update(new InventoryItem(item.id, item.name, 0, Status.CHECKED_OUT))
}

private static void countStock(Duration countDuration) {
try {
Thread.sleep(countDuration) // Simulates a slow count in an external system.
} catch (InterruptedException e) {
Thread.currentThread().interrupt()
throw new IllegalStateException('The stock count was interrupted', e)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright 2017-2026 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.micronaut

enum Status {
AVAILABLE,
RECONCILED,
CHECKED_OUT
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2017-2026 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.micronaut

import groovy.transform.CompileStatic
import io.micronaut.http.HttpRequest
import io.micronaut.http.HttpResponse
import io.micronaut.http.HttpStatus
import io.micronaut.http.annotation.Produces
import io.micronaut.http.server.exceptions.ExceptionHandler
import io.micronaut.http.server.exceptions.response.ErrorContext
import io.micronaut.http.server.exceptions.response.ErrorResponseProcessor
import io.micronaut.transaction.exceptions.OracleTransactionPriorityException
import jakarta.inject.Singleton

@CompileStatic
@Produces
@Singleton
class TransactionPriorityExceptionHandler
implements ExceptionHandler<OracleTransactionPriorityException, HttpResponse<?>> { // <1>

private final ErrorResponseProcessor<?> errorResponseProcessor

TransactionPriorityExceptionHandler(ErrorResponseProcessor<?> errorResponseProcessor) {
this.errorResponseProcessor = errorResponseProcessor
}

@Override
HttpResponse<?> handle(HttpRequest request, OracleTransactionPriorityException exception) {
ErrorContext errorContext = ErrorContext.builder(request)
.cause(exception)
.errorMessage('Oracle rolled back this operation in favor of a higher-priority transaction') // <2>
.build()
errorResponseProcessor.processResponse(errorContext, HttpResponse.status(HttpStatus.CONFLICT)) // <3>
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
micronaut.application.name=micronautguide

#tag::datasource[]
datasources.default.schema-generate=CREATE_DROP
# <1>
datasources.default.driver-class-name=oracle.jdbc.OracleDriver
# <2>
datasources.default.db-type=oracle
# <3>
datasources.default.dialect=ORACLE
#end::datasource[]

#tag::testresources[]
test-resources.containers.oracle.image-name=gvenzl/oracle-free
test-resources.containers.oracle.image-tag=latest
test-resources.containers.oracle.startup-timeout=600s
# <1>
test-resources.containers.oracle.copy-to-container[0].src/main/resources/oracle/priority-txns.sql=/container-entrypoint-startdb.d/priority-txns.sql
#end::testresources[]
Loading
Loading