Real-time, multi-model data processing (and many microservices workflows) often involves transactions across several systems: an event stream, relational history, location data, vector search, and more. When a failure occurs, it can be difficult to reason about: Which work committed? Which message was published? What now needs compensating?
In this code example, Oracle AI Database handles relational data, transactions, event streaming, spatial queries, vector search, and AI inference from one platform. To determine card fraud, the application queries signals together to create a score, and persists results without merging data from separate data stores.
You can download the sample from GitHub here: TxEventQ fraud-detection sample. If you like the sample, give it a star on GitHub!

The producer begins a transaction with OKafka (Oracle Kafka Java APIs), inserts the card charge, publishes the matching event, and commits only after both succeed. If the work fails before commit, it aborts the transaction rather than leaving the application to reconcile a persisted charge with a missing event.
The consumer then calculates an explicit fraud score from relational, spatial, and vector signals and stores the decision with its reason codes.
Note: this is a teaching sample, not a production fraud model. A real model should include training, monitoring, feedback, and other signals.
Real-time, multi-model data processing: transactional pub/sub events
Card transactions are often processed as a real-time event stream. Oracle supports high-throughput event streaming with Transactional Event Queues (TxEventQ). In this example, we’ll use the Oracle Kafka Java API for TxEventQ, known as OKafka, to consume and process those events.
OKafka provides a familiar Kafka-compatible Java API for producing and consuming events backed by Oracle AI Database. Our CardTransactionProducer inserts a card transaction into the database, captures the generated transaction ID, and then publishes the enriched event to a TxEventQ topic.
producer.beginTransaction();
try {
long id = insertCardCharge(producer.getDBConnection(), event);
event.setTransactionId(id);
producer.send(new ProducerRecord<>(topic, event));
producer.commitTransaction();
} catch (Exception exception) {
producer.abortTransaction();
throw new IllegalStateException("Unable to publish card charge event", exception);
}Once the producer begins a transaction, both the database operations and the event publication participate in the same atomic unit of work. Either they all succeed and are committed together, or they all fail and are rolled back together. Because the row insert and event publication commit together, the application never has to reconcile “the row exists but the event doesn’t” or vice versa.
The CardTransactionConsumer follows the same pattern. It reads events from the topic, scores them, and persists the assessment within a single database transaction.
private void processRecords(ConsumerRecords<String, CardChargeEvent> poll) {
Connection conn = consumer.getDBConnection();
try {
for (ConsumerRecord<String, CardChargeEvent> record : poll) {
if (record.value() == null) {
continue;
}
CardChargeEvent event = record.value();
FraudAssessment assessment = scoringService.score(conn, event);
System.out.printf("\nCARD TRANSACTION: %s \n-> %s (%.1f): %s%n", event.toSemanticString(),
assessment.decision(), assessment.totalScore(), assessment.reasonCodes());
if (SELECTAI_ENABLED) {
final String prompt = "give a transaction summary, including an explanation of fraudulent charges for this transaction: %s, Transaction ID %d";
String result = selectAI.call(conn,
prompt.formatted(event.toSemanticString(), event.getTransactionId()),
SelectAI.Action.NARRATE);
System.out.printf("Select AI summary for transaction:\n%s\n", result);
}
}
} catch (SQLException e) {
try {
conn.rollback();
} catch (SQLException ex) {
e.addSuppressed(ex);
}
throw new IllegalStateException("Unable to score card charge events", e);
}
}Unlike a traditional Kafka deployment there’s no outbox table, dual-write coordination, or separate messaging transaction to manage. Both the row insert and the event publication participate in the same Oracle AI Database transaction.
Multi-model fraud detection signals
The FraudScoringService calculates four scores on a 0–100 scale:
- Spatial score: The distance from the cardholder’s most recent approved transaction.
- Behavior score: The cosine vector distance to the closest behavioral profile for that cardholder.
- Amount score: The increase over the cardholder’s configured normal spending amount.
- Velocity score: The number of previous charges within the last 15 minutes.
These scores are combined into a weighted total:
double totalScore = spatialScore * .40 + behaviorScore * .30
+ amountScore * .20 + velocityScore * .10;
String decision = totalScore >= 70d ? "DECLINE"
: totalScore >= 40d ? "REVIEW" : "APPROVE";A score below 40 results in APPROVE, 40–69 in REVIEW, and 70 or above in DECLINE. The service persists each component score, the total score, the final decision, and human-readable reason codes such as DISTANT_RECENT_TRANSACTION and UNUSUAL_BEHAVIOR.
The nice part is that none of these queries leave the database. SQL, spatial, and vector similarity all run against the same schema. No need for specialized databases, just one Oracle AI Database.
AI Vectors complement card transaction behavior
Not every suspicious transaction happens far from home. A purchase can occur at a familiar location but still be unusual for a particular cardholder. The BehaviorVector class represents cardholder behavior as vector embeddings and compares new transactions against historical profiles using vector similarity search.
When a new transaction arrives, Oracle AI Database compares its embedding against the cardholder’s historical behavior profile using vector similarity search. Transactions that are close to a cardholder’s historical behavior contribute a lower behavior score. Transactions that are farther away contribute a higher one.
Vector similarity is just one input to the overall fraud assessment: Oracle AI Database evaluates vector, relational, and spatial data together on the same platform, with the behavior score contributing alongside the amount, velocity, and location scores. Each component is persisted independently, making it easy to understand why a transaction was approved, flagged for review, or declined.
Test it out locally
The integration test starts Oracle AI Database Free in Testcontainers, creates deterministic behavior profiles, and processes seven fixed events. Each event is persisted to the database and then evaluated for fraud.
The sample is easy to test locally because event processing, relational data, spatial queries, and AI Vector Search all run in the same Oracle AI Database Free container.
Run it from the repository root with Java 21, Maven, and a Docker-compatible container runtime:
mvn test -pl txeventq-fraud-detection/pom.xmlYou should see events processed like so, displaying the card transaction event, the fraud result (APPROVE, REVIEW, or DECLINE), and the signals used.
CARD TRANSACTION: USD 2 charge of 48.00 at Bay Fuel in the FUEL category via CARD_PRESENT using device bob-phone
-> APPROVE (0.0): NORMAL_PATTERN.
CARD TRANSACTION: USD 2 charge of 2000.00 at Digital Vault Exchange in the CRYPTO category via ECOMMERCE using device new-device
-> DECLINE (90.0): DISTANT_RECENT_TRANSACTION,UNUSUAL_BEHAVIOR,UNUSUAL_AMOUNTSelect AI for event enrichment
The sample includes an optional Select AI integration that generates a natural-language summary of each fraud assessment.
After the consumer calculates the fraud score, it invokes select ai narrate to produce a human-readable explanation of the transaction and its outcome. For example:
CARD TRANSACTION: USD 1 charge of 58.00 at Neighborhood Market in the GROCERY category via CARD_PRESENT using device alice-phone
-> APPROVE (0.0): NORMAL_PATTERN
Select AI summary for transaction:
The transaction is for 58.00 USD at Neighborhood Market in the GROCERY category using a card present method with a device named alice-phone. The transaction was approved and is considered a normal pattern, indicating it is not likely a fraudulent charge.
CARD TRANSACTION: USD 1 charge of 950.00 at Skyline Airways in the TRAVEL category via ECOMMERCE using device unknown-device
-> DECLINE (84.9): DISTANT_RECENT_TRANSACTION,UNUSUAL_BEHAVIOR,UNUSUAL_AMOUNT
Select AI summary for transaction:
The transaction is a 950.00 USD charge at Skyline Airways in the travel category via ecommerce using an unknown device. The transaction was declined due to suspicious activity, including a recent similar transaction from a distant location, unusual behavior, and an unusual amount, indicating potential fraudulent charges.Select AI doesn’t determine whether a transaction is approved or declined. That decision has already been made by the fraud scoring service using relational, spatial, and vector queries. Select AI turns the fraud assessment into a readable explanation via select ai narrate.
The explanation is generated in the same pipeline, using the same data. You could even use an in-database ONNX model instead of OCI GenAI to keep everything local.
Running the Select AI example
In this sample, Select AI uses OCI Generative AI as its inference provider. To enable the optional path, you’ll need an OCI account, a local OCI configuration file, and your OCI compartment ID.
The SelectAISetup helper creates the SELECTAI.MY_PROFILE profile, grants profile access to TESTUSER, and configures the OCI_GENAI credential used by that profile to authenticate with OCI.
Enable the example by setting your compartment ID and running the tests with the selectai profile:
export OCI_COMPARTMENT_ID=<compartment>
mvn test -pl txeventq-fraud-detection/pom.xml -DselectaiThe Select AI call runs after the fraud decision has been computed, making it an enrichment step rather than part of the scoring logic.

Leave a Reply