├── .gitignore
├── LICENSE
├── README.md
├── ai-vector-search
├── README.md
├── pom.xml
└── src
│ ├── main
│ └── java
│ │ └── com
│ │ └── example
│ │ ├── Embedding.java
│ │ ├── OracleDataAdapter.java
│ │ ├── OracleVectorSample.java
│ │ └── SearchRequest.java
│ └── test
│ ├── java
│ └── com
│ │ └── example
│ │ └── OracleVectorSampleIT.java
│ └── resources
│ └── country_facts.txt
├── database-per-service-example
├── README.md
├── courses
│ └── pom.xml
├── pom.xml
├── sample
│ └── pom.xml
└── students
│ └── pom.xml
├── jdbc-event-streaming
├── README.md
├── pom.xml
└── src
│ ├── main
│ └── java
│ │ └── com
│ │ └── example
│ │ └── jdbc
│ │ └── events
│ │ ├── App.java
│ │ ├── Event.java
│ │ ├── JDBCBatchProducer.java
│ │ └── JDBCConsumer.java
│ └── test
│ ├── java
│ └── com
│ │ └── example
│ │ └── jdbc
│ │ └── events
│ │ └── JDBCEventStreamingTest.java
│ └── resources
│ ├── jdbc-events.sql
│ └── producer-events.txt
├── jms-producer-consumer
├── README.md
├── pom.xml
└── src
│ ├── main
│ └── java
│ │ └── com
│ │ └── example
│ │ └── jms
│ │ ├── App.java
│ │ ├── queue
│ │ ├── QueueConsumer.java
│ │ └── QueueProducer.java
│ │ └── topic
│ │ ├── JMSConsumer.java
│ │ └── JMSProducer.java
│ └── test
│ ├── java
│ └── com
│ │ └── example
│ │ └── jms
│ │ ├── queue
│ │ └── JMSQueueTest.java
│ │ └── topic
│ │ └── JMSMultiConsumerTest.java
│ └── resources
│ ├── create-table.sql
│ ├── producer-events.txt
│ ├── testuser-queue.sql
│ └── testuser-topic.sql
├── migrate-kafka-to-oracle
├── README.md
├── kafka-app-step-1
│ ├── .gitignore
│ ├── pom.xml
│ └── src
│ │ └── main
│ │ ├── java
│ │ └── com
│ │ │ └── example
│ │ │ └── kafka1
│ │ │ ├── KafkaApp.java
│ │ │ └── WeatherEvent.java
│ │ └── resources
│ │ └── .gitignore
├── kafka-app-step-2
│ ├── .gitignore
│ ├── pom.xml
│ └── src
│ │ └── main
│ │ ├── java
│ │ └── com
│ │ │ └── example
│ │ │ └── kafka2
│ │ │ ├── KafkaApp.java
│ │ │ ├── OSONSerializer.java
│ │ │ └── WeatherEvent.java
│ │ └── resources
│ │ └── .gitignore
├── kafka-app-step-3
│ ├── .gitignore
│ ├── pom.xml
│ └── src
│ │ └── main
│ │ ├── java
│ │ └── com
│ │ │ └── example
│ │ │ └── kafka3
│ │ │ ├── KafkaApp.java
│ │ │ ├── OSONSerializer.java
│ │ │ └── WeatherEvent.java
│ │ └── resources
│ │ └── .gitignore
├── kafka-app
│ ├── .gitignore
│ ├── pom.xml
│ └── src
│ │ └── main
│ │ ├── java
│ │ └── com
│ │ │ └── example
│ │ │ └── kafka
│ │ │ ├── KafkaApp.java
│ │ │ └── WeatherEvent.java
│ │ └── resources
│ │ └── .gitignore
├── migrate-kafka-to-oracle-txeventq.png
├── testuser.sql
├── transactional-messaging.md
└── using-oracle-json.md
├── news-event-streaming
├── .gitignore
├── README.md
├── images
│ ├── converged.png
│ ├── streaming-diagram.png
│ └── summary.png
├── news-streaming-up
├── pom.xml
└── src
│ ├── main
│ ├── java
│ │ └── com
│ │ │ └── example
│ │ │ └── news
│ │ │ ├── Application.java
│ │ │ ├── NewsService.java
│ │ │ ├── events
│ │ │ ├── EventsConfiguration.java
│ │ │ ├── OKafkaManager.java
│ │ │ ├── factory
│ │ │ │ ├── NewsConsumerFactory.java
│ │ │ │ └── NewsParserConsumerProducerFactory.java
│ │ │ ├── parser
│ │ │ │ ├── Parser.java
│ │ │ │ ├── ParserConfiguration.java
│ │ │ │ └── Splitter.java
│ │ │ ├── producerconsumer
│ │ │ │ ├── NewsConsumer.java
│ │ │ │ ├── NewsParserConsumerProducer.java
│ │ │ │ ├── OKafkaTask.java
│ │ │ │ └── RawNewsProducer.java
│ │ │ └── serde
│ │ │ │ ├── JSONBDeserializer.java
│ │ │ │ └── JSONBSerializer.java
│ │ │ ├── genai
│ │ │ ├── GenAIConfiguration.java
│ │ │ ├── chat
│ │ │ │ ├── ChatService.java
│ │ │ │ └── OCIChatService.java
│ │ │ ├── embedding
│ │ │ │ ├── EmbeddingService.java
│ │ │ │ └── OCIEmbeddingService.java
│ │ │ └── vectorstore
│ │ │ │ ├── NewsQueryService.java
│ │ │ │ ├── NewsStore.java
│ │ │ │ └── VectorDataAdapter.java
│ │ │ └── model
│ │ │ ├── News.java
│ │ │ ├── NewsDTO.java
│ │ │ ├── NewsVector.java
│ │ │ └── SearchRequest.java
│ └── resources
│ │ └── application.yaml
│ └── test
│ ├── java
│ └── com
│ │ └── example
│ │ └── news
│ │ ├── NewsEventStreamingIT.java
│ │ ├── ParserTest.java
│ │ ├── Utils.java
│ │ └── events
│ │ └── parser
│ │ └── SplitterTest.java
│ └── resources
│ ├── cleanup.sql
│ ├── input-data.json
│ ├── intput-small.json
│ ├── news-schema.sql
│ ├── ojdbc.properties
│ ├── one-record.json
│ └── testuser.sql
├── oracle-database-kafka-apis
├── README.md
├── pom.xml
└── src
│ ├── main
│ └── java
│ │ └── com
│ │ └── example
│ │ ├── AdminUtil.java
│ │ ├── OKafkaProperties.java
│ │ ├── SampleConsumer.java
│ │ ├── SampleProducer.java
│ │ ├── TransactionalConsumer.java
│ │ ├── TransationalProducer.java
│ │ ├── auth
│ │ └── AuthenticationExample.java
│ │ └── okafka
│ │ └── App.java
│ └── test
│ ├── java
│ └── com
│ │ └── example
│ │ ├── OKafkaExampleIT.java
│ │ ├── TransactionalConsumeIT.java
│ │ └── TransactionalProduceIT.java
│ └── resources
│ ├── ojdbc.properties
│ ├── okafka.sql
│ └── weather_sensor_data.txt
├── pom.xml
├── spring-boot-dynamic-property-source
├── README.md
├── pom.xml
└── src
│ ├── main
│ ├── java
│ │ └── com
│ │ │ └── example
│ │ │ ├── DatabaseEnvironmentPostProcessor.java
│ │ │ ├── DatabaseProperties.java
│ │ │ ├── DatabasePropertyLoader.java
│ │ │ ├── DatabasePropertySource.java
│ │ │ ├── Property.java
│ │ │ ├── PropertyService.java
│ │ │ └── SampleApp.java
│ └── resources
│ │ ├── META-INF
│ │ └── spring.factories
│ │ └── application.yaml
│ └── test
│ ├── java
│ └── com
│ │ └── example
│ │ └── DatabasePropertySourceTest.java
│ └── resources
│ └── init.sql
├── spring-boot-jms-example
├── README.md
├── pom.xml
└── src
│ ├── main
│ ├── java
│ │ └── com
│ │ │ └── example
│ │ │ ├── Consumer.java
│ │ │ ├── Producer.java
│ │ │ └── SampleApp.java
│ └── resources
│ │ └── application.yaml
│ └── test
│ ├── java
│ └── com
│ │ └── example
│ │ └── SpringJmsTest.java
│ └── resources
│ └── init.sql
├── spring-data-mongodb-oracle-api
├── README.md
├── pom.xml
└── src
│ ├── main
│ ├── java
│ │ └── com
│ │ │ └── example
│ │ │ └── mongodb
│ │ │ ├── Application.java
│ │ │ ├── Student.java
│ │ │ └── StudentRepository.java
│ └── resources
│ │ └── application.yaml
│ └── test
│ └── java
│ └── com
│ └── example
│ └── mongodb
│ └── OracleMongoDBTest.java
├── spring-jpa
├── README.md
├── pom.xml
└── src
│ ├── main
│ ├── java
│ │ └── com
│ │ │ └── example
│ │ │ ├── Application.java
│ │ │ ├── model
│ │ │ └── Student.java
│ │ │ ├── paging
│ │ │ ├── model
│ │ │ │ ├── Author.java
│ │ │ │ ├── Book.java
│ │ │ │ └── BookGenre.java
│ │ │ └── repository
│ │ │ │ ├── AuthorRepository.java
│ │ │ │ ├── AuthorSpecifications.java
│ │ │ │ ├── BookGenreRepository.java
│ │ │ │ └── BookRepository.java
│ │ │ ├── relationships
│ │ │ ├── model
│ │ │ │ ├── Actor.java
│ │ │ │ ├── Director.java
│ │ │ │ ├── DirectorBio.java
│ │ │ │ └── Movie.java
│ │ │ └── repository
│ │ │ │ ├── ActorRepository.java
│ │ │ │ ├── DirectorBioRepository.java
│ │ │ │ ├── DirectorRepository.java
│ │ │ │ └── MovieRepository.java
│ │ │ └── repository
│ │ │ └── StudentRepository.java
│ └── resources
│ │ └── application.yaml
│ └── test
│ ├── java
│ └── com
│ │ └── example
│ │ ├── JPARelationshipsTest.java
│ │ ├── PagingSortingFilteringTest.java
│ │ └── SpringJPATest.java
│ └── resources
│ ├── movie.sql
│ ├── paging.sql
│ └── student.sql
├── spring-resource-sample
├── README.md
├── pom.xml
└── src
│ ├── main
│ ├── java
│ │ └── com
│ │ │ └── example
│ │ │ ├── DatabaseLocation.java
│ │ │ ├── DatabaseResource.java
│ │ │ ├── DatabaseResourceResolver.java
│ │ │ └── SampleApp.java
│ └── resources
│ │ └── application.yaml
│ └── test
│ ├── java
│ └── com
│ │ └── example
│ │ └── SpringDatabaseResourceTest.java
│ └── resources
│ └── cat.jpg
├── spring-vault-oracle-app
├── README.md
├── pom.xml
└── src
│ └── main
│ ├── java
│ └── com
│ │ └── example
│ │ ├── AppController.java
│ │ └── SampleApp.java
│ └── resources
│ └── application.yaml
├── testcontainers
├── README.md
├── pom.xml
└── src
│ ├── main
│ ├── java
│ │ └── com
│ │ │ └── example
│ │ │ └── Application.java
│ └── resources
│ │ └── application.yaml
│ └── test
│ ├── java
│ └── com
│ │ └── example
│ │ ├── GetDatabaseConnectionTest.java
│ │ ├── InitializedDatabaseTest.java
│ │ ├── SpringBootDatabaseTest.java
│ │ ├── SysdbaInitTest.java
│ │ └── reusable
│ │ ├── README.md
│ │ ├── ResuableDatabaseTest.java
│ │ ├── ReusableSelectTest.java
│ │ └── ReusableVersionTest.java
│ └── resources
│ ├── dbainit.sql
│ └── students.sql
└── txeventq-examples
├── README.md
├── okafka.sql
├── ords.md
├── pom.xml
├── springjms.sql
├── src
└── main
│ ├── java
│ └── com
│ │ └── example
│ │ └── txeventq
│ │ ├── OKafkaConsumer.java
│ │ ├── OKafkaProducer.java
│ │ ├── Prompt.java
│ │ ├── SpringJMSConsumer.java
│ │ ├── SpringJMSProducer.java
│ │ └── Values.java
│ └── resources
│ ├── application-jms-consumer.yaml
│ ├── application-jms-producer.yaml
│ ├── application.yaml
│ ├── banner-jms-consumer.txt
│ ├── banner-jms-producer.txt
│ ├── logback.xml
│ └── ojdbc.properties
└── txeventq.sql
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | *.iml
3 | target/
4 | .DS_Store
5 |
6 | # Oracle Database Wallet
7 | cwallet.sso
8 | ewallet.p12
9 | ewallet.pem
10 | keystore.jks
11 | sqlnet.ora
12 | tnsnames.ora
13 | truststore.jks
14 |
15 |
--------------------------------------------------------------------------------
/ai-vector-search/README.md:
--------------------------------------------------------------------------------
1 | # Similarity Search using Oracle Database 23ai
2 |
3 | This code sample demonstrates how to use Oracle Database 23ai as a vector store for similarity search on text embeddings.
4 |
5 | The [OracleVectorSample](src/main/java/com/example/OracleVectorSample.java) implements a vector store abstraction that supports inserting embeddings into the database, and querying embeddings.
6 |
7 | To learn more about Vector Database, read my article [Intro to Vector Databases](https://medium.com/@anders.swanson.93/intro-to-vector-databases-9f4330c47ac0)
8 |
9 | ## Running the sample
10 |
11 | Prerequisites:
12 | - Maven
13 | - Java 21+
14 | - A docker environment to support TestContainers
15 |
16 | Run the sample from the project root directory:
17 |
18 | ```shell
19 | mvn integration-test
20 | ```
--------------------------------------------------------------------------------
/ai-vector-search/src/main/java/com/example/Embedding.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | public record Embedding(float[] vector, String content) {}
4 |
--------------------------------------------------------------------------------
/ai-vector-search/src/main/java/com/example/OracleDataAdapter.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import java.sql.SQLException;
4 |
5 | import oracle.sql.VECTOR;
6 |
7 | public class OracleDataAdapter {
8 | float[] toFloatArray(double[] vector) {
9 | float[] result = new float[vector.length];
10 | for (int i = 0; i < vector.length; i++) {
11 | result[i] = (float) vector[i];
12 | }
13 | return result;
14 | }
15 |
16 | VECTOR toVECTOR(float[] vector, boolean normalizeVector) throws SQLException {
17 | if (normalizeVector) {
18 | vector = normalize(vector);
19 | }
20 | return VECTOR.ofFloat64Values(vector);
21 | }
22 |
23 | private float[] normalize(float[] v) {
24 | double squaredSum = 0d;
25 |
26 | for (float e : v) {
27 | squaredSum += e * e;
28 | }
29 |
30 | final float magnitude = (float) Math.sqrt(squaredSum);
31 |
32 | if (magnitude > 0) {
33 | final float multiplier = 1f / magnitude;
34 | final int length = v.length;
35 | for (int i = 0; i < length; i++) {
36 | v[i] *= multiplier;
37 | }
38 | }
39 |
40 | return v;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/ai-vector-search/src/main/java/com/example/SearchRequest.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import java.util.Objects;
4 |
5 | public class SearchRequest {
6 | private final String text;
7 | private final float[] vector;
8 | private final int maxResults;
9 | private final double minScore;
10 |
11 | public SearchRequest(String text, float[] vector) {
12 | this(text, vector, null, null);
13 | }
14 |
15 | public SearchRequest(String text, float[] vector, Integer maxResults, Double minScore) {
16 | this.text = text;
17 | this.vector = vector;
18 | this.maxResults = Objects.requireNonNullElse(maxResults, 1);
19 | this.minScore = Objects.requireNonNullElse(minScore, 0.0);
20 | }
21 |
22 | public String getText() {
23 | return text;
24 | }
25 |
26 | public float[] getVector() {
27 | return vector;
28 | }
29 |
30 | public int getMaxResults() {
31 | return maxResults;
32 | }
33 |
34 | public double getMinScore() {
35 | return minScore;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/database-per-service-example/README.md:
--------------------------------------------------------------------------------
1 | # Database Per Service pattern with Pluggable Databases (PDBs)
2 |
3 | Under construction! Stay tuned.
4 |
--------------------------------------------------------------------------------
/database-per-service-example/courses/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | com.example
7 | database-per-service
8 | 1.0.0-SNAPSHOT
9 | ../pom.xml
10 |
11 |
12 | courses-service
13 | 1.0.0-SNAPSHOT
14 | courses-service
15 | Courses Service for Database Per Service
16 |
17 |
--------------------------------------------------------------------------------
/database-per-service-example/sample/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | com.example
7 | database-per-service
8 | 1.0.0-SNAPSHOT
9 | ../pom.xml
10 |
11 |
12 | sample-runner
13 | 1.0.0-SNAPSHOT
14 | sample-runner
15 | Test Runner for Database Per Service Example
16 |
17 |
--------------------------------------------------------------------------------
/database-per-service-example/students/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | com.example
7 | database-per-service
8 | 1.0.0-SNAPSHOT
9 | ../pom.xml
10 |
11 |
12 | students
13 | 1.0.0-SNAPSHOT
14 | students
15 | Students service for database per service example
16 |
17 |
--------------------------------------------------------------------------------
/jdbc-event-streaming/README.md:
--------------------------------------------------------------------------------
1 | # JDBC Event Streaming
2 |
3 | Example of streaming events using a plain JDBC connection for Oracle Database Transactional Event Queues (TxEventQ).
4 |
5 | ## [jdbc.events package](./src/main/java/com/example/jdbc/events)
6 |
7 | The [jdbc.events package](./src/main/java/com/example/jdbc/events) package implements the JDBCBatchProducer class to send events, and the JDBCConsumer class to receive. These classes use PL/SQL procedures defined in the [jdbc-events.sql](./src/test/resources/jdbc-events.sql) script, specific the `produce_json_event` and `consume_json_event` procedures, respectively.
8 |
9 | The [JDBCEventStreamingTest](./src/test/java/com/example/jdbc/events/JDBCEventStreamingTest.java) class implements a test scenario using Oracle Database Free to produce and consume events:
10 |
11 | 1. The JDBCBatchProducer writes a series of records to a queue.
12 | 2. Three JDBCConsumer instances run in parallel to consume events.
13 | 3. Each consumer inserts events into a database table after receiving them.
14 | 4. The test class verifies all events were received and inserted into the database.
15 |
16 | ## Run the sample
17 |
18 | You can run the test with Maven (`mvn test`). Note that you need Java 21+ and a Docker-compatible environment to run the test. After the test is complete, you should see output similar to the following, though the ordering may be different do to parallelization:
19 |
20 | ```
21 | [PRODUCER] Published all events. Shutting down producer.
22 | [CONSUMER 1] Consumed 30 events. Shutting down consumer.
23 | [CONSUMER 3] Consumed 27 events. Shutting down consumer.
24 | [CONSUMER 2] Consumed 29 events. Shutting down consumer.
25 | ```
--------------------------------------------------------------------------------
/jdbc-event-streaming/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | com.example
7 | oracle-database-java-samples
8 | 1.0.0-SNAPSHOT
9 |
10 |
11 | jdbc-event-streaming
12 | 1.0.0-SNAPSHOT
13 | jdbc-event-streaming
14 | Example implementation of JDBC Event Streaming with Oracle Database Transactional Event Queues
15 |
16 |
17 |
18 | com.oracle.database.spring
19 | oracle-spring-boot-starter-json-collections
20 | ${oracle.starters.version}
21 |
22 |
23 | com.oracle.database.jdbc
24 | ucp
25 | ${oracle.version}
26 |
27 |
28 | com.oracle.database.jdbc
29 | ojdbc11
30 | ${oracle.version}
31 |
32 |
33 | com.oracle.database.messaging
34 | aqapi-jakarta
35 | ${aqapi.jakarta.version}
36 |
37 |
38 |
39 |
40 | org.springframework.boot
41 | spring-boot-starter-test
42 | test
43 |
44 |
45 | org.springframework.boot
46 | spring-boot-testcontainers
47 | test
48 |
49 |
50 | org.testcontainers
51 | oracle-free
52 | ${testcontainers.version}
53 | test
54 |
55 |
56 | org.testcontainers
57 | junit-jupiter
58 | test
59 |
60 |
61 |
62 |
63 |
64 |
65 | org.springframework.boot
66 | spring-boot-maven-plugin
67 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/jdbc-event-streaming/src/main/java/com/example/jdbc/events/App.java:
--------------------------------------------------------------------------------
1 | package com.example.jdbc.events;
2 |
3 | public class App {
4 | public static void main(String[] args) {}
5 | }
6 |
--------------------------------------------------------------------------------
/jdbc-event-streaming/src/main/java/com/example/jdbc/events/Event.java:
--------------------------------------------------------------------------------
1 | package com.example.jdbc.events;
2 |
3 | public class Event {
4 | private double temperature;
5 | private double humidity;
6 | private double latitude;
7 | private double longitude;
8 |
9 | public Event() {}
10 |
11 | public Event(double temperature, double humidity, double latitude, double longitude) {
12 | this.temperature = temperature;
13 | this.humidity = humidity;
14 | this.latitude = latitude;
15 | this.longitude = longitude;
16 | }
17 |
18 | public double getTemperature() {
19 | return temperature;
20 | }
21 |
22 | public void setTemperature(double temperature) {
23 | this.temperature = temperature;
24 | }
25 |
26 | public double getHumidity() {
27 | return humidity;
28 | }
29 |
30 | public void setHumidity(double humidity) {
31 | this.humidity = humidity;
32 | }
33 |
34 | public double getLatitude() {
35 | return latitude;
36 | }
37 |
38 | public void setLatitude(double latitude) {
39 | this.latitude = latitude;
40 | }
41 |
42 | public double getLongitude() {
43 | return longitude;
44 | }
45 |
46 | public void setLongitude(double longitude) {
47 | this.longitude = longitude;
48 | }
49 |
50 | @Override
51 | public final boolean equals(Object o) {
52 | if (!(o instanceof Event event)) return false;
53 |
54 | return Double.compare(getTemperature(), event.getTemperature()) == 0 && Double.compare(getHumidity(), event.getHumidity()) == 0 && Double.compare(getLatitude(), event.getLatitude()) == 0 && Double.compare(getLongitude(), event.getLongitude()) == 0;
55 | }
56 |
57 | @Override
58 | public int hashCode() {
59 | int result = Double.hashCode(getTemperature());
60 | result = 31 * result + Double.hashCode(getHumidity());
61 | result = 31 * result + Double.hashCode(getLatitude());
62 | result = 31 * result + Double.hashCode(getLongitude());
63 | return result;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/jdbc-event-streaming/src/main/java/com/example/jdbc/events/JDBCBatchProducer.java:
--------------------------------------------------------------------------------
1 | package com.example.jdbc.events;
2 |
3 | import java.sql.CallableStatement;
4 | import java.sql.Connection;
5 | import java.sql.SQLException;
6 | import java.util.List;
7 |
8 | import com.oracle.spring.json.jsonb.JSONB;
9 | import javax.sql.DataSource;
10 | import oracle.jdbc.OracleTypes;
11 |
12 | public class JDBCBatchProducer implements Runnable {
13 | private final DataSource dataSource;
14 | private final JSONB jsonb;
15 | private final List input;
16 | private final int batchSize;
17 |
18 |
19 | public JDBCBatchProducer(DataSource dataSource, JSONB jsonb, List input, int batchSize) {
20 | this.dataSource = dataSource;
21 | this.jsonb = jsonb;
22 | this.input = input;
23 | this.batchSize = batchSize;
24 | }
25 |
26 | @Override
27 | public void run() {
28 | try (Connection conn = dataSource.getConnection();
29 | CallableStatement cs = conn.prepareCall(
30 | "{CALL produce_json_event(?)}"
31 | )) {
32 |
33 | for (int i = 0; i < input.size(); i++) {
34 | // Convert java object to OSON.
35 | byte[] oson = jsonb.toOSON(input.get(i));
36 | // Set OSON in callable statement.
37 | cs.setObject(1, oson, OracleTypes.JSON);
38 | cs.addBatch();
39 |
40 | // Produce a batch of events
41 | if (i % batchSize == 0) {
42 | cs.executeBatch();
43 | }
44 | }
45 | // Produce any remaining events in the batch.
46 | if (input.size() % batchSize != 0) {
47 | cs.executeBatch();
48 | }
49 | } catch (SQLException e) {
50 | System.out.println("[PRODUCER] " + e.getMessage());
51 | throw new RuntimeException(e);
52 | }
53 | System.out.println("[PRODUCER] Published all events. Shutting down producer.");
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/jdbc-event-streaming/src/main/java/com/example/jdbc/events/JDBCConsumer.java:
--------------------------------------------------------------------------------
1 | package com.example.jdbc.events;
2 |
3 | import java.sql.CallableStatement;
4 | import java.sql.Connection;
5 | import java.sql.PreparedStatement;
6 | import java.sql.SQLException;
7 | import java.util.concurrent.CountDownLatch;
8 |
9 | import javax.sql.DataSource;
10 | import oracle.jdbc.OracleTypes;
11 |
12 | public class JDBCConsumer implements Runnable {
13 | // Ignore errors empty queue errors.
14 | private static final int END_OF_FETCH_CODE = 25228;
15 |
16 | private final DataSource dataSource;
17 | private final int id;
18 | private final CountDownLatch expectedEvents;
19 | private boolean done = false;
20 | private int consumedEvents = 0;
21 |
22 | public JDBCConsumer(DataSource dataSource, int id, CountDownLatch expectedEvents) {
23 | this.dataSource = dataSource;
24 | this.id = id;
25 | this.expectedEvents = expectedEvents;
26 | }
27 |
28 | @Override
29 | public void run() {
30 | if (expectedEvents.getCount() > 0) {
31 | try (Connection conn = dataSource.getConnection();
32 | CallableStatement cs = conn.prepareCall("{? = call consume_json_event()}");
33 | ) {
34 |
35 | // Consume the event.
36 | cs.registerOutParameter(1, OracleTypes.JSON);
37 | cs.executeUpdate();
38 |
39 | // Save the event into the weather_events table.
40 | byte[] oson = cs.getBytes(1);
41 | if (oson != null) {
42 | try (PreparedStatement ps = conn.prepareStatement("insert into weather_events (data) values(?)")) {
43 | ps.setObject(1, oson, OracleTypes.JSON);
44 | ps.execute();
45 | expectedEvents.countDown();
46 | consumedEvents++;
47 | }
48 | }
49 | } catch (SQLException e) {
50 | if (e.getErrorCode() != END_OF_FETCH_CODE) {
51 | System.err.println("Error consuming event: " + e.getMessage());
52 | }
53 | }
54 | } else if (!done) {
55 | done = true;
56 | System.out.printf("[CONSUMER %d] Consumed %d events. Shutting down consumer.\n", id, consumedEvents);
57 | }
58 |
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/jdbc-event-streaming/src/test/resources/jdbc-events.sql:
--------------------------------------------------------------------------------
1 | -- When using Oracle Database Free as a local container, this value may be too low for subscriber jobs.
2 | -- It is not usually necessary to change this value outside of Oracle Database Free container instances.
3 | alter system set job_queue_processes=10;
4 | -- Set as appropriate for your database. "freepdb1" is the default PDB in Oracle Database Free
5 | alter session set container = freepdb1;
6 |
7 | -- Configure testuser with the necessary privileges to use Transactional Event Queues.
8 | grant execute on dbms_aq to testuser;
9 | grant execute on dbms_aqadm to testuser;
10 | grant execute on dbms_aqin to testuser;
11 | grant execute on dbms_aqjms to testuser;
12 |
13 | -- Create a Transactional Event Queue
14 | begin
15 | -- See https://docs.oracle.com/en/database/oracle/oracle-database/23/arpls/DBMS_AQADM.html#GUID-93B0FF90-5045-4437-A9C4-B7541BEBE573
16 | -- For documentation on creating Transactional Event Queues.
17 | dbms_aqadm.create_transactional_event_queue(
18 | queue_name => 'testuser.event_stream',
19 | -- Payload can be RAW, JSON, DBMS_AQADM.JMS_TYPE, or an object type.
20 | -- Default is DBMS_AQADM.JMS_TYPE.
21 | queue_payload_type => 'JSON'
22 | );
23 | -- Start the queue
24 | dbms_aqadm.start_queue(
25 | queue_name => 'testuser.event_stream'
26 | );
27 | end;
28 | /
29 |
30 | -- Procedure to produce a JSON event to the event_stream queue.
31 | create or replace procedure testuser.produce_json_event (
32 | event in json
33 | ) as
34 | enqueue_options dbms_aq.enqueue_options_t;
35 | message_properties dbms_aq.message_properties_t;
36 | msg_id raw(16);
37 | begin
38 | enqueue_options := dbms_aq.enqueue_options_t();
39 | message_properties := dbms_aq.message_properties_t();
40 | dbms_aq.enqueue(
41 | queue_name => 'testuser.event_stream',
42 | enqueue_options => enqueue_options,
43 | message_properties => message_properties,
44 | payload => event,
45 | msgid => msg_id
46 | );
47 | commit;
48 | end;
49 | /
50 |
51 | -- Procedure to consume a JSON event from the event_stream queue.
52 | create or replace function testuser.consume_json_event return json is
53 | dequeue_options dbms_aq.dequeue_options_t;
54 | message_properties dbms_aq.message_properties_t;
55 | msg_id raw(16);
56 | event json;
57 | begin
58 | dequeue_options := dbms_aq.dequeue_options_t();
59 | message_properties := dbms_aq.message_properties_t();
60 | dequeue_options.navigation := dbms_aq.first_message;
61 | dequeue_options.wait := dbms_aq.no_wait;
62 |
63 | dbms_aq.dequeue(
64 | queue_name => 'testuser.event_stream',
65 | dequeue_options => dequeue_options,
66 | message_properties => message_properties,
67 | payload => event,
68 | msgid => msg_id
69 | );
70 | return event;
71 | end;
72 | /
73 |
74 | -- Create a table to store JSON events
75 | create table testuser.weather_events
76 | (
77 | id number(10) generated always as identity primary key,
78 | data json
79 | );
80 | /
81 |
--------------------------------------------------------------------------------
/jms-producer-consumer/src/main/java/com/example/jms/App.java:
--------------------------------------------------------------------------------
1 | package com.example.jms;
2 |
3 | public class App {
4 | public static void main(String[] args) {}
5 | }
6 |
--------------------------------------------------------------------------------
/jms-producer-consumer/src/main/java/com/example/jms/queue/QueueProducer.java:
--------------------------------------------------------------------------------
1 | package com.example.jms.queue;
2 |
3 | import java.util.List;
4 |
5 | import jakarta.jms.JMSException;
6 | import jakarta.jms.Queue;
7 | import jakarta.jms.QueueConnection;
8 | import jakarta.jms.Session;
9 | import jakarta.jms.TextMessage;
10 | import javax.sql.DataSource;
11 | import oracle.jakarta.jms.AQjmsFactory;
12 | import oracle.jakarta.jms.AQjmsQueueSender;
13 | import oracle.jakarta.jms.AQjmsSession;
14 |
15 | public class QueueProducer implements Runnable {
16 | private final DataSource dataSource;
17 | private final String username;
18 | private final String queueName;
19 | private final List input;
20 |
21 |
22 | public QueueProducer(DataSource dataSource, String username, String queueName, List input) {
23 | this.dataSource = dataSource;
24 | this.username = username;
25 | this.queueName = queueName;
26 | this.input = input;
27 | }
28 |
29 |
30 | @Override
31 | public void run() {
32 | System.out.printf("[PRODUCER] Producing %d messages.\n", input.size());
33 | // Create a new JMS connection and session.
34 | try (QueueConnection connection = AQjmsFactory.getQueueConnectionFactory(dataSource).createQueueConnection();
35 | AQjmsSession session = (AQjmsSession) connection.createQueueSession(true, Session.AUTO_ACKNOWLEDGE)) {
36 |
37 | // The JMS Connection must be started before use.
38 | connection.start();
39 | Queue jmsQueue = session.getQueue(username, queueName);
40 | AQjmsQueueSender sender = (AQjmsQueueSender) session.createSender(jmsQueue);
41 | // Write the input data as JMS text messages to a JMS topic.
42 | for (String s : input) {
43 | TextMessage message = session.createTextMessage(s);
44 | sender.send(message);
45 | }
46 | session.commit();
47 | } catch (JMSException e) {
48 | System.out.println("JMSException caught: " + e);
49 | throw new RuntimeException(e);
50 | }
51 |
52 | System.out.println("[PRODUCER] Sent all JMS messages. Closing producer!");
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/jms-producer-consumer/src/main/java/com/example/jms/topic/JMSProducer.java:
--------------------------------------------------------------------------------
1 | package com.example.jms.topic;
2 |
3 | import java.util.List;
4 | import java.util.UUID;
5 |
6 | import jakarta.jms.JMSException;
7 | import jakarta.jms.Session;
8 | import jakarta.jms.TextMessage;
9 | import jakarta.jms.Topic;
10 | import jakarta.jms.TopicConnection;
11 | import javax.sql.DataSource;
12 | import oracle.jakarta.jms.AQjmsFactory;
13 | import oracle.jakarta.jms.AQjmsSession;
14 | import oracle.jakarta.jms.AQjmsTopicPublisher;
15 |
16 | public class JMSProducer implements Runnable {
17 | private final DataSource dataSource;
18 | private final String username;
19 | private final String topicName;
20 | private final List input;
21 |
22 |
23 | public JMSProducer(DataSource dataSource, String username, String topicName, List input) {
24 | this.dataSource = dataSource;
25 | this.username = username;
26 | this.topicName = topicName;
27 | this.input = input;
28 | }
29 |
30 |
31 | @Override
32 | public void run() {
33 | // Create a new JMS connection and session.
34 | try (TopicConnection connection = AQjmsFactory.getTopicConnectionFactory(dataSource).createTopicConnection();
35 | AQjmsSession session = (AQjmsSession) connection.createTopicSession(true, Session.AUTO_ACKNOWLEDGE)) {
36 |
37 | connection.start();
38 | Topic jmsTopic = session.getTopic(username, topicName);
39 | // The JMS Connection must be started before use.
40 | AQjmsTopicPublisher publisher = (AQjmsTopicPublisher) session.createPublisher(jmsTopic);
41 | // Write the input data as JMS text messages to a JMS topic.
42 | for (String s : input) {
43 | TextMessage message = session.createTextMessage(s);
44 | message.setJMSCorrelationID(UUID.randomUUID().toString());
45 | publisher.publish(message);
46 | }
47 | session.commit();
48 | } catch (JMSException e) {
49 | System.out.println("JMSException caught: " + e);
50 | throw new RuntimeException(e);
51 | }
52 |
53 | System.out.println("[PRODUCER] Sent all JMS messages. Closing producer!");
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/jms-producer-consumer/src/test/resources/create-table.sql:
--------------------------------------------------------------------------------
1 | -- Create a table to store JSON events
2 | create table weather_events
3 | (
4 | id number(10) generated always as identity primary key,
5 | data json
6 | );
7 |
--------------------------------------------------------------------------------
/jms-producer-consumer/src/test/resources/testuser-queue.sql:
--------------------------------------------------------------------------------
1 | -- When using Oracle Database Free as a local container, this value may be too low for subscriber jobs.
2 | -- It is not usually necessary to change this value outside of Oracle Database Free container instances.
3 | alter system set job_queue_processes=10;
4 | -- Set as appropriate for your database. "freepdb1" is the default PDB in Oracle Database Free
5 | alter session set container = freepdb1;
6 |
7 | -- Configure testuser with the necessary privileges to use Transactional Event Queues.
8 | grant execute on dbms_aq to testuser;
9 | grant execute on dbms_aqadm to testuser;
10 | grant execute on dbms_aqin to testuser;
11 | grant execute on dbms_aqjms to testuser;
12 |
13 | -- Create a Transactional Event Queue
14 | begin
15 | -- See https://docs.oracle.com/en/database/oracle/oracle-database/23/arpls/DBMS_AQADM.html#GUID-93B0FF90-5045-4437-A9C4-B7541BEBE573
16 | -- For documentation on creating Transactional Event Queues.
17 | dbms_aqadm.create_transactional_event_queue(
18 | queue_name => 'testuser.myqueue',
19 | -- Payload can be RAW, JSON, DBMS_AQADM.JMS_TYPE, or an object type.
20 | -- Default is DBMS_AQADM.JMS_TYPE.
21 | queue_payload_type => DBMS_AQADM.JMS_TYPE,
22 | multiple_consumers => false
23 | );
24 |
25 | -- Start the queue
26 | dbms_aqadm.start_queue(
27 | queue_name => 'testuser.myqueue'
28 | );
29 | end;
30 | /
31 |
--------------------------------------------------------------------------------
/jms-producer-consumer/src/test/resources/testuser-topic.sql:
--------------------------------------------------------------------------------
1 | -- When using Oracle Database Free as a local container, this value may be too low for subscriber jobs.
2 | -- It is not usually necessary to change this value outside of Oracle Database Free container instances.
3 | alter system set job_queue_processes=10;
4 | -- Set as appropriate for your database. "freepdb1" is the default PDB in Oracle Database Free
5 | alter session set container = freepdb1;
6 |
7 | -- Configure testuser with the necessary privileges to use Transactional Event Queues.
8 | grant execute on dbms_aq to testuser;
9 | grant execute on dbms_aqadm to testuser;
10 | grant execute on dbms_aqin to testuser;
11 | grant execute on dbms_aqjms to testuser;
12 |
13 | -- Create a Transactional Event Queue
14 | begin
15 | -- See https://docs.oracle.com/en/database/oracle/oracle-database/23/arpls/DBMS_AQADM.html#GUID-93B0FF90-5045-4437-A9C4-B7541BEBE573
16 | -- For documentation on creating Transactional Event Queues.
17 | dbms_aqadm.create_transactional_event_queue(
18 | queue_name => 'testuser.mytopic',
19 | -- Payload can be RAW, JSON, DBMS_AQADM.JMS_TYPE, or an object type.
20 | -- Default is DBMS_AQADM.JMS_TYPE.
21 | queue_payload_type => DBMS_AQADM.JMS_TYPE,
22 | -- FALSE means queues can only have one consumer for each message. This is the default.
23 | -- TRUE means queues created in the table can have multiple consumers for each message.
24 | multiple_consumers => true
25 | );
26 |
27 | -- 6 queue shards for consumer parallelization
28 | dbms_aqadm.set_queue_parameter('testuser.mytopic', 'shard_num', 6);
29 | -- must be set to make sure that order is guaranteed at the jms consumer.
30 | dbms_aqadm.set_queue_parameter('testuser.mytopic', 'sticky_dequeue', 1);
31 | -- must be set to make sure that order of the events per correlation in place
32 | DBMS_AQADM.set_queue_parameter('testuser.mytopic', 'KEY_BASED_ENQUEUE', 1);
33 |
34 | -- Start the queue
35 | dbms_aqadm.start_queue(
36 | queue_name => 'testuser.mytopic'
37 | );
38 | end;
39 | /
40 |
41 | begin
42 | dbms_aqadm.add_subscriber(
43 | queue_name => 'testuser.mytopic',
44 | subscriber => sys.aq$_agent('example_subscriber_1', null, null)
45 | );
46 | dbms_aqadm.add_subscriber(
47 | queue_name => 'testuser.mytopic',
48 | subscriber => sys.aq$_agent('example_subscriber_2', null, null)
49 | );
50 | end;
51 | /
52 |
--------------------------------------------------------------------------------
/migrate-kafka-to-oracle/kafka-app-step-1/.gitignore:
--------------------------------------------------------------------------------
1 | ojdbc.properties
2 |
3 |
--------------------------------------------------------------------------------
/migrate-kafka-to-oracle/kafka-app-step-1/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | com.example
8 | oracle-database-java-samples
9 | 1.0.0-SNAPSHOT
10 | ../../pom.xml
11 |
12 |
13 | kafka-app-step-1
14 | kafka-app
15 | Sample Kafka App
16 | 1.0.0-SNAPSHOT
17 |
18 |
19 | com.example.kafka1.KafkaApp
20 |
21 |
22 |
23 |
24 | com.oracle.database.messaging
25 | okafka
26 | 23.6.0.0
27 |
28 |
29 | org.slf4j
30 | slf4j-nop
31 |
32 |
33 |
--------------------------------------------------------------------------------
/migrate-kafka-to-oracle/kafka-app-step-1/src/main/resources/.gitignore:
--------------------------------------------------------------------------------
1 | ojdbc.properties
2 | wallet/
3 |
--------------------------------------------------------------------------------
/migrate-kafka-to-oracle/kafka-app-step-2/.gitignore:
--------------------------------------------------------------------------------
1 | ojdbc.properties
2 |
3 |
--------------------------------------------------------------------------------
/migrate-kafka-to-oracle/kafka-app-step-2/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | com.example
8 | oracle-database-java-samples
9 | 1.0.0-SNAPSHOT
10 | ../../pom.xml
11 |
12 |
13 | kafka-app-step-2
14 | kafka-app
15 | Sample Kafka App
16 | 1.0.0-SNAPSHOT
17 |
18 |
19 | com.example.kafka2.KafkaApp
20 |
21 |
22 |
23 |
24 | com.oracle.database.messaging
25 | okafka
26 | 23.6.0.0
27 |
28 |
29 | com.oracle.database.spring
30 | oracle-spring-boot-starter-json-collections
31 | 25.2.0
32 |
33 |
34 | org.springframework.boot
35 | spring-boot-starter
36 |
37 |
38 |
39 |
40 | org.slf4j
41 | slf4j-nop
42 |
43 |
44 |
--------------------------------------------------------------------------------
/migrate-kafka-to-oracle/kafka-app-step-2/src/main/java/com/example/kafka2/OSONSerializer.java:
--------------------------------------------------------------------------------
1 | package com.example.kafka2;
2 |
3 | import com.oracle.spring.json.jsonb.JSONB;
4 | import org.apache.kafka.common.serialization.Serializer;
5 |
6 | public class OSONSerializer implements Serializer {
7 | private final JSONB jsonb;
8 |
9 | public OSONSerializer(JSONB jsonb) {
10 | this.jsonb = jsonb;
11 | }
12 |
13 | @Override
14 | public byte[] serialize(String s, T obj) {
15 | return jsonb.toOSON(obj);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/migrate-kafka-to-oracle/kafka-app-step-2/src/main/resources/.gitignore:
--------------------------------------------------------------------------------
1 | ojdbc.properties
2 | wallet/
3 |
--------------------------------------------------------------------------------
/migrate-kafka-to-oracle/kafka-app-step-3/.gitignore:
--------------------------------------------------------------------------------
1 | ojdbc.properties
2 |
3 |
--------------------------------------------------------------------------------
/migrate-kafka-to-oracle/kafka-app-step-3/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | com.example
8 | oracle-database-java-samples
9 | 1.0.0-SNAPSHOT
10 | ../../pom.xml
11 |
12 |
13 | kafka-app-step-3
14 | kafka-app
15 | Sample Kafka App
16 | 1.0.0-SNAPSHOT
17 |
18 |
19 | com.example.kafka3.KafkaApp
20 |
21 |
22 |
23 |
24 | com.oracle.database.messaging
25 | okafka
26 | 23.6.0.0
27 |
28 |
29 | com.oracle.database.spring
30 | oracle-spring-boot-starter-json-collections
31 | 25.2.0
32 |
33 |
34 | org.springframework.boot
35 | spring-boot-starter
36 |
37 |
38 |
39 |
40 | org.slf4j
41 | slf4j-nop
42 |
43 |
44 |
--------------------------------------------------------------------------------
/migrate-kafka-to-oracle/kafka-app-step-3/src/main/java/com/example/kafka3/OSONSerializer.java:
--------------------------------------------------------------------------------
1 | package com.example.kafka3;
2 |
3 | import com.oracle.spring.json.jsonb.JSONB;
4 | import org.apache.kafka.common.serialization.Serializer;
5 |
6 | public class OSONSerializer implements Serializer {
7 | private final JSONB jsonb;
8 |
9 | public OSONSerializer(JSONB jsonb) {
10 | this.jsonb = jsonb;
11 | }
12 |
13 | @Override
14 | public byte[] serialize(String s, T obj) {
15 | return jsonb.toOSON(obj);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/migrate-kafka-to-oracle/kafka-app-step-3/src/main/resources/.gitignore:
--------------------------------------------------------------------------------
1 | ojdbc.properties
2 | wallet/
3 |
--------------------------------------------------------------------------------
/migrate-kafka-to-oracle/kafka-app/.gitignore:
--------------------------------------------------------------------------------
1 | ojdbc.properties
2 |
3 |
--------------------------------------------------------------------------------
/migrate-kafka-to-oracle/kafka-app/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | com.example
8 | oracle-database-java-samples
9 | 1.0.0-SNAPSHOT
10 | ../../pom.xml
11 |
12 |
13 | kafka-app
14 | kafka-app
15 | Sample Kafka App
16 | 1.0.0-SNAPSHOT
17 |
18 |
19 | com.example.kafka.KafkaApp
20 |
21 |
22 |
23 |
24 | org.apache.kafka
25 | kafka-clients
26 |
27 |
28 | org.slf4j
29 | slf4j-nop
30 |
31 |
32 |
--------------------------------------------------------------------------------
/migrate-kafka-to-oracle/kafka-app/src/main/resources/.gitignore:
--------------------------------------------------------------------------------
1 | ojdbc.properties
2 | wallet/
3 |
--------------------------------------------------------------------------------
/migrate-kafka-to-oracle/migrate-kafka-to-oracle-txeventq.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anders-swanson/oracle-database-java-samples/7f3175c3eacbd462de3ba10775d9070b2d9613a9/migrate-kafka-to-oracle/migrate-kafka-to-oracle-txeventq.png
--------------------------------------------------------------------------------
/migrate-kafka-to-oracle/testuser.sql:
--------------------------------------------------------------------------------
1 | alter session set container = freepdb1;
2 | create user testuser identified by testpwd;
3 |
4 | -- you may wish to modify the unlimited tablespace grant as appropriate.
5 | grant resource, connect, unlimited tablespace to testuser;
6 |
7 | -- TxEventQ related grants
8 | grant aq_user_role to testuser;
9 | grant execute on dbms_aq to testuser;
10 | grant execute on dbms_aqadm to testuser;
11 | grant select on gv_$session to testuser;
12 | grant select on v_$session to testuser;
13 | grant select on gv_$instance to testuser;
14 | grant select on gv_$listener_network to testuser;
15 | grant select on sys.dba_rsrc_plan_directives to testuser;
16 | grant select on gv_$pdbs to testuser;
17 | grant select on user_queue_partition_assignment_table to testuser;
18 | exec dbms_aqadm.grant_priv_for_rm_plan('testuser');
19 | commit;
20 |
--------------------------------------------------------------------------------
/news-event-streaming/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | target/
3 |
--------------------------------------------------------------------------------
/news-event-streaming/images/converged.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anders-swanson/oracle-database-java-samples/7f3175c3eacbd462de3ba10775d9070b2d9613a9/news-event-streaming/images/converged.png
--------------------------------------------------------------------------------
/news-event-streaming/images/streaming-diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anders-swanson/oracle-database-java-samples/7f3175c3eacbd462de3ba10775d9070b2d9613a9/news-event-streaming/images/streaming-diagram.png
--------------------------------------------------------------------------------
/news-event-streaming/images/summary.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anders-swanson/oracle-database-java-samples/7f3175c3eacbd462de3ba10775d9070b2d9613a9/news-event-streaming/images/summary.png
--------------------------------------------------------------------------------
/news-event-streaming/news-streaming-up:
--------------------------------------------------------------------------------
1 | #!/bin.bash
2 |
3 |
--------------------------------------------------------------------------------
/news-event-streaming/src/main/java/com/example/news/Application.java:
--------------------------------------------------------------------------------
1 | package com.example.news;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 |
6 | @SpringBootApplication
7 | public class Application {
8 | public static void main(String[] args) {
9 | SpringApplication.run(Application.class, args);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/news-event-streaming/src/main/java/com/example/news/NewsService.java:
--------------------------------------------------------------------------------
1 | package com.example.news;
2 |
3 |
4 | import java.net.URI;
5 | import java.sql.SQLException;
6 | import java.util.List;
7 |
8 | import com.example.news.events.producerconsumer.RawNewsProducer;
9 | import com.example.news.genai.vectorstore.NewsQueryService;
10 | import com.example.news.model.NewsDTO;
11 | import com.example.news.model.SearchRequest;
12 | import org.springframework.http.ResponseEntity;
13 | import org.springframework.web.bind.annotation.GetMapping;
14 | import org.springframework.web.bind.annotation.PathVariable;
15 | import org.springframework.web.bind.annotation.PostMapping;
16 | import org.springframework.web.bind.annotation.RequestBody;
17 | import org.springframework.web.bind.annotation.RestController;
18 | import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
19 |
20 | @RestController
21 | public class NewsService {
22 | private final RawNewsProducer rawNewsProducer;
23 | private final NewsQueryService newsQueryService;
24 |
25 | public NewsService(RawNewsProducer rawNewsProducer, NewsQueryService newsQueryService) {
26 | this.rawNewsProducer = rawNewsProducer;
27 | this.newsQueryService = newsQueryService;
28 | }
29 |
30 |
31 | @PostMapping("/news")
32 | public ResponseEntity> postNews(@RequestBody List news) throws SQLException {
33 | rawNewsProducer.send(news);
34 | return ResponseEntity.created(location()).build();
35 | }
36 |
37 | @GetMapping("/news")
38 | public ResponseEntity getNews(@RequestBody SearchRequest searchRequest) throws Exception {
39 | List results = newsQueryService.search(searchRequest);
40 | if (results.isEmpty()) {
41 | return ResponseEntity.notFound().build();
42 | }
43 | return ResponseEntity.ok(new SearchResponse(results));
44 | }
45 |
46 | @PostMapping("/news/summarize")
47 | public ResponseEntity summarize(@RequestBody SearchRequest searchRequest) throws Exception {
48 | return ResponseEntity.ok(new SummarizeResponse(newsQueryService.summarize(searchRequest)));
49 | }
50 |
51 | @PostMapping("/news/summarize/{id}")
52 | public ResponseEntity summarizeById(@PathVariable("id") String id) throws Exception {
53 | return ResponseEntity.ok(new SummarizeResponse(newsQueryService.summarize(id)));
54 | }
55 |
56 | @PostMapping("/news/reset")
57 | public ResponseEntity> resetNews() throws Exception {
58 | newsQueryService.cleanup();
59 | return ResponseEntity.noContent().build();
60 | }
61 |
62 | public record SummarizeResponse(String result) {}
63 |
64 | public record SearchResponse(List articles) {
65 | }
66 |
67 | private URI location() {
68 | return ServletUriComponentsBuilder
69 | .fromCurrentRequest()
70 | .build()
71 | .toUri();
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/news-event-streaming/src/main/java/com/example/news/events/EventsConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.example.news.events;
2 |
3 | import java.util.Properties;
4 |
5 | import com.example.news.events.producerconsumer.RawNewsProducer;
6 | import org.oracle.okafka.clients.producer.KafkaProducer;
7 | import org.springframework.beans.factory.annotation.Qualifier;
8 | import org.springframework.beans.factory.annotation.Value;
9 | import org.springframework.context.annotation.Bean;
10 | import org.springframework.context.annotation.Configuration;
11 |
12 | @Configuration
13 | public class EventsConfiguration {
14 | @Value("${okafka.ojdbcPath}")
15 | private String ojdbcPath;
16 |
17 | @Value("${okafka.bootstrapServers:localhost:1521}")
18 | private String bootstrapServers;
19 |
20 | // We use the default 23ai Free service name
21 | @Value("${okafka.serviceName:freepdb1}")
22 | private String serviceName;
23 |
24 | // We use plaintext for a containerized, local database.
25 | // Use "SSL" for wallet connections, like Autonomous Database.
26 | @Value("${okafka.securityProtocol:PLAINTEXT}")
27 | private String securityProtocol;
28 |
29 | @Value("${news.topic.raw}")
30 | private String rawTopic;
31 |
32 | @Bean
33 | @Qualifier("stringProducer")
34 | public KafkaProducer stringProducer() {
35 | Properties props = okafkaProperties();
36 | props.put("enable.idempotence", "true");
37 | props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
38 | props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
39 | // Note the use of the org.oracle.okafka.clients.producer.KafkaProducer class
40 | // for producing records to Oracle Database Transactional Event Queues.
41 | KafkaProducer stringProducer = new KafkaProducer<>(props);
42 | return stringProducer;
43 | }
44 |
45 | @Bean
46 | public RawNewsProducer rawNewsProducer(@Qualifier("stringProducer") KafkaProducer stringProducer) {
47 | return new RawNewsProducer(rawTopic, stringProducer);
48 | }
49 |
50 | @Bean
51 | @Qualifier("okafkaProperties")
52 | public Properties okafkaProperties() {
53 | Properties props = new Properties();
54 | props.put("oracle.service.name", serviceName);
55 | props.put("security.protocol", securityProtocol);
56 | props.put("bootstrap.servers", bootstrapServers);
57 | // If using Oracle Database wallet, pass wallet directory
58 | props.put("oracle.net.tns_admin", ojdbcPath);
59 | return props;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/news-event-streaming/src/main/java/com/example/news/events/factory/NewsConsumerFactory.java:
--------------------------------------------------------------------------------
1 | package com.example.news.events.factory;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 | import java.util.Properties;
6 |
7 | import com.example.news.events.producerconsumer.NewsConsumer;
8 | import com.example.news.events.producerconsumer.OKafkaTask;
9 | import com.example.news.genai.vectorstore.NewsStore;
10 | import com.example.news.model.News;
11 | import com.example.news.events.serde.JSONBDeserializer;
12 | import com.oracle.spring.json.jsonb.JSONB;
13 | import org.apache.kafka.common.serialization.Deserializer;
14 | import org.apache.kafka.common.serialization.StringDeserializer;
15 | import org.oracle.okafka.clients.consumer.KafkaConsumer;
16 | import org.springframework.beans.factory.annotation.Qualifier;
17 | import org.springframework.beans.factory.annotation.Value;
18 | import org.springframework.stereotype.Component;
19 |
20 | @Component
21 | public class NewsConsumerFactory {
22 | private final Properties okafkaProperties;
23 | private final NewsStore newsStore;
24 | private final JSONB jsonb;
25 | private final String parsedTopic;
26 | private final int numThreads;
27 |
28 | public NewsConsumerFactory(@Qualifier("okafkaProperties") Properties okafkaProperties,
29 | NewsStore newsStore,
30 | JSONB jsonb,
31 | @Value("${news.topic.parsed}") String parsedTopic,
32 | @Value("${news.threads.newsParserConsumerProducer}") int numThreads) {
33 | this.okafkaProperties = okafkaProperties;
34 | this.newsStore = newsStore;
35 | this.jsonb = jsonb;
36 | this.parsedTopic = parsedTopic;
37 | this.numThreads = numThreads;
38 | }
39 |
40 | public List create() {
41 | List tasks = new ArrayList<>();
42 | for (int i = 0; i < numThreads; i++) {
43 | NewsConsumer newsConsumer = new NewsConsumer(
44 | createConsumer(),
45 | parsedTopic,
46 | newsStore
47 | );
48 | tasks.add(newsConsumer);
49 | }
50 | return tasks;
51 | }
52 |
53 | public KafkaConsumer createConsumer() {
54 | Properties props = new Properties();
55 | props.putAll(okafkaProperties);
56 | props.put("auto.offset.reset", "earliest");
57 | props.put("group.id" , "NEWS_CONSUMER");
58 | props.put("enable.auto.commit","false");
59 | props.put("max.poll.records", 50);
60 |
61 | Deserializer keyDeserializer = new StringDeserializer();
62 | Deserializer valueDeserializer = new JSONBDeserializer<>(jsonb, News.class);
63 | return new KafkaConsumer<>(props, keyDeserializer, valueDeserializer);
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/news-event-streaming/src/main/java/com/example/news/events/parser/Parser.java:
--------------------------------------------------------------------------------
1 | package com.example.news.events.parser;
2 |
3 | import java.sql.SQLException;
4 | import java.util.ArrayList;
5 | import java.util.List;
6 | import java.util.concurrent.CompletableFuture;
7 |
8 | import com.example.news.model.News;
9 | import com.example.news.genai.embedding.EmbeddingService;
10 | import com.example.news.model.NewsVector;
11 | import oracle.sql.VECTOR;
12 | import org.springframework.scheduling.annotation.Async;
13 | import org.springframework.stereotype.Component;
14 |
15 | @Component
16 | public class Parser {
17 | private final EmbeddingService embeddingService;
18 | private final Splitter splitter;
19 |
20 | public Parser(EmbeddingService embeddingService,
21 | Splitter splitter) {
22 | this.embeddingService = embeddingService;
23 | this.splitter = splitter;
24 | }
25 |
26 | @Async
27 | public CompletableFuture parseAsync(String text) throws SQLException {
28 | News news = new News();
29 | news.setArticle(text);
30 | List chunks = splitter.split(news.getArticle());
31 | List vectors = embeddingService.embedAll(chunks);
32 |
33 | List embeddings = new ArrayList<>();
34 | for (int i = 0; i < vectors.size(); i++) {
35 | NewsVector newsVector = new NewsVector();
36 | newsVector.setEmbedding(vectors.get(i).toFloatArray());
37 | newsVector.setChunk(chunks.get(i));
38 | embeddings.add(newsVector);
39 | }
40 |
41 | news.setNews_vector(embeddings);
42 | return CompletableFuture.completedFuture(news);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/news-event-streaming/src/main/java/com/example/news/events/parser/ParserConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.example.news.events.parser;
2 |
3 | import java.util.regex.Pattern;
4 |
5 | import com.fasterxml.jackson.databind.ObjectMapper;
6 | import org.springframework.context.annotation.Bean;
7 | import org.springframework.context.annotation.Configuration;
8 |
9 | @Configuration
10 | public class ParserConfiguration {
11 | @Bean
12 | Pattern sentencePattern() {
13 | // Regular expression pattern to match one or more whitespace characters followed by a period, question mark, or exclamation mark
14 | return Pattern.compile("[.!?]");
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/news-event-streaming/src/main/java/com/example/news/events/parser/Splitter.java:
--------------------------------------------------------------------------------
1 | package com.example.news.events.parser;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 | import java.util.regex.Matcher;
6 | import java.util.regex.Pattern;
7 |
8 | import org.springframework.beans.factory.annotation.Value;
9 | import org.springframework.stereotype.Component;
10 |
11 | @Component
12 | public class Splitter {
13 | private int chunkSize;
14 | private final Pattern pattern;
15 |
16 | public Splitter(@Value("${new.chunking.characters:2000}") int chunkSize,
17 | Pattern pattern) {
18 | this.chunkSize = chunkSize;
19 | this.pattern = pattern;
20 | }
21 |
22 | public List split(String text) {
23 | List chunks = new ArrayList<>();
24 | if (text == null || text.isEmpty()) {
25 | return chunks;
26 | }
27 | Matcher matcher = pattern.matcher(text);
28 | int startPos = 0, endPos;
29 | StringBuilder currentChunk = new StringBuilder();
30 | while (matcher.find()) {
31 | endPos = matcher.start() + 1;
32 | String sentence = text.substring(startPos, endPos);
33 | currentChunk.append(sentence);
34 | startPos = endPos;
35 | if (currentChunk.length() >= chunkSize) {
36 | chunks.add(currentChunk.toString().trim());
37 | currentChunk.setLength(0);
38 | }
39 | }
40 |
41 | // Capture any remaining text after the last match
42 | if (startPos < text.length()) {
43 | currentChunk.append(text.substring(startPos));
44 | }
45 |
46 | if (!currentChunk.isEmpty()) {
47 | chunks.add(currentChunk.toString().trim());
48 | }
49 |
50 | return chunks;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/news-event-streaming/src/main/java/com/example/news/events/producerconsumer/NewsConsumer.java:
--------------------------------------------------------------------------------
1 | package com.example.news.events.producerconsumer;
2 |
3 | import java.sql.SQLException;
4 | import java.time.Duration;
5 | import java.util.Collections;
6 |
7 | import com.example.news.genai.vectorstore.NewsStore;
8 | import com.example.news.model.News;
9 | import lombok.extern.slf4j.Slf4j;
10 | import org.apache.kafka.clients.consumer.ConsumerRecords;
11 | import org.oracle.okafka.clients.consumer.KafkaConsumer;
12 | import org.springframework.beans.factory.annotation.Value;
13 |
14 | @Slf4j
15 | public class NewsConsumer implements OKafkaTask {
16 | private final KafkaConsumer consumer;
17 | private final String newsTopic;
18 | private final NewsStore vectorStore;
19 |
20 |
21 | public NewsConsumer(KafkaConsumer consumer,
22 | @Value("${news.topic.parsed}") String newsTopic, NewsStore vectorStore) {
23 | this.consumer = consumer;
24 | this.newsTopic = newsTopic;
25 | this.vectorStore = vectorStore;
26 | }
27 |
28 |
29 | @Override
30 | public void run() {
31 | consumer.subscribe(Collections.singletonList(newsTopic));
32 |
33 | while (true) {
34 | // Poll a batch of records from the news topic.
35 | ConsumerRecords records = consumer.poll(Duration.ofMillis(200));
36 | if (records.isEmpty()) {
37 | continue;
38 | }
39 | try {
40 | // Add all news records to the
41 | vectorStore.addAll(records, consumer.getDBConnection());
42 | // You may also use auto-commit, or consumer.commitAsync()
43 | consumer.commitSync();
44 | log.info("Committed {} records", records.count());
45 | } catch (Exception e) {
46 | log.error("Error processing news events", e);
47 | handleError();
48 | }
49 | }
50 | }
51 |
52 | private void handleError() {
53 | try {
54 | consumer.getDBConnection().rollback();
55 | } catch (SQLException e) {
56 | log.error("Error rolling back transaction", e);
57 | }
58 |
59 | }
60 |
61 | @Override
62 | public void close() throws Exception {
63 | this.consumer.close();
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/news-event-streaming/src/main/java/com/example/news/events/producerconsumer/OKafkaTask.java:
--------------------------------------------------------------------------------
1 | package com.example.news.events.producerconsumer;
2 |
3 | public interface OKafkaTask extends AutoCloseable, Runnable {
4 | }
5 |
--------------------------------------------------------------------------------
/news-event-streaming/src/main/java/com/example/news/events/producerconsumer/RawNewsProducer.java:
--------------------------------------------------------------------------------
1 | package com.example.news.events.producerconsumer;
2 |
3 | import java.util.List;
4 |
5 | import lombok.extern.slf4j.Slf4j;
6 | import org.apache.kafka.clients.producer.ProducerRecord;
7 | import org.oracle.okafka.clients.producer.KafkaProducer;
8 |
9 | @Slf4j
10 | public class RawNewsProducer implements AutoCloseable {
11 | private final String rawTopic;
12 | private final KafkaProducer producer;
13 |
14 |
15 | public RawNewsProducer(
16 | String rawTopic,
17 | KafkaProducer producer) {
18 | this.rawTopic = rawTopic;
19 | this.producer = producer;
20 | }
21 |
22 | public void send(List news) {
23 | try {
24 | news.parallelStream().forEach(newsItem -> {
25 | ProducerRecord record = new ProducerRecord<>(rawTopic, newsItem);
26 | producer.send(record);
27 | });
28 | log.info("Successfully produced {} records", news.size());
29 | } catch (Exception e) {
30 | throw new RuntimeException(e);
31 | }
32 | }
33 |
34 | @Override
35 | public void close() throws Exception {
36 | this.producer.close();
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/news-event-streaming/src/main/java/com/example/news/events/serde/JSONBDeserializer.java:
--------------------------------------------------------------------------------
1 | package com.example.news.events.serde;
2 |
3 | import java.nio.ByteBuffer;
4 |
5 | import com.oracle.spring.json.jsonb.JSONB;
6 | import org.apache.kafka.common.serialization.Deserializer;
7 |
8 |
9 | /**
10 | * The JSONBDeserializer converts Oracle JSONB byte arrays to Java objects.
11 | * @param deserialization type
12 | */
13 | public class JSONBDeserializer implements Deserializer {
14 | private final JSONB jsonb;
15 | private final Class clazz;
16 |
17 | public JSONBDeserializer(JSONB jsonb, Class clazz) {
18 | this.jsonb = jsonb;
19 | this.clazz = clazz;
20 | }
21 |
22 | @Override
23 | public T deserialize(String s, byte[] bytes) {
24 | return jsonb.fromOSON(ByteBuffer.wrap(bytes), clazz);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/news-event-streaming/src/main/java/com/example/news/events/serde/JSONBSerializer.java:
--------------------------------------------------------------------------------
1 | package com.example.news.events.serde;
2 |
3 | import com.oracle.spring.json.jsonb.JSONB;
4 | import org.apache.kafka.common.serialization.Serializer;
5 |
6 | /**
7 | * The JSONBSerializer converts Java objects to an Oracle JSONB byte array.
8 | * @param serialization type.
9 | */
10 | public class JSONBSerializer implements Serializer {
11 | private final JSONB jsonb;
12 |
13 | public JSONBSerializer(JSONB jsonb) {
14 | this.jsonb = jsonb;
15 | }
16 |
17 | @Override
18 | public byte[] serialize(String s, T obj) {
19 | return jsonb.toOSON(obj);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/news-event-streaming/src/main/java/com/example/news/genai/GenAIConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.example.news.genai;
2 |
3 | import java.io.IOException;
4 | import java.nio.file.Paths;
5 |
6 | import com.example.news.genai.chat.ChatService;
7 | import com.example.news.genai.chat.OCIChatService;
8 | import com.oracle.bmc.auth.BasicAuthenticationDetailsProvider;
9 | import com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider;
10 | import com.oracle.bmc.generativeaiinference.GenerativeAiInference;
11 | import com.oracle.bmc.generativeaiinference.GenerativeAiInferenceClient;
12 | import com.oracle.bmc.generativeaiinference.model.EmbedTextDetails;
13 | import com.oracle.bmc.generativeaiinference.model.OnDemandServingMode;
14 | import org.springframework.beans.factory.annotation.Qualifier;
15 | import org.springframework.beans.factory.annotation.Value;
16 | import org.springframework.context.annotation.Bean;
17 | import org.springframework.context.annotation.Configuration;
18 |
19 | @Configuration
20 | public class GenAIConfiguration {
21 | @Value("${oci.compartment}")
22 | private String compartmentId;
23 |
24 | @Value("${oci.chatModelID}")
25 | private String chatModelId;
26 |
27 | @Value("${oci.embeddingModelID}")
28 | private String embeddingModelId;
29 |
30 | @Bean
31 | public BasicAuthenticationDetailsProvider authProvider() throws IOException {
32 | // Create an OCI authentication provider using the default local
33 | // config file.
34 | return new ConfigFileAuthenticationDetailsProvider(
35 | Paths.get(System.getProperty("user.home"), ".oci", "config")
36 | .toString(),
37 | "DEFAULT"
38 | );
39 | }
40 |
41 | @Bean
42 | public GenerativeAiInference generativeAiInferenceClient(BasicAuthenticationDetailsProvider authProvider) {
43 | return GenerativeAiInferenceClient.builder()
44 | .build(authProvider);
45 | }
46 |
47 | @Bean
48 | @Qualifier("chatServingMode")
49 | public OnDemandServingMode chatModelServingMode() {
50 | // Create a chat service for an On-Demand OCI GenAI chat model.
51 | return OnDemandServingMode.builder()
52 | .modelId(chatModelId)
53 | .build();
54 | }
55 |
56 | @Bean
57 | @Qualifier("embedServingMode")
58 | public OnDemandServingMode embeddingModelServingMode() {
59 | // Create a chat service for an On-Demand OCI GenAI chat model.
60 | return OnDemandServingMode.builder()
61 | .modelId(embeddingModelId)
62 | .build();
63 | }
64 |
65 | @Bean
66 | public EmbedTextDetails.Truncate truncate() {
67 | return EmbedTextDetails.Truncate.End;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/news-event-streaming/src/main/java/com/example/news/genai/chat/ChatService.java:
--------------------------------------------------------------------------------
1 | package com.example.news.genai.chat;
2 |
3 | public interface ChatService {
4 | String chat(String prompt);
5 | }
6 |
--------------------------------------------------------------------------------
/news-event-streaming/src/main/java/com/example/news/genai/embedding/EmbeddingService.java:
--------------------------------------------------------------------------------
1 | package com.example.news.genai.embedding;
2 |
3 | import java.util.Collections;
4 | import java.util.List;
5 |
6 | import oracle.sql.VECTOR;
7 |
8 | public interface EmbeddingService {
9 | List embedAll(List chunks);
10 | default VECTOR embed(String chunk) {
11 | return embedAll(Collections.singletonList(chunk)).getFirst();
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/news-event-streaming/src/main/java/com/example/news/genai/vectorstore/NewsQueryService.java:
--------------------------------------------------------------------------------
1 | package com.example.news.genai.vectorstore;
2 |
3 | import java.sql.Connection;
4 | import java.sql.SQLException;
5 | import java.util.List;
6 | import java.util.Optional;
7 |
8 | import com.example.news.genai.chat.ChatService;
9 | import com.example.news.genai.embedding.EmbeddingService;
10 | import com.example.news.model.NewsDTO;
11 | import com.example.news.model.SearchRequest;
12 | import javax.sql.DataSource;
13 | import oracle.sql.VECTOR;
14 | import org.springframework.stereotype.Component;
15 |
16 | @Component
17 | public class NewsQueryService {
18 | private final NewsStore newsStore;
19 | private final EmbeddingService embeddingService;
20 | private final ChatService chatService;
21 | private final DataSource dataSource;
22 |
23 | private static final String summaryTemplate = """
24 | You are an assistant for summarizing news articles.
25 | Summarize the the following retrieved news article, and do not use any outside information.
26 | Use one paragraph maximum and keep the answer concise without extra detail.
27 | Article to summarize: {%s}
28 | Summary:
29 | """;
30 |
31 | private static final String notFoundMessage = "I'm sorry, but I couldn't find any relevant articles for the given query.";
32 |
33 | public NewsQueryService(NewsStore newsStore, EmbeddingService embeddingService, ChatService chatService, DataSource dataSource) {
34 | this.newsStore = newsStore;
35 | this.embeddingService = embeddingService;
36 | this.chatService = chatService;
37 | this.dataSource = dataSource;
38 | }
39 |
40 | public List search(SearchRequest searchRequest) throws SQLException {
41 | try (Connection conn = dataSource.getConnection()) {
42 | VECTOR vector = embeddingService.embed(searchRequest.input());
43 |
44 | return newsStore.search(
45 | searchRequest,
46 | vector,
47 | conn
48 | );
49 | }
50 | }
51 |
52 | public String summarize(SearchRequest summarizeRequest) throws SQLException {
53 | List results = search(summarizeRequest);
54 | return summarize(results.stream().findFirst());
55 | }
56 |
57 | public String summarize(String id) throws SQLException {
58 | try (Connection conn = dataSource.getConnection()){
59 | return summarize(newsStore.findByID(id, conn));
60 | }
61 | }
62 |
63 | private String summarize(Optional newsDTO) {
64 | return newsDTO.map(news -> chatService.chat(summaryTemplate.formatted(news.article())))
65 | .orElse(notFoundMessage);
66 | }
67 |
68 | public void cleanup() throws SQLException {
69 | try (Connection conn = dataSource.getConnection()) {
70 | newsStore.cleanup(conn);
71 | }
72 | }
73 |
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/news-event-streaming/src/main/java/com/example/news/genai/vectorstore/VectorDataAdapter.java:
--------------------------------------------------------------------------------
1 | package com.example.news.genai.vectorstore;
2 |
3 | import java.sql.SQLException;
4 | import java.util.List;
5 |
6 | import oracle.sql.VECTOR;
7 | import org.springframework.stereotype.Component;
8 |
9 | @Component
10 | public class VectorDataAdapter {
11 | public VECTOR toVECTOR(List e) throws SQLException {
12 | double[] vector = e.stream()
13 | .mapToDouble(Float::floatValue)
14 | .toArray();
15 | return VECTOR.ofFloat64Values(vector);
16 | }
17 |
18 | /**
19 | * Normalizes a vector, converting it to a unit vector with length 1.
20 | * @param v Vector to normalize.
21 | * @return The normalized vector.
22 | */
23 | private float[] normalize(float[] v) {
24 | double squaredSum = 0d;
25 |
26 | for (float e : v) {
27 | squaredSum += e * e;
28 | }
29 |
30 | final float magnitude = (float) Math.sqrt(squaredSum);
31 |
32 | if (magnitude > 0) {
33 | final float multiplier = 1f / magnitude;
34 | final int length = v.length;
35 | for (int i = 0; i < length; i++) {
36 | v[i] *= multiplier;
37 | }
38 | }
39 |
40 | return v;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/news-event-streaming/src/main/java/com/example/news/model/News.java:
--------------------------------------------------------------------------------
1 | package com.example.news.model;
2 |
3 | import java.util.List;
4 | import java.util.UUID;
5 |
6 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
7 | import lombok.Data;
8 |
9 | @Data
10 | @JsonIgnoreProperties(ignoreUnknown = true)
11 | public class News {
12 | private String _id = UUID.randomUUID().toString();
13 | private String article;
14 |
15 | private List news_vector;
16 | }
17 |
--------------------------------------------------------------------------------
/news-event-streaming/src/main/java/com/example/news/model/NewsDTO.java:
--------------------------------------------------------------------------------
1 | package com.example.news.model;
2 |
3 | public record NewsDTO (String id, String article) {
4 | }
5 |
--------------------------------------------------------------------------------
/news-event-streaming/src/main/java/com/example/news/model/NewsVector.java:
--------------------------------------------------------------------------------
1 | package com.example.news.model;
2 |
3 | import java.util.UUID;
4 |
5 | import lombok.Data;
6 |
7 | @Data
8 | public class NewsVector {
9 | private String id = UUID.randomUUID().toString();
10 | private String chunk;
11 | private float[] embedding;
12 | }
13 |
--------------------------------------------------------------------------------
/news-event-streaming/src/main/java/com/example/news/model/SearchRequest.java:
--------------------------------------------------------------------------------
1 | package com.example.news.model;
2 |
3 | import java.util.Objects;
4 |
5 | public record SearchRequest(String input,
6 | Double minScore) {
7 |
8 | public Double getMinScore() {
9 | return Objects.requireNonNullElse(minScore, 0.5);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/news-event-streaming/src/main/resources/application.yaml:
--------------------------------------------------------------------------------
1 |
2 | okafka:
3 | ojdbcPath: ${OJDBC_PATH}
4 | bootstrapServers: ${BOOTSTRAP_SERVERS:localhost:1521}
5 | serviceName: ${OKAFKA_SERVICE_NAME:freepdb1}
6 | securityProtocol: ${OKAFKA_SECURITY_PROTOCOL:PLAINTEXT}
7 |
8 | news:
9 | threads:
10 | newsParserConsumerProducer: 3
11 | newsConsumer: 3
12 | topic:
13 | raw: news_raw
14 | parsed: news_parsed
15 |
16 | oci:
17 | chatModelID: ${OCI_CHAT_MODEL_ID}
18 | embeddingModelID: ${OCI_EMBEDDING_MODEL_ID}
19 | compartment: ${OCI_COMPARTMENT_ID}
20 | chat:
21 | preambleOverride: ""
22 | temperature: 1.0
23 | frequencyPenalty: 0.0
24 | presencePenalty: 0.0
25 | maxTokens: 4000
26 | topP: 0.75
27 | topK: 0
28 | inferenceRequestType: COHERE
29 |
30 | spring:
31 | datasource:
32 | username: testuser
33 | password: testpwd
34 | url: jdbc:oracle:thin:@localhost:1521/freepdb1
35 | driver-class-name: oracle.jdbc.OracleDriver
36 | type: oracle.ucp.jdbc.PoolDataSource
37 | oracleucp:
38 | initial-pool-size: 1
39 | min-pool-size: 1
40 | max-pool-size: 30
41 | connection-pool-name: OracleDatabaseTest
42 | connection-factory-class-name: oracle.jdbc.pool.OracleDataSource
43 | threads:
44 | virtual:
45 | enabled: true # Turn on Java Virtual Threads
46 |
47 | logging:
48 | level:
49 | org:
50 | apache:
51 | kafka: WARN
52 | oracle:
53 | okafka: WARN
--------------------------------------------------------------------------------
/news-event-streaming/src/test/java/com/example/news/ParserTest.java:
--------------------------------------------------------------------------------
1 | package com.example.news;
2 |
3 | import com.example.news.events.parser.Parser;
4 | import com.example.news.events.parser.ParserConfiguration;
5 | import com.example.news.events.parser.Splitter;
6 | import com.example.news.genai.GenAIConfiguration;
7 | import com.example.news.genai.embedding.OCIEmbeddingService;
8 | import com.example.news.genai.vectorstore.VectorDataAdapter;
9 | import com.example.news.model.News;
10 | import org.junit.jupiter.api.Test;
11 | import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
12 | import org.springframework.beans.factory.annotation.Autowired;
13 | import org.springframework.boot.test.context.SpringBootTest;
14 |
15 | import static com.example.news.Utils.readFile;
16 | import static org.assertj.core.api.Assertions.assertThat;
17 |
18 |
19 | @SpringBootTest(classes = {
20 | Parser.class,
21 | OCIEmbeddingService.class,
22 | Splitter.class,
23 | VectorDataAdapter.class,
24 | ParserConfiguration.class,
25 | GenAIConfiguration.class
26 | })
27 | // This integration test uses OCI GenAI services.
28 | // To run this test, set the following environment variables using your OCI compartment,
29 | // and Cohere model IDs for chat and embedding.
30 | @EnabledIfEnvironmentVariable(named = "OCI_COMPARTMENT", matches = ".+")
31 | @EnabledIfEnvironmentVariable(named = "OCI_EMBEDDING_MODEL_ID", matches = ".+")
32 | public class ParserTest {
33 |
34 | @Autowired
35 | private Parser parser;
36 |
37 | @Test
38 | void parseArticles() throws Exception {
39 | String s = readFile("one-record.json");
40 | News news = parser.parseAsync(s.substring(1, s.length()-1)).get();
41 | assertThat(news).isNotNull();
42 | assertThat(news.getNews_vector().size()).isGreaterThan(1);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/news-event-streaming/src/test/java/com/example/news/Utils.java:
--------------------------------------------------------------------------------
1 | package com.example.news;
2 |
3 | import java.io.File;
4 | import java.io.IOException;
5 | import java.nio.file.Files;
6 |
7 | import org.springframework.core.io.ClassPathResource;
8 | import org.springframework.core.io.Resource;
9 |
10 | public class Utils {
11 | public static String readFile(String fileName) {
12 | try {
13 | Resource resource = new ClassPathResource(fileName);
14 | File file = resource.getFile();
15 | return new String(Files.readAllBytes(file.toPath()));
16 | } catch (IOException e) {
17 | throw new RuntimeException(e);
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/news-event-streaming/src/test/resources/cleanup.sql:
--------------------------------------------------------------------------------
1 |
2 | -- Clean up TxEventQ
3 | begin
4 | dbms_aqadm.stop_queue(
5 | queue_name => 'news_raw'
6 | );
7 | dbms_aqadm.drop_transactional_event_queue(
8 | queue_name => 'news_raw'
9 | );
10 |
11 | dbms_aqadm.stop_queue(
12 | queue_name => 'news_parsed'
13 | );
14 | dbms_aqadm.drop_transactional_event_queue(
15 | queue_name => 'news_parsed'
16 | );
17 | end;
18 | /
19 |
20 | -- Clean up database tables
21 | drop view news_dv;
22 | drop index news_vector_ivf_idx;
23 | drop table news_vector cascade constraints ;
24 | drop table news;
25 |
--------------------------------------------------------------------------------
/news-event-streaming/src/test/resources/news-schema.sql:
--------------------------------------------------------------------------------
1 | -- News articles
2 | create table if not exists news (
3 | news_id varchar2(36) default sys_guid() primary key,
4 | article clob
5 | );
6 |
7 | -- News Vectors, many-to-one with news
8 | create table if not exists news_vector (
9 | id varchar2(36) default sys_guid() primary key,
10 | news_id varchar2(36) ,
11 | chunk varchar2(2500),
12 | embedding vector(1024, FLOAT64) annotations(Distance 'COSINE', IndexType 'IVF'),
13 | constraint fk_news_vector foreign key (news_id)
14 | references news(news_id) on delete cascade
15 | );
16 |
17 | -- Vector index for News Vectors
18 | create vector index if not exists news_vector_ivf_idx on news_vector (embedding) organization neighbor partitions
19 | distance COSINE
20 | with target accuracy 90
21 | parameters (type IVF, neighbor partitions 10);
22 |
23 | -- JSON Relational Duality View for the News Schema
24 | create or replace force editionable json relational duality view news_dv
25 | as
26 | news @insert @update @delete {
27 | _id : news_id
28 | article
29 | news_vector @insert @update @delete
30 | @link (to : [NEWS_ID]) {
31 | id
32 | chunk
33 | embedding
34 | }
35 | }
36 | /
--------------------------------------------------------------------------------
/news-event-streaming/src/test/resources/ojdbc.properties:
--------------------------------------------------------------------------------
1 | user = testuser
2 | password = testpwd
3 |
--------------------------------------------------------------------------------
/news-event-streaming/src/test/resources/one-record.json:
--------------------------------------------------------------------------------
1 | ["(CNN)The world's biggest and most powerful physics experiment is taking place as you read this. The Large Hadron Collider (LHC), a particle accelerator and the largest machine in the world, is ready for action following a two-year shutdown. After problems that delayed the restart in March, scientists at the European Organization for Nuclear Research (CERN) completed final tests, enabling the first beams to start circulating Sunday inside the LHC's 17 mile (27 km) ring. \"Operating accelerators for the benefit of the physics community is what CERN's here for,\" CERN Director-General Rolf Heuer said on the organization's website. \"Today, CERN's heart beats once more to the rhythm of the LHC.\" The LHC generates up to 600 million particles per second, with a beam circulating for 10 hours, traveling more than 6 billion miles (more than 10 billion kilometers) -- the distance from Earth to Neptune and back again. At near light-speed, a proton in the LHC makes 11,245 circuits per second. It took thousands of scientists, engineers and technicians decades to devise and build the particle accelerator, housed in a tunnel between Lake Geneva and the Jura mountain range. The purpose of the lengthy project is to recreate the conditions that existed moments after the \"Big Bang\" -- the scientific theory said to explain the creation of the universe. By replicating the energy density and temperature, scientists hope to uncover how the universe evolved. Our current, limited, knowledge is based on what's called The Standard Model of particle physics. \"But we know that this model is not complete,\" Dr. Mike Lamont, operations group leader at the LHC, told CNN in March. The burning questions that remain include the origin of mass and why some particles are very heavy, while others have no mass at all; a unified description of all the fundamental forces such as gravity; and uncovering dark matter and dark energy, since visible matter accounts for only 4 percent of the universe. The LHC could also question the idea that the universe is only made of matter, despite the theory that antimatter must have been produced in the same amounts at the time of the Big Bang. CERN says the energies achievable by the LHC have only ever been found in nature. The machine alone costs approximately three billion euros (about $3.3 billion), paid for by member countries of CERN and contributions by non-member nations. The organization also asserts that its guidelines for the protection of the environment and personnel comply with standards set by Swiss and French laws and a European Council Directive. Scientists and physics enthusiasts will be waiting with bated breath as the LHC ventures into the great unknown. \"After two years of effort, the LHC is in great shape,\" said CERN Director for Accelerators and Technology, Frédérick Bordry. \"But the most important step is still to come when we increase the energy of the beams to new record levels.\" Peter Shadbolt contributed to this report.The Large Hadron Collider (LHC) begins again after a two-year shutdown .\nThe restart was delayed in March .The Large Hadron Collider (LHC) begins again after a two-year shutdown .\nThe restart was delayed in March ."]
--------------------------------------------------------------------------------
/news-event-streaming/src/test/resources/testuser.sql:
--------------------------------------------------------------------------------
1 | alter session set container = freepdb1;
2 |
3 | -- Create app user. You may wish to modify tablespace as needed.
4 | create user if not exists testuser identified by testpwd;
5 | grant create session to testuser;
6 | grant connect, resource, unlimited tablespace to testuser;
7 |
8 | -- Grants for the Kafka Java Client for Oracle Database Transactional Event Queues
9 | grant aq_user_role to testuser;
10 | grant execute on dbms_aq to testuser;
11 | grant execute on dbms_aqadm to testuser;
12 | grant select on gv_$session to testuser;
13 | grant select on v_$session to testuser;
14 | grant select on gv_$instance to testuser;
15 | grant select on gv_$listener_network to testuser;
16 | grant select on sys.dba_rsrc_plan_directives to testuser;
17 | grant select on gv_$pdbs to testuser;
18 | grant select on user_queue_partition_assignment_table to testuser;
19 | exec dbms_aqadm.grant_priv_for_rm_plan('testuser');
20 | commit;
21 |
22 |
--------------------------------------------------------------------------------
/oracle-database-kafka-apis/README.md:
--------------------------------------------------------------------------------
1 | # Oracle Database Kafka APIs
2 |
3 | The following articles describe using Kafka Java APIs with Oracle Database Transactional Event Queues:
4 |
5 | - [Transactional Messaging with the Kafka Java Client for Oracle Database Transactional Event Queues](https://medium.com/@anders.swanson.93/using-transactional-kafka-apis-with-oracle-database-70f58598a176)
6 | - [Produce and consume messages with the Kafka Java Client for Oracle Database Transactional Event Queues](https://medium.com/@anders.swanson.93/seamlessly-stream-data-with-kafka-apis-for-oracle-79db9ce02dc0)
7 |
8 | ### Running the Oracle Database Kafka API tests
9 |
10 | The tests in this package demonstrate using the Kafka Java Client for Oracle Database Transactional Event Queues to produce and consume messages. The tests use a containerized Oracle Database instance with Testcontainers to run locally on a Docker-compatible environment with Java 21.
11 |
12 | Prerequisites:
13 | - Java 21
14 | - Maven
15 | - Docker
16 |
17 | Once your docker environment is configured, you can run the integration tests with maven:
18 |
19 |
20 | 1. To demonstrate producing and consuming messages from a Transactional Event Queue topic using Kafka APIs, run the OKafkaExampleIT test.
21 | ```shell
22 | mvn integration-test -Dit.test=OKafkaExampleIT
23 | ```
24 |
25 | 2. To demonstrate a transactional producer, run the TransactionalProduceIT test. With a transactional producer, messages are only produced if the producer successfully commits the transaction.
26 | ```shell
27 | mvn integration-test -Dit.test=TransactionalProduceIT
28 | ```
29 |
30 | 3. To demonstrate a transactional consumer, run the TransactionalConsumeIT test.
31 | ```shell
32 | mvn integration-test -Dit.test=TransactionalConsumeIT
33 | ```
34 |
35 | To run all the Transactional Event Queue Kafka API tests, run `mvn integration-test`.
--------------------------------------------------------------------------------
/oracle-database-kafka-apis/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | com.example
8 | oracle-database-java-samples
9 | 1.0.0-SNAPSHOT
10 |
11 |
12 | oracle-database-kafka-apis
13 | oracle-kafka-apis
14 | Oracle Kafka API Example
15 | 1.0.0-SNAPSHOT
16 |
17 |
18 |
19 |
20 | com.oracle.database.messaging
21 | okafka
22 | ${okafka.version}
23 |
24 |
25 |
26 |
27 | org.springframework.boot
28 | spring-boot-starter-test
29 | test
30 |
31 |
32 | org.springframework.boot
33 | spring-boot-testcontainers
34 | test
35 |
36 |
37 | org.testcontainers
38 | oracle-free
39 | ${testcontainers.version}
40 | test
41 |
42 |
43 | org.testcontainers
44 | junit-jupiter
45 | test
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | org.apache.maven.plugins
54 | maven-failsafe-plugin
55 |
56 |
57 | **/*IT.java
58 |
59 |
60 |
61 |
62 |
63 | integration-test
64 | verify
65 |
66 |
67 |
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/oracle-database-kafka-apis/src/main/java/com/example/AdminUtil.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import java.util.Collections;
4 | import java.util.Properties;
5 | import java.util.concurrent.ExecutionException;
6 |
7 | import org.apache.kafka.clients.admin.Admin;
8 | import org.apache.kafka.clients.admin.NewTopic;
9 | import org.apache.kafka.common.errors.TopicExistsException;
10 | import org.oracle.okafka.clients.admin.AdminClient;
11 |
12 | public class AdminUtil {
13 | public static void createTopicIfNotExists(Properties okafkaProperties,
14 | NewTopic newTopic) {
15 | try (Admin admin = AdminClient.create(okafkaProperties)) {
16 | admin.createTopics(Collections.singletonList(newTopic))
17 | .all()
18 | .get();
19 | } catch (ExecutionException | InterruptedException e) {
20 | if (e.getCause() instanceof TopicExistsException) {
21 | System.out.println("Topic already exists, skipping creation");
22 | } else {
23 | throw new RuntimeException(e);
24 | }
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/oracle-database-kafka-apis/src/main/java/com/example/OKafkaProperties.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import java.util.Objects;
4 | import java.util.Properties;
5 |
6 | public class OKafkaProperties {
7 | public static Properties getLocalConnectionProps(String ojdbcPropertiesFile,
8 | Integer port) {
9 | Properties props = new Properties();
10 | // We use the default PDB for Oracle Database 23ai.
11 | props.put("oracle.service.name", "freepdb1");
12 | // The localhost connection uses PLAINTEXT.
13 | props.put("security.protocol", "PLAINTEXT");
14 | props.put("bootstrap.servers", String.format("localhost:%d",
15 | Objects.requireNonNullElse(port, 1521)));
16 | props.put("oracle.net.tns_admin", ojdbcPropertiesFile);
17 | return props;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/oracle-database-kafka-apis/src/main/java/com/example/SampleConsumer.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import java.time.Duration;
4 | import java.util.List;
5 |
6 | import org.apache.kafka.clients.consumer.Consumer;
7 | import org.apache.kafka.clients.consumer.ConsumerRecords;
8 |
9 | public class SampleConsumer implements Runnable, AutoCloseable {
10 | private final Consumer consumer;
11 | private final String topic;
12 | private final int expectedMessages;
13 |
14 | public SampleConsumer(Consumer consumer, String topic, int expectedMessages) {
15 | this.consumer = consumer;
16 | this.topic = topic;
17 | this.expectedMessages = expectedMessages;
18 | }
19 |
20 | @Override
21 | public void run() {
22 | consumer.subscribe(List.of(topic));
23 | int consumedRecords = 0;
24 | while (true) {
25 | ConsumerRecords records = consumer.poll(Duration.ofMillis(100));
26 | System.out.println("Consumed records: " + records.count());
27 | consumedRecords += records.count();
28 | if (consumedRecords >= expectedMessages) {
29 | return;
30 | }
31 | processRecords(records);
32 | // Commit records when done processing.
33 | consumer.commitAsync();
34 | }
35 | }
36 |
37 | private void processRecords(ConsumerRecords records) {
38 | // Application implementation of record processing.
39 | }
40 |
41 | @Override
42 | public void close() throws Exception {
43 | if (consumer != null) {
44 | consumer.close();
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/oracle-database-kafka-apis/src/main/java/com/example/SampleProducer.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import java.util.concurrent.atomic.AtomicInteger;
4 | import java.util.stream.Stream;
5 |
6 | import org.apache.kafka.clients.producer.Producer;
7 | import org.apache.kafka.clients.producer.ProducerRecord;
8 |
9 | public class SampleProducer implements Runnable, AutoCloseable {
10 | private final Producer producer;
11 | private final String topic;
12 | private final Stream inputs;
13 |
14 | public SampleProducer(Producer producer, String topic, Stream inputs) {
15 | this.producer = producer;
16 | this.topic = topic;
17 | this.inputs = inputs;
18 | }
19 |
20 | @Override
21 | public void run() {
22 | AtomicInteger i = new AtomicInteger();
23 | inputs.forEach(t -> {
24 | i.incrementAndGet();
25 | ProducerRecord record = new ProducerRecord<>(topic, t);
26 | producer.send(record);
27 | });
28 | System.out.printf("Produced %d records\n", i.get());
29 | }
30 |
31 | @Override
32 | public void close() throws Exception {
33 | if (this.producer != null) {
34 | producer.close();
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/oracle-database-kafka-apis/src/main/java/com/example/TransactionalConsumer.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import java.sql.Connection;
4 | import java.sql.PreparedStatement;
5 | import java.sql.SQLException;
6 | import java.time.Duration;
7 | import java.util.List;
8 |
9 | import org.apache.kafka.clients.consumer.ConsumerRecord;
10 | import org.apache.kafka.clients.consumer.ConsumerRecords;
11 | import org.oracle.okafka.clients.consumer.KafkaConsumer;
12 |
13 | public class TransactionalConsumer implements AutoCloseable, Runnable {
14 | private static final String insertRecord = """
15 | insert into records (data, idx) values (?, ?)
16 | """;
17 |
18 | private final KafkaConsumer consumer;
19 | private final List topics;
20 | private final int expectedMessages;
21 | private final boolean isCommitted;
22 |
23 | public TransactionalConsumer(KafkaConsumer consumer,
24 | List topics, int expectedMessages,
25 | boolean isCommitted) {
26 | this.consumer = consumer;
27 | this.topics = topics;
28 | this.expectedMessages = expectedMessages;
29 | this.isCommitted = isCommitted;
30 | }
31 |
32 | @Override
33 | public void run() {
34 | int idx = 0;
35 | this.consumer.subscribe(topics);
36 | while (true) {
37 | ConsumerRecords records = consumer.poll(Duration.ofMillis(100));
38 | System.out.println("Consumed records: " + records.count());
39 | Connection conn = consumer.getDBConnection();
40 | for (ConsumerRecord record : records) {
41 | idx++;
42 | processRecord(record, idx, conn);
43 | }
44 | if (!isCommitted) {
45 | // Since auto-commit is disabled, transactions will not be committed to the DB
46 | System.out.println("Unexpected error processing records. Aborting transaction!");
47 | return;
48 | }
49 | // Blocking commit on the current batch of records. For non-blocking, use commitAsync()
50 | consumer.commitSync();
51 | if (idx >= expectedMessages) {
52 | System.out.printf("Committed %d records\n", records.count());
53 | return;
54 | }
55 | }
56 | }
57 |
58 | private void processRecord(ConsumerRecord record, int idx, Connection conn) {
59 | try (PreparedStatement ps = conn.prepareStatement(insertRecord)) {
60 | ps.setString(1, record.value());
61 | ps.setInt(2, idx);
62 | ps.executeUpdate();
63 | } catch (SQLException e) {
64 | throw new RuntimeException(e);
65 | }
66 | }
67 |
68 |
69 | @Override
70 | public void close() throws Exception {
71 | if (this.consumer != null) {
72 | this.consumer.close();
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/oracle-database-kafka-apis/src/main/java/com/example/TransationalProducer.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import java.sql.Connection;
4 | import java.sql.PreparedStatement;
5 | import java.sql.SQLException;
6 | import java.util.Iterator;
7 | import java.util.stream.Stream;
8 |
9 | import org.apache.kafka.clients.producer.ProducerRecord;
10 | import org.oracle.okafka.clients.producer.KafkaProducer;
11 |
12 | /**
13 | * TransactionalProducer uses the org.oracle.okafka.clients.producer.KafkaProducer database connection
14 | * to write records to a table.
15 | *
16 | * After limit records have been produced, the TransactionalProducer simulates a processing error,
17 | * and aborts the current producer batch.
18 | */
19 | public class TransationalProducer implements AutoCloseable {
20 | private static final String insertRecord = """
21 | insert into records (data, idx) values (?, ?)
22 | """;
23 |
24 | private final KafkaProducer producer;
25 | private final String topic;
26 |
27 | // Simulate an message processing error after limit messages have been produced
28 | private final int limit;
29 |
30 | public TransationalProducer(KafkaProducer producer,
31 | String topic,
32 | int limit) {
33 | this.producer = producer;
34 | this.topic = topic;
35 | this.limit = limit;
36 | // Initialize transactional producer
37 | this.producer. initTransactions();
38 | }
39 |
40 | public void produce(Stream inputs) {
41 | // Being producer transaction
42 | producer.beginTransaction();
43 | Connection conn = producer.getDBConnection();
44 | int idx = 0;
45 | Iterator records = inputs.iterator();
46 | while (records.hasNext()) {
47 | String record = records.next();
48 | if (++idx >= limit) {
49 | System.out.printf("Produced %d records\n", idx);
50 | System.out.println("Unexpected error processing records. Aborting transaction!");
51 | // Abort the database transaction on error, cancelling the effective batch
52 | producer.abortTransaction();
53 | return;
54 | }
55 |
56 | ProducerRecord pr = new ProducerRecord<>(topic, Integer.toString(idx), record);
57 | producer.send(pr);
58 | persistRecord(record, idx, conn);
59 | }
60 | System.out.printf("Produced %d records\n", idx);
61 | // commit the transaction
62 | producer.commitTransaction();
63 | }
64 |
65 | private void persistRecord(String record, int idx, Connection conn) {
66 | try (PreparedStatement ps = conn.prepareStatement(insertRecord)) {
67 | ps.setString(1, record);
68 | ps.setInt(2, idx);
69 | ps.executeUpdate();
70 | } catch (SQLException e) {
71 | throw new RuntimeException(e);
72 | }
73 | }
74 |
75 | @Override
76 | public void close() throws Exception {
77 | if (this.producer != null) {
78 | producer.close();
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/oracle-database-kafka-apis/src/main/java/com/example/okafka/App.java:
--------------------------------------------------------------------------------
1 | package com.example.okafka;
2 |
3 | public class App {
4 | public static void main(String[] args) {}
5 | }
6 |
--------------------------------------------------------------------------------
/oracle-database-kafka-apis/src/test/resources/ojdbc.properties:
--------------------------------------------------------------------------------
1 | user = testuser
2 | password = Welcome123#
3 |
--------------------------------------------------------------------------------
/oracle-database-kafka-apis/src/test/resources/okafka.sql:
--------------------------------------------------------------------------------
1 | alter session set container=freepdb1;
2 |
3 | -- In our example, this user is created by Testcontainers
4 | -- create user TESTUSER identified by Welcome123#;
5 |
6 | -- You may wish to modify the unlimited tablespace grant as appropriate.
7 | grant resource, connect, unlimited tablespace to TESTUSER;
8 | grant aq_user_role to TESTUSER;
9 | grant execute on dbms_aq to TESTUSER;
10 | grant execute on dbms_aqadm to TESTUSER;
11 | grant select on gv_$session to TESTUSER;
12 | grant select on v_$session to TESTUSER;
13 | grant select on gv_$instance to TESTUSER;
14 | grant select on gv_$listener_network to TESTUSER;
15 | grant select on SYS.DBA_RSRC_PLAN_DIRECTIVES to TESTUSER;
16 | grant select on gv_$pdbs to TESTUSER;
17 | grant select on user_queue_partition_assignment_table to TESTUSER;
18 | exec dbms_aqadm.GRANT_PRIV_FOR_RM_PLAN('TESTUSER');
19 | commit;
20 |
--------------------------------------------------------------------------------
/oracle-database-kafka-apis/src/test/resources/weather_sensor_data.txt:
--------------------------------------------------------------------------------
1 | 2024-07-03 00:00:00, WeatherPro1, 18.5°C, 72%, UV 0
2 | 2024-07-03 00:30:00, WeatherPro1, 18.2°C, 73%, UV 0
3 | 2024-07-03 01:00:00, WeatherPro1, 17.9°C, 74%, UV 0
4 | 2024-07-03 01:30:00, WeatherPro1, 17.7°C, 75%, UV 0
5 | 2024-07-03 02:00:00, WeatherPro1, 17.5°C, 76%, UV 0
6 | 2024-07-03 02:30:00, WeatherPro1, 17.3°C, 77%, UV 0
7 | 2024-07-03 03:00:00, WeatherPro1, 17.1°C, 78%, UV 0
8 | 2024-07-03 03:30:00, WeatherPro1, 16.9°C, 79%, UV 0
9 | 2024-07-03 04:00:00, WeatherPro1, 16.7°C, 80%, UV 0
10 | 2024-07-03 04:30:00, WeatherPro1, 16.5°C, 81%, UV 0
11 | 2024-07-03 05:00:00, WeatherPro1, 16.3°C, 82%, UV 0
12 | 2024-07-03 05:30:00, WeatherPro1, 16.5°C, 81%, UV 1
13 | 2024-07-03 06:00:00, WeatherPro1, 17.0°C, 79%, UV 1
14 | 2024-07-03 06:30:00, WeatherPro1, 17.8°C, 76%, UV 2
15 | 2024-07-03 07:00:00, WeatherPro1, 18.5°C, 73%, UV 2
16 | 2024-07-03 07:30:00, WeatherPro1, 19.2°C, 70%, UV 3
17 | 2024-07-03 08:00:00, WeatherPro1, 20.0°C, 67%, UV 4
18 | 2024-07-03 08:30:00, WeatherPro1, 20.8°C, 64%, UV 5
19 | 2024-07-03 09:00:00, WeatherPro1, 21.5°C, 61%, UV 6
20 | 2024-07-03 09:30:00, WeatherPro1, 22.2°C, 58%, UV 7
21 | 2024-07-03 10:00:00, WeatherPro1, 23.0°C, 55%, UV 8
22 | 2024-07-03 10:30:00, WeatherPro1, 23.8°C, 52%, UV 9
23 | 2024-07-03 11:00:00, WeatherPro1, 24.5°C, 49%, UV 10
24 | 2024-07-03 11:30:00, WeatherPro1, 25.2°C, 47%, UV 10
25 | 2024-07-03 12:00:00, WeatherPro1, 25.8°C, 45%, UV 11
26 | 2024-07-03 12:30:00, WeatherPro1, 26.3°C, 44%, UV 11
27 | 2024-07-03 13:00:00, WeatherPro1, 26.7°C, 43%, UV 11
28 | 2024-07-03 13:30:00, WeatherPro1, 27.0°C, 42%, UV 10
29 | 2024-07-03 14:00:00, WeatherPro1, 27.2°C, 41%, UV 10
30 | 2024-07-03 14:30:00, WeatherPro1, 27.3°C, 41%, UV 9
31 | 2024-07-03 15:00:00, WeatherPro1, 27.2°C, 42%, UV 8
32 | 2024-07-03 15:30:00, WeatherPro1, 27.0°C, 43%, UV 7
33 | 2024-07-03 16:00:00, WeatherPro1, 26.7°C, 44%, UV 6
34 | 2024-07-03 16:30:00, WeatherPro1, 26.3°C, 46%, UV 5
35 | 2024-07-03 17:00:00, WeatherPro1, 25.8°C, 48%, UV 4
36 | 2024-07-03 17:30:00, WeatherPro1, 25.2°C, 50%, UV 3
37 | 2024-07-03 18:00:00, WeatherPro1, 24.5°C, 53%, UV 2
38 | 2024-07-03 18:30:00, WeatherPro1, 23.8°C, 56%, UV 1
39 | 2024-07-03 19:00:00, WeatherPro1, 23.0°C, 59%, UV 1
40 | 2024-07-03 19:30:00, WeatherPro1, 22.2°C, 62%, UV 0
41 | 2024-07-03 20:00:00, WeatherPro1, 21.5°C, 65%, UV 0
42 | 2024-07-03 20:30:00, WeatherPro1, 20.8°C, 68%, UV 0
43 | 2024-07-03 21:00:00, WeatherPro1, 20.0°C, 71%, UV 0
44 | 2024-07-03 21:30:00, WeatherPro1, 19.3°C, 74%, UV 0
45 | 2024-07-03 22:00:00, WeatherPro1, 18.7°C, 76%, UV 0
46 | 2024-07-03 22:30:00, WeatherPro1, 18.2°C, 78%, UV 0
47 | 2024-07-03 23:00:00, WeatherPro1, 17.8°C, 79%, UV 0
48 | 2024-07-03 23:30:00, WeatherPro1, 17.5°C, 80%, UV 0
49 | 2024-07-04 00:00:00, WeatherPro1, 17.3°C, 81%, UV 0
50 | 2024-07-04 00:30:00, WeatherPro1, 17.1°C, 82%, UV 0
--------------------------------------------------------------------------------
/spring-boot-dynamic-property-source/README.md:
--------------------------------------------------------------------------------
1 | # Spring Dynamic Property Source Example
2 |
3 | This example application demonstrates how to use the Spring Boot EnumerablePropertySource and EnvironmentPostProcessor classes to load properties dynamically at application startup from an external source (in this case an Oracle Database server).
4 |
5 | The use of dynamic property sources allows applications to manage and rotate properties from external services, such as secure key vaults, file servers, or databases for increased application configurability and security.
6 |
7 | For an in-depth walkthough, see the following guide: [Dynamically load Spring properties from external repositories](https://medium.com/@anders.swanson.93/dynamically-load-spring-properties-from-external-locations-3ef644b42035).
8 | . See also [Spring Cloud Config](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/) for an example of externalized configuration in distributed systems.
9 |
10 | ## Prerequisites
11 |
12 | - Java 21+, Maven
13 |
14 | ## Run the sample
15 |
16 | The sample provides an all-in-one test leveraging Testcontainers and Oracle Database to do the following:
17 |
18 | 1. Start and configure a database server using Testcontainers
19 | 2. Load properties from the database and verify them at application startup
20 | 3. Modify a property, and reload a bean to verify the database property refresh is working.
21 |
22 | You can run the test like so, from the project's root directory:
23 |
24 | `mvn test`
25 |
26 | You should see output similar to the following, indicating properties were successfully loaded from the database, updated, and reloaded into Spring Beans:
27 |
28 | ```
29 | Starting Property Source Test
30 | Value of 'property1': initial value
31 | Updating Property 'property1'
32 | Reloading PropertyService Bean
33 | New value of 'property1': updated
34 | ```
35 |
--------------------------------------------------------------------------------
/spring-boot-dynamic-property-source/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | com.example
7 | oracle-database-java-samples
8 | 1.0.0-SNAPSHOT
9 |
10 |
11 | spring-boot-dynamic-property-source
12 | 1.0.0-SNAPSHOT
13 | spring-boot-dynamic-property-source
14 | Example implementation of dynamic property sources with Spring Boot
15 |
16 |
17 |
18 | org.springframework.boot
19 | spring-boot-starter-data-jdbc
20 |
21 |
22 | com.oracle.database.spring
23 | oracle-spring-boot-starter-ucp
24 | ${oracle.starters.version}
25 |
26 |
27 |
28 |
29 | org.springframework.boot
30 | spring-boot-starter-test
31 | test
32 |
33 |
34 | org.springframework.boot
35 | spring-boot-testcontainers
36 | test
37 |
38 |
39 | org.testcontainers
40 | oracle-free
41 | ${testcontainers.version}
42 | test
43 |
44 |
45 | org.testcontainers
46 | junit-jupiter
47 | test
48 |
49 |
50 |
51 |
52 |
53 |
54 | org.springframework.boot
55 | spring-boot-maven-plugin
56 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/spring-boot-dynamic-property-source/src/main/java/com/example/DatabaseProperties.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import java.time.Duration;
4 | import java.util.List;
5 |
6 | import org.springframework.boot.context.properties.ConfigurationProperties;
7 | import org.springframework.stereotype.Component;
8 |
9 | @ConfigurationProperties(prefix = DatabaseProperties.PREFIX)
10 | @Component
11 | public class DatabaseProperties {
12 | public static final String PREFIX = "database";
13 |
14 | private Duration propertyRefreshInterval;
15 |
16 | private List propertySources;
17 |
18 | public static class PropertySource {
19 | private String table;
20 |
21 | public String getTable() {
22 | return table;
23 | }
24 |
25 | public void setTable(String table) {
26 | this.table = table;
27 | }
28 | }
29 |
30 | public Duration getPropertyRefreshInterval() {
31 | return propertyRefreshInterval;
32 | }
33 |
34 | public void setPropertyRefreshInterval(Duration propertyRefreshInterval) {
35 | this.propertyRefreshInterval = propertyRefreshInterval;
36 | }
37 |
38 | public List getPropertySources() {
39 | return propertySources;
40 | }
41 |
42 | public void setPropertySources(List propertySources) {
43 | this.propertySources = propertySources;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/spring-boot-dynamic-property-source/src/main/java/com/example/DatabasePropertySource.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import org.springframework.core.env.EnumerablePropertySource;
4 |
5 | /**
6 | * A custom {@link org.springframework.core.env.PropertySource} implementation that retrieves properties from a database.
7 | * This class extends {@link EnumerablePropertySource} and delegates property access to an underlying
8 | * {@link DatabasePropertyLoader}.
9 | *
10 | * @see DatabasePropertyLoader
11 | */
12 | public class DatabasePropertySource extends EnumerablePropertySource {
13 | public DatabasePropertySource(String name, DatabasePropertyLoader source) {
14 | super(name, source);
15 | }
16 |
17 | @Override
18 | public String[] getPropertyNames() {
19 | return source.getPropertyNames();
20 | }
21 |
22 | @Override
23 | public Object getProperty(String name) {
24 | return source.getProperty(name);
25 | }
26 |
27 | @Override
28 | public boolean containsProperty(String name) {
29 | return source.containsProperty(name);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/spring-boot-dynamic-property-source/src/main/java/com/example/Property.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | public record Property(String key,
4 | String value) {
5 | }
6 |
--------------------------------------------------------------------------------
/spring-boot-dynamic-property-source/src/main/java/com/example/PropertyService.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import org.springframework.beans.factory.annotation.Value;
4 | import org.springframework.jdbc.core.JdbcTemplate;
5 | import org.springframework.stereotype.Component;
6 |
7 | @Component
8 | public class PropertyService {
9 | private static final String PROPERTY_TABLE = "spring_property";
10 | private static final String UPDATE_PROPERTY = """
11 | update %s set value = ? where key = ?
12 | """.formatted(PROPERTY_TABLE);
13 | private final JdbcTemplate jdbcTemplate;
14 |
15 | /**
16 | * The value of 'property1' is dynamically loaded from the database during
17 | * startup, using Spring's property loader mechanism.
18 | */
19 | @Value("${property1}")
20 | private String property1;
21 |
22 | public PropertyService(JdbcTemplate jdbcTemplate) {
23 | this.jdbcTemplate = jdbcTemplate;
24 | }
25 |
26 | /**
27 | * Used to demonstrate dynamic property loading, using the value of
28 | * 'property1' loaded from the database property source.
29 | * @return the current value of property1.
30 | */
31 | public String getProperty1() {
32 | return property1;
33 | }
34 |
35 | /**
36 | * Updates an existing property in the database.
37 | *
38 | * @param property the updated property object containing the new value and key
39 | */
40 | public void updateProperty(Property property) {
41 | jdbcTemplate.update(UPDATE_PROPERTY, property.value(), property.key());
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/spring-boot-dynamic-property-source/src/main/java/com/example/SampleApp.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 |
6 | @SpringBootApplication
7 | public class SampleApp {
8 | public static void main(String[] args) {
9 | SpringApplication.run(SampleApp.class, args);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/spring-boot-dynamic-property-source/src/main/resources/META-INF/spring.factories:
--------------------------------------------------------------------------------
1 | org.springframework.boot.env.EnvironmentPostProcessor=com.example.DatabaseEnvironmentPostProcessor
--------------------------------------------------------------------------------
/spring-boot-dynamic-property-source/src/main/resources/application.yaml:
--------------------------------------------------------------------------------
1 | spring:
2 | datasource:
3 | username: ${USERNAME}
4 | password: ${PASSWORD}
5 | url: ${JDBC_URL}
6 |
7 | # Set these to use UCP over Hikari.
8 | driver-class-name: oracle.jdbc.OracleDriver
9 | type: oracle.ucp.jdbc.PoolDataSource
10 | oracleucp:
11 | initial-pool-size: 1
12 | min-pool-size: 1
13 | max-pool-size: 30
14 | connection-pool-name: UCPSampleApplication
15 | connection-factory-class-name: oracle.jdbc.pool.OracleDataSource
16 |
17 |
18 | # Load properties from the spring_property database table every 1000ms
19 | database:
20 | property-sources:
21 | - table: spring_property
22 | property-refresh-interval: 1000ms
--------------------------------------------------------------------------------
/spring-boot-dynamic-property-source/src/test/resources/init.sql:
--------------------------------------------------------------------------------
1 | create table spring_property (
2 | key varchar2(255) primary key not null ,
3 | value varchar2(255) not null
4 | );
5 |
6 | insert into spring_property (key, value) values ('property1', 'initial value');
7 |
--------------------------------------------------------------------------------
/spring-boot-jms-example/README.md:
--------------------------------------------------------------------------------
1 | # Spring Java Message Service (JMS) Example
2 |
3 | This example demonstrates how to write a pub/sub application using [Spring JMS](https://spring.io/guides/gs/messaging-jms) and [Oracle Database Transactional Event Queues](https://docs.oracle.com/en/database/oracle/oracle-database/23/adque/aq-introduction.html). If you're unfamiliar with Transactional Event Queues, it is a high-throughput, distributed asynchronous messaging system built into Oracle Database. The integration of Transactional Event Queues with Spring JMS provides a simple interface for rapid development of messaging applications.
4 |
5 | The [Spring Boot Starter for AQ/JMS](https://github.com/oracle/spring-cloud-oracle/tree/main/database/starters/oracle-spring-boot-starter-aqjms) used in the example pulls in all necessary dependencies to use Spring JMS with Oracle Database Transactional Event Queues, requiring minimal configuration.
6 |
7 | For an in-depth walkthough of Spring JMS with Transactional Event Queues, see the following guide: [Use JMS for asynchronous messaging in Spring Boot](https://medium.com/@anders.swanson.93/use-jms-for-asynchronous-messaging-in-spring-boot-d67f8349c7c4).
8 |
9 | ## Prerequisites
10 |
11 | - Java 21+, Maven
12 | - Docker compatible environment
13 |
14 | ## Run the sample
15 |
16 | The sample provides an all-in-one test leveraging Testcontainers and Oracle Database to do the following:
17 |
18 | 1. Start and configure a database server using Testcontainers
19 | 2. Produce several messages to a Transactional Event Queue using an autowired JMSTemplate.
20 | 3. Verify all messages are consumed by the JMSListener.
21 |
22 | You can run the test like so, from the project's root directory:
23 |
24 | `mvn test`
25 |
26 | You should see output similar to the following:
27 |
28 | ```
29 | Starting Spring Boot JMS Example
30 | Produced 5 messages to the queue via JMS.
31 | Waiting for consumer to finish processing messages...
32 | Received message: test message 1
33 | Received message: test message 2
34 | Received message: test message 3
35 | Received message: test message 4
36 | Received message: test message 5
37 | Consumer finished.
38 | ```
--------------------------------------------------------------------------------
/spring-boot-jms-example/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | com.example
7 | oracle-database-java-samples
8 | 1.0.0-SNAPSHOT
9 |
10 |
11 | spring-boot-jms-example
12 | 1.0.0-SNAPSHOT
13 | spring-boot-jms-example
14 | Example implementation Spring JMS with Oracle Database Transactional Event Queues
15 |
16 |
17 |
18 | org.springframework.boot
19 | spring-boot-starter-data-jdbc
20 |
21 |
22 | com.oracle.database.spring
23 | oracle-spring-boot-starter-aqjms
24 | ${oracle.starters.version}
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | org.springframework.boot
37 | spring-boot-starter-test
38 | test
39 |
40 |
41 | org.springframework.boot
42 | spring-boot-testcontainers
43 | test
44 |
45 |
46 | org.testcontainers
47 | oracle-free
48 | ${testcontainers.version}
49 | test
50 |
51 |
52 | org.testcontainers
53 | junit-jupiter
54 | test
55 |
56 |
57 |
58 |
59 |
60 |
61 | org.springframework.boot
62 | spring-boot-maven-plugin
63 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/spring-boot-jms-example/src/main/java/com/example/Consumer.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import java.util.concurrent.CountDownLatch;
4 |
5 | import org.springframework.beans.factory.annotation.Value;
6 | import org.springframework.jms.annotation.JmsListener;
7 | import org.springframework.stereotype.Component;
8 |
9 | @Component
10 | public class Consumer {
11 | private final CountDownLatch latch;
12 |
13 | public Consumer(@Value("${txeventq.consumer.numMessages:5}") int numMessages) {
14 | latch = new CountDownLatch(numMessages);
15 | }
16 |
17 | @JmsListener(destination = "${txeventq.queue.name:testqueue}", id = "sampleConsumer")
18 | public void receiveMessage(String message) {
19 | System.out.printf("Received message: %s%n", message);
20 | latch.countDown();
21 | }
22 |
23 | public void await() throws InterruptedException {
24 | latch.await();
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/spring-boot-jms-example/src/main/java/com/example/Producer.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import org.springframework.beans.factory.annotation.Value;
4 | import org.springframework.jms.core.JmsTemplate;
5 | import org.springframework.stereotype.Component;
6 |
7 | @Component
8 | public class Producer {
9 | private final JmsTemplate jmsTemplate;
10 | private final String queueName;
11 |
12 | public Producer(JmsTemplate jmsTemplate,
13 | @Value("${txeventq.queue.name:testqueue}") String queueName) {
14 | this.jmsTemplate = jmsTemplate;
15 | this.queueName = queueName;
16 | }
17 |
18 | public void enqueue(String message) {
19 | jmsTemplate.convertAndSend(queueName, message);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/spring-boot-jms-example/src/main/java/com/example/SampleApp.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import javax.sql.DataSource;
4 |
5 | import jakarta.jms.ConnectionFactory;
6 | import jakarta.jms.JMSException;
7 | import oracle.jakarta.jms.AQjmsFactory;
8 | import org.springframework.boot.SpringApplication;
9 | import org.springframework.boot.autoconfigure.SpringBootApplication;
10 | import org.springframework.context.annotation.Bean;
11 |
12 | @SpringBootApplication
13 | public class SampleApp {
14 | public static void main(String[] args) {
15 | SpringApplication.run(SampleApp.class, args);
16 | }
17 |
18 | @Bean
19 | public ConnectionFactory aqJmsConnectionFactory(DataSource ds) throws JMSException {
20 | return AQjmsFactory.getConnectionFactory(ds);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/spring-boot-jms-example/src/main/resources/application.yaml:
--------------------------------------------------------------------------------
1 | spring:
2 | datasource:
3 | username: ${USERNAME}
4 | password: ${PASSWORD}
5 | url: ${JDBC_URL}
6 |
7 | # Set these to use UCP over Hikari.
8 | driver-class-name: oracle.jdbc.OracleDriver
9 | type: oracle.ucp.jdbc.PoolDataSource
10 | oracleucp:
11 | initial-pool-size: 1
12 | min-pool-size: 1
13 | max-pool-size: 30
14 | connection-pool-name: UCPSampleApplication
15 | connection-factory-class-name: oracle.jdbc.pool.OracleDataSource
16 |
--------------------------------------------------------------------------------
/spring-boot-jms-example/src/test/java/com/example/SpringJmsTest.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import java.time.Duration;
4 |
5 | import org.junit.jupiter.api.BeforeAll;
6 | import org.junit.jupiter.api.Test;
7 | import org.springframework.beans.factory.annotation.Autowired;
8 | import org.springframework.boot.test.context.SpringBootTest;
9 | import org.springframework.jms.config.JmsListenerEndpointRegistry;
10 | import org.testcontainers.junit.jupiter.Testcontainers;
11 | import org.testcontainers.oracle.OracleContainer;
12 | import org.testcontainers.utility.MountableFile;
13 |
14 | import static org.junit.jupiter.api.Assertions.assertTimeout;
15 |
16 | @Testcontainers
17 | @SpringBootTest
18 | public class SpringJmsTest {
19 | /**
20 | * Use a containerized Oracle Database instance for testing.
21 | */
22 | static OracleContainer oracleContainer = new OracleContainer("gvenzl/oracle-free:23.7-slim-faststart")
23 | .withStartupTimeout(Duration.ofMinutes(5))
24 | .withUsername("testuser")
25 | .withPassword(("testpwd"));
26 |
27 | /**
28 | * Set up the test environment:
29 | * 1. configure Spring Properties to use the test database.
30 | * 2. run a SQL script to configure the test database for our JMS example.
31 | */
32 | @BeforeAll
33 | static void setUp() throws Exception {
34 | oracleContainer.start();
35 |
36 | // Dynamically configure Spring Boot properties to use the Testcontainers database.
37 | System.setProperty("JDBC_URL", oracleContainer.getJdbcUrl());
38 | System.setProperty("USERNAME", oracleContainer.getUsername());
39 | System.setProperty("PASSWORD", oracleContainer.getPassword());
40 |
41 | // Configures the test database, granting the test user access to TxEventQ, creating and starting a queue for JMS.
42 | oracleContainer.copyFileToContainer(MountableFile.forClasspathResource("init.sql"), "/tmp/init.sql");
43 | oracleContainer.execInContainer("sqlplus", "sys / as sysdba", "@/tmp/init.sql");
44 | }
45 |
46 | @Autowired
47 | private Producer producer;
48 |
49 | @Autowired
50 | private Consumer consumer;
51 |
52 | @Autowired
53 | private JmsListenerEndpointRegistry jmsListenerEndpointRegistry;
54 |
55 | @Test
56 | void springBootJMSExample() {
57 | System.out.println("Starting Spring Boot JMS Example");
58 |
59 | for (int i = 1; i < 6; i++) {
60 | producer.enqueue("test message %d".formatted(i));
61 | }
62 | System.out.println("Produced 5 messages to the queue via JMS.");
63 |
64 | System.out.println("Waiting for consumer to finish processing messages...");
65 | assertTimeout(Duration.ofSeconds(5), () -> {
66 | consumer.await();
67 | });
68 |
69 | // Do a clean shutdown of the JMS listener.
70 | jmsListenerEndpointRegistry.getListenerContainer("sampleConsumer").stop();
71 | System.out.println("Consumer finished.");
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/spring-boot-jms-example/src/test/resources/init.sql:
--------------------------------------------------------------------------------
1 | -- Set as appropriate for your database.
2 | alter session set container = freepdb1;
3 |
4 | -- Configure testuser with the necessary privileges to use Transactional Event Queues.
5 | grant aq_user_role to testuser;
6 | grant execute on dbms_aq to testuser;
7 | grant execute on dbms_aqadm to testuser;
8 | grant execute ON dbms_aqin TO testuser;
9 | grant execute ON dbms_aqjms TO testuser;
10 |
11 | -- Create a Transactional Event Queue
12 | begin
13 | -- See https://docs.oracle.com/en/database/oracle/oracle-database/23/arpls/DBMS_AQADM.html#GUID-93B0FF90-5045-4437-A9C4-B7541BEBE573
14 | -- For documentation on creating Transactional Event Queues.
15 | dbms_aqadm.create_transactional_event_queue(
16 | queue_name => 'testuser.testqueue',
17 | -- Payload can be RAW, JSON, DBMS_AQADM.JMS_TYPE, or an object type.
18 | -- Default is DBMS_AQADM.JMS_TYPE.
19 | queue_payload_type => DBMS_AQADM.JMS_TYPE,
20 | -- FALSE means queues can only have one consumer for each message. This is the default.
21 | -- TRUE means queues created in the table can have multiple consumers for each message.
22 | multiple_consumers => false
23 | );
24 |
25 | -- Start the queue
26 | dbms_aqadm.start_queue(
27 | queue_name => 'testuser.testqueue'
28 | );
29 | end;
30 | /
--------------------------------------------------------------------------------
/spring-data-mongodb-oracle-api/README.md:
--------------------------------------------------------------------------------
1 | # Using Oracle Database's MongoDB API with Spring Data MongoDB
2 |
3 | MongoDB is a popular, open-source NoSQL database management program that stores data in a flexible, JSON-like format, making it ideal for handling large, unstructured, or semi-structured data.
4 |
5 | The [Oracle Database MongoDB API](https://docs.oracle.com/en/database/oracle/mongodb-api/) translates the MongoDB client requests into SQL statements that are run by Oracle Database, allowing developers to easily use MongoDB clients with Oracle Database. The Oracle Database MongoDB API works best with release 22.3 or later of [Oracle REST Data Services (ORDS)](https://www.oracle.com/database/technologies/appdev/rest.html)
6 |
7 | ### What's included in this module?
8 |
9 | This module implements a basic MongoRepository for a Student document, and demonstrates Oracle-MongoDB wire compatibility in the [OracleMongoDBTest](./src/test/java/com/example/mongodb/OracleMongoDBTest.java) test class.
10 |
11 | Example operations for CRUD, querying, and MongoTemplate are included - each being identical to if you were _actually_ using MongoDB.
12 |
13 | ### How can I run the test?
14 |
15 | The test requires an Oracle Database instance with ORDS installed. The easiest way to do this is to configure an Oracle Autonomous Database for JSON processing, as described in the [Using Oracle Database API for MongoDB document](https://docs.oracle.com/en/cloud/paas/autonomous-database/serverless/adbsb/mongo-using-oracle-database-api-mongodb.html#GUID-8321D7A6-9DBD-44F8-8C16-1B1FBE66AC56)
16 |
17 | Once you have your database configured, set the following environment variables:
18 | - `DB_USERNAME` (the database username you'll use to connect)
19 | - `DB_PASSWORD` (the database password you'll use to connect)
20 | - `DB_URI` (the URI of your database instance, e.g., `my-db.adb.my-region.oraclecloudapps.com`)
21 |
22 | Then, you can run the test using Maven:
23 |
24 | ```
25 | mvn test
26 | ```
27 |
28 | You should see output similar to the following. I recommend reading the [test class](./src/test/java/com/example/mongodb/OracleMongoDBTest.java) to get an idea of what it's doing. Note that _no_ Oracle-specific APIs are being invoked by the client!
29 | ```bash
30 | [INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 4.715 s -- in com.example.mongodb.OracleMongoDBTest
31 | [INFO]
32 | [INFO] Results:
33 | [INFO]
34 | [INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0
35 | [INFO]
36 | [INFO] ------------------------------------------------------------------------
37 | [INFO] BUILD SUCCESS
38 | [INFO] ------------------------------------------------------------------------
39 | [INFO] Total time: 7.045 s
40 | [INFO] Finished at: 2025-04-02T11:00:10-07:00
41 | [INFO] ------------------------------------------------------------------------
42 | ```
--------------------------------------------------------------------------------
/spring-data-mongodb-oracle-api/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 |
8 | com.example
9 | oracle-database-java-samples
10 | 1.0.0-SNAPSHOT
11 |
12 |
13 | spring-data-mongodb-oracle-api
14 | Sample :: Oracle Database MongoDB API with Spring Boot
15 | Spring Data MongoDB using Oracle Database's MongoDB API
16 | 1.0.0-SNAPSHOT
17 |
18 |
19 |
20 | org.springframework.boot
21 | spring-boot-starter-data-mongodb
22 |
23 |
24 |
25 |
26 | org.springframework.boot
27 | spring-boot-starter-test
28 | test
29 |
30 |
31 | org.springframework.boot
32 | spring-boot-testcontainers
33 | test
34 |
35 |
36 | org.testcontainers
37 | oracle-free
38 | ${testcontainers.version}
39 | test
40 |
41 |
42 | org.testcontainers
43 | junit-jupiter
44 | test
45 |
46 |
47 |
--------------------------------------------------------------------------------
/spring-data-mongodb-oracle-api/src/main/java/com/example/mongodb/Application.java:
--------------------------------------------------------------------------------
1 | package com.example.mongodb;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 |
6 | @SpringBootApplication
7 | public class Application {
8 | public static void main(String[] args) {
9 | SpringApplication.run(Application.class, args);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/spring-data-mongodb-oracle-api/src/main/java/com/example/mongodb/Student.java:
--------------------------------------------------------------------------------
1 | package com.example.mongodb;
2 |
3 | import org.springframework.data.annotation.Id;
4 | import org.springframework.data.mongodb.core.mapping.Document;
5 | import org.springframework.data.mongodb.core.mapping.Field;
6 |
7 | @Document(collection = "students")
8 | public class Student {
9 | @Id
10 | private String _id;
11 |
12 | @Field(name = "full_name")
13 | private String name;
14 |
15 | private String email;
16 |
17 | @Field(name = "course_count")
18 | private Integer courseCount;
19 |
20 | private Integer credits;
21 |
22 | private Double gpa;
23 |
24 | public Student() {}
25 |
26 | public Student(String id, String name, String email, Integer courseCount, Integer credits, Double gpa) {
27 | this._id = id;
28 | this.name = name;
29 | this.email = email;
30 | this.courseCount = courseCount;
31 | this.credits = credits;
32 | this.gpa = gpa;
33 | }
34 |
35 | public String get_id() {
36 | return _id;
37 | }
38 |
39 | public void set_id(String _id) {
40 | this._id = _id;
41 | }
42 |
43 | public String getName() {
44 | return name;
45 | }
46 |
47 | public void setName(String name) {
48 | this.name = name;
49 | }
50 |
51 | public String getEmail() {
52 | return email;
53 | }
54 |
55 | public void setEmail(String email) {
56 | this.email = email;
57 | }
58 |
59 | public Integer getCourseCount() {
60 | return courseCount;
61 | }
62 |
63 | public void setCourseCount(Integer courseCount) {
64 | this.courseCount = courseCount;
65 | }
66 |
67 | public Integer getCredits() {
68 | return credits;
69 | }
70 |
71 | public void setCredits(Integer credits) {
72 | this.credits = credits;
73 | }
74 |
75 | public Double getGpa() {
76 | return gpa;
77 | }
78 |
79 | public void setGpa(Double gpa) {
80 | this.gpa = gpa;
81 | }
82 |
83 | @Override
84 | public final boolean equals(Object o) {
85 | if (!(o instanceof Student student)) return false;
86 |
87 | return get_id().equals(student.get_id()) && getEmail().equals(student.getEmail());
88 | }
89 |
90 | @Override
91 | public int hashCode() {
92 | int result = get_id().hashCode();
93 | result = 31 * result + getEmail().hashCode();
94 | return result;
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/spring-data-mongodb-oracle-api/src/main/java/com/example/mongodb/StudentRepository.java:
--------------------------------------------------------------------------------
1 | package com.example.mongodb;
2 |
3 | import java.util.List;
4 | import java.util.Optional;
5 |
6 | import org.springframework.data.mongodb.repository.MongoRepository;
7 | import org.springframework.data.mongodb.repository.Query;
8 |
9 | public interface StudentRepository extends MongoRepository {
10 | Optional findByEmail(String email);
11 |
12 | @Query("{ 'course_count': { $gte: ?0, $lte: ?1 } }")
13 | List findByCourseCountInRange(int minCourses, int maxCourses);
14 |
15 | @Query(value = "{ credits: { $gte: ?0 } }", fields = "{ 'email': 1 }")
16 | List findStudentEmailsWhereCreditsGreaterThan(int minCredits);
17 | }
18 |
--------------------------------------------------------------------------------
/spring-data-mongodb-oracle-api/src/main/resources/application.yaml:
--------------------------------------------------------------------------------
1 | spring:
2 | data:
3 | mongodb:
4 | uri: mongodb://${DB_USERNAME}:${DB_PASSWORD}@${DB_URI}:27017/${DB_USERNAME}?authMechanism=PLAIN&authSource=$external&ssl=true&retryWrites=false&loadBalanced=true
5 | database: admin
6 |
--------------------------------------------------------------------------------
/spring-jpa/README.md:
--------------------------------------------------------------------------------
1 | # Learn Spring JPA By Example with Oracle Database Free
2 |
3 | Spring JPA (Java Persistence API) provides an abstraction layer over JPA using ORM (Object Relational Mapping). Spring JPA simplifies database interactions by abstracting common operations to simple Java objects and annotations.
4 |
5 | This module includes idiomatic examples of [Spring JPA](https://spring.io/projects/spring-data-jpa) with [Oracle Database Free](https://medium.com/@anders.swanson.93/oracle-database-23ai-free-11abf827ab37).
6 |
7 | ### Basic JPA Entity Example
8 |
9 | The [com.example.relationships](./src/main/java/com/example) package defines a basic JPA entity and repository, using the [student](./src/test/resources/student.sql) schema.
10 |
11 | The [SpringJPATest](./src/test/java/com/example/SpringJPATest.java) class provides examples of basic JPA repository usage.
12 |
13 | ### JPA Entity Relationships
14 |
15 | The [com.example.relationships](./src/main/java/com/example/relationships) package defines JPA entities for the [movie schema](./src/test/resources/movie.sql) with one-to-one, one-to-many, and many-to-many relationships.
16 |
17 | The [JPARelationshipsTest](./src/test/java/com/example/JPARelationshipsTest.java) class provides examples on managing JPA relationships using repositories.
18 |
19 | ### Paging, Sorting, and Filtering JPA Entities
20 |
21 | The [com.example.paging](./src/main/java/com/example/paging) package defines JPA entities for the [author schema](./src/test/resources/paging.sql), including repositories with custom JPA methods that utilize paging, sorting, and filtering.
22 |
23 | The [PagingSortingFilteringTest](./src/test/java/com/example/PagingSortingFilteringTest.java) class provides examples for paging, sorting, and filtering using the Author and Books repositories. Example of JPA @Query annotations, JPA query methods, and specification queries (introduced in JPA 2.0) are included.
24 |
--------------------------------------------------------------------------------
/spring-jpa/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | com.example
7 | oracle-database-java-samples
8 | 1.0.0-SNAPSHOT
9 |
10 |
11 | spring-jpa
12 | 1.0.0-SNAPSHOT
13 | spring-jpa
14 | Example of Spring JPA with Oracle Database
15 |
16 |
17 |
18 | org.projectlombok
19 | lombok
20 | provided
21 |
22 |
23 | org.springframework.boot
24 | spring-boot-starter
25 |
26 |
27 | org.springframework.boot
28 | spring-boot-starter-data-jpa
29 |
30 |
31 | com.oracle.database.spring
32 | oracle-spring-boot-starter-ucp
33 | ${oracle.starters.version}
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | org.springframework.boot
46 | spring-boot-starter-test
47 | test
48 |
49 |
50 | org.springframework.boot
51 | spring-boot-testcontainers
52 | test
53 |
54 |
55 | org.testcontainers
56 | oracle-free
57 | ${testcontainers.version}
58 | test
59 |
60 |
61 | org.testcontainers
62 | junit-jupiter
63 | test
64 |
65 |
66 |
67 |
68 |
69 |
70 | org.springframework.boot
71 | spring-boot-maven-plugin
72 |
73 |
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/spring-jpa/src/main/java/com/example/Application.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 |
6 | @SpringBootApplication
7 | public class Application {
8 | public static void main(String[] args) {
9 | SpringApplication.run(Application.class, args);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/spring-jpa/src/main/java/com/example/model/Student.java:
--------------------------------------------------------------------------------
1 | package com.example.model;
2 |
3 | import java.util.UUID;
4 |
5 | import jakarta.persistence.Column;
6 | import jakarta.persistence.Entity;
7 | import jakarta.persistence.GeneratedValue;
8 | import jakarta.persistence.GenerationType;
9 | import jakarta.persistence.Id;
10 | import jakarta.persistence.Table;
11 |
12 | @Entity
13 | @Table(name = "STUDENT")
14 | public class Student {
15 | // The primary key of the default
16 | @Id
17 | // Denotes values generated by the database, in this case a UUID.
18 | @GeneratedValue(strategy = GenerationType.UUID)
19 | @Column(name = "id")
20 | private UUID id;
21 | @Column(name = "first_name") // Customize column names as appropriate for Java objects.
22 | private String firstName;
23 | @Column(name = "last_name")
24 | private String lastName;
25 | private String email;
26 | private String major;
27 | private double credits;
28 | private double gpa;
29 |
30 | public Student() {}
31 |
32 | public Student(String firstName, String lastName, String email, String major, double credits, double gpa) {
33 | this.firstName = firstName;
34 | this.lastName = lastName;
35 | this.email = email;
36 | this.major = major;
37 | this.credits = credits;
38 | this.gpa = gpa;
39 | }
40 |
41 | public UUID getId() {
42 | return id;
43 | }
44 |
45 | public void setId(UUID id) {
46 | this.id = id;
47 | }
48 |
49 | public String getFirstName() {
50 | return firstName;
51 | }
52 |
53 | public void setFirstName(String firstName) {
54 | this.firstName = firstName;
55 | }
56 |
57 | public String getLastName() {
58 | return lastName;
59 | }
60 |
61 | public void setLastName(String lastName) {
62 | this.lastName = lastName;
63 | }
64 |
65 | public String getEmail() {
66 | return email;
67 | }
68 |
69 | public void setEmail(String email) {
70 | this.email = email;
71 | }
72 |
73 | public String getMajor() {
74 | return major;
75 | }
76 |
77 | public void setMajor(String major) {
78 | this.major = major;
79 | }
80 |
81 | public double getCredits() {
82 | return credits;
83 | }
84 |
85 | public void setCredits(double credits) {
86 | this.credits = credits;
87 | }
88 |
89 | public double getGpa() {
90 | return gpa;
91 | }
92 |
93 | public void setGpa(double gpa) {
94 | this.gpa = gpa;
95 | }
96 | }
97 |
98 |
--------------------------------------------------------------------------------
/spring-jpa/src/main/java/com/example/paging/model/Author.java:
--------------------------------------------------------------------------------
1 | package com.example.paging.model;
2 |
3 | import java.util.List;
4 |
5 | import jakarta.persistence.CascadeType;
6 | import jakarta.persistence.Column;
7 | import jakarta.persistence.Entity;
8 | import jakarta.persistence.GeneratedValue;
9 | import jakarta.persistence.GenerationType;
10 | import jakarta.persistence.Id;
11 | import jakarta.persistence.OneToMany;
12 | import jakarta.persistence.Table;
13 | import lombok.Getter;
14 | import lombok.Setter;
15 |
16 | @Entity
17 | @Table(name = "author")
18 | @Getter
19 | @Setter
20 | public class Author {
21 | @Id
22 | @GeneratedValue(strategy = GenerationType.IDENTITY)
23 | @Column(name = "author_id")
24 | private Long authorId;
25 |
26 | @Column(nullable = false, length = 100)
27 | private String name;
28 |
29 | @Column(name = "birth_year")
30 | private Integer birthYear;
31 |
32 | private String bio;
33 |
34 | @OneToMany(
35 | mappedBy = "author",
36 | cascade = CascadeType.ALL,
37 | orphanRemoval = true
38 | )
39 | private List books;
40 | }
--------------------------------------------------------------------------------
/spring-jpa/src/main/java/com/example/paging/model/Book.java:
--------------------------------------------------------------------------------
1 | package com.example.paging.model;
2 |
3 | import jakarta.persistence.Column;
4 | import jakarta.persistence.Entity;
5 | import jakarta.persistence.FetchType;
6 | import jakarta.persistence.GeneratedValue;
7 | import jakarta.persistence.GenerationType;
8 | import jakarta.persistence.Id;
9 | import jakarta.persistence.JoinColumn;
10 | import jakarta.persistence.ManyToOne;
11 | import jakarta.persistence.Table;
12 | import lombok.Getter;
13 | import lombok.Setter;
14 |
15 | @Entity
16 | @Table(name = "book")
17 | @Getter
18 | @Setter
19 | public class Book {
20 | @Id
21 | @GeneratedValue(strategy = GenerationType.IDENTITY)
22 | @Column(name = "book_id")
23 | private Long bookId;
24 |
25 | @Column(nullable = false, length = 200)
26 | private String title;
27 |
28 | private Integer pages;
29 |
30 | @Column(name = "published_year")
31 | private Integer publishedYear;
32 |
33 | @ManyToOne(fetch = FetchType.LAZY)
34 | @JoinColumn(name = "author_id", nullable = false)
35 | private Author author;
36 |
37 | @ManyToOne(fetch = FetchType.LAZY)
38 | @JoinColumn(name = "genre_id")
39 | private BookGenre genre;
40 | }
41 |
--------------------------------------------------------------------------------
/spring-jpa/src/main/java/com/example/paging/model/BookGenre.java:
--------------------------------------------------------------------------------
1 | package com.example.paging.model;
2 |
3 | import jakarta.persistence.Column;
4 | import jakarta.persistence.Entity;
5 | import jakarta.persistence.GeneratedValue;
6 | import jakarta.persistence.GenerationType;
7 | import jakarta.persistence.Id;
8 | import jakarta.persistence.Table;
9 | import lombok.Getter;
10 | import lombok.Setter;
11 |
12 | @Entity
13 | @Table(name = "book_genre")
14 | @Getter
15 | @Setter
16 | public class BookGenre {
17 | @Id
18 | @GeneratedValue(strategy = GenerationType.IDENTITY)
19 | @Column(name = "genre_id")
20 | private Long genreId;
21 |
22 | @Column(name = "genre_name", nullable = false, unique = true, length = 50)
23 | private String genreName;
24 | }
25 |
--------------------------------------------------------------------------------
/spring-jpa/src/main/java/com/example/paging/repository/AuthorRepository.java:
--------------------------------------------------------------------------------
1 | package com.example.paging.repository;
2 |
3 | import java.util.List;
4 |
5 | import com.example.paging.model.Author;
6 | import org.springframework.data.domain.Page;
7 | import org.springframework.data.domain.Pageable;
8 | import org.springframework.data.jpa.repository.JpaRepository;
9 | import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
10 | import org.springframework.data.jpa.repository.Query;
11 | import org.springframework.data.repository.query.Param;
12 |
13 | public interface AuthorRepository extends JpaRepository, JpaSpecificationExecutor {
14 |
15 | @Query("select a from Author a where size(a.books) > :bookCount")
16 | List findAuthorsWithMoreThanXBooksJPASyntax(@Param("bookCount") int bookCount);
17 |
18 | @Query("""
19 | select distinct a from Author a
20 | join Book b on b.author.authorId = a.authorId
21 | group by a
22 | having count(b) > :bookCount
23 | """)
24 | List findAuthorsWithMoreThanXBooksSQLSyntax(@Param("bookCount") int bookCount);
25 |
26 |
27 | @Query("""
28 | select distinct a from Author a
29 | join Book b on b.author.authorId = a.authorId
30 | where a.name like 'T%'
31 | group by a
32 | having count(b) > :bookCount
33 | order by a.birthYear
34 | """)
35 | Page moreThanXBooksNameStartsWithTSortedByBirthYear(
36 | @Param("bookCount") int bookCount,
37 | Pageable pageable);
38 | }
39 |
--------------------------------------------------------------------------------
/spring-jpa/src/main/java/com/example/paging/repository/AuthorSpecifications.java:
--------------------------------------------------------------------------------
1 | package com.example.paging.repository;
2 |
3 | import com.example.paging.model.Author;
4 | import com.example.paging.model.Book;
5 | import jakarta.persistence.criteria.Join;
6 | import jakarta.persistence.criteria.JoinType;
7 | import org.springframework.data.jpa.domain.Specification;
8 |
9 | public final class AuthorSpecifications {
10 | public static Specification hasBookCountGreaterThanX(Long countThreshold) {
11 | return (root, query, criteriaBuilder) -> {
12 | Join bookJoin = root.join("books", JoinType.LEFT);
13 | query.groupBy(root);
14 | query.having(criteriaBuilder.gt(criteriaBuilder.count(bookJoin), countThreshold));
15 |
16 | return query.getRestriction();
17 | };
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/spring-jpa/src/main/java/com/example/paging/repository/BookGenreRepository.java:
--------------------------------------------------------------------------------
1 | package com.example.paging.repository;
2 |
3 | import com.example.paging.model.BookGenre;
4 | import org.springframework.data.jpa.repository.JpaRepository;
5 |
6 | public interface BookGenreRepository extends JpaRepository {
7 | }
8 |
--------------------------------------------------------------------------------
/spring-jpa/src/main/java/com/example/paging/repository/BookRepository.java:
--------------------------------------------------------------------------------
1 | package com.example.paging.repository;
2 |
3 | import java.util.List;
4 | import java.util.Optional;
5 |
6 | import com.example.paging.model.Author;
7 | import com.example.paging.model.Book;
8 | import com.example.paging.model.BookGenre;
9 | import org.springframework.data.domain.Page;
10 | import org.springframework.data.domain.Pageable;
11 | import org.springframework.data.jpa.repository.JpaRepository;
12 |
13 | public interface BookRepository extends JpaRepository {
14 | Optional findFirstByOrderByTitleAsc();
15 |
16 | // The following methods aren't used in the PagingSortingFiltering test,
17 | // but are provided as an example of JPA Query Methods.
18 |
19 | List findByGenre(BookGenre bookGenre, Pageable pageable);
20 |
21 | Page findByTitleContaining(String title, Pageable pageable);
22 |
23 | Page findByAuthor_AuthorId(Long author_authorId, Pageable pageable);
24 |
25 | Page findByAuthorAndGenre(Author author, BookGenre genre, Pageable pageable);
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/spring-jpa/src/main/java/com/example/relationships/model/Actor.java:
--------------------------------------------------------------------------------
1 | package com.example.relationships.model;
2 |
3 | import java.util.HashSet;
4 | import java.util.Objects;
5 | import java.util.Set;
6 |
7 | import jakarta.persistence.Column;
8 | import jakarta.persistence.Entity;
9 | import jakarta.persistence.GeneratedValue;
10 | import jakarta.persistence.GenerationType;
11 | import jakarta.persistence.Id;
12 | import jakarta.persistence.ManyToMany;
13 | import jakarta.persistence.Table;
14 | import lombok.Getter;
15 | import lombok.Setter;
16 |
17 | @Entity
18 | @Table(name = "actor")
19 | @Getter
20 | @Setter
21 | public class Actor {
22 | @Id
23 | @GeneratedValue(strategy = GenerationType.IDENTITY)
24 | @Column(name = "actor_id")
25 | private Long actorId;
26 |
27 | @Column(name = "first_name", nullable = false, length = 50)
28 | private String firstName;
29 |
30 | @Column(name = "last_name", nullable = false, length = 50)
31 | private String lastName;
32 |
33 | @ManyToMany(mappedBy = "actors")
34 | private Set movies;
35 |
36 | /**
37 | * Adds an Actor to a movie, maintaining bidirectional integrity.
38 | * @param movie to add the Actor into.
39 | */
40 | public void addMovie(Movie movie) {
41 | if (movies == null) {
42 | movies = new HashSet<>();
43 | }
44 | movies.add(movie);
45 | movie.getActors().add(this);
46 | }
47 |
48 | @Override
49 | public final boolean equals(Object o) {
50 | if (!(o instanceof Actor actor)) return false;
51 |
52 | return Objects.equals(getActorId(), actor.getActorId());
53 | }
54 |
55 | @Override
56 | public int hashCode() {
57 | return Objects.hashCode(getActorId());
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/spring-jpa/src/main/java/com/example/relationships/model/Director.java:
--------------------------------------------------------------------------------
1 | package com.example.relationships.model;
2 |
3 | import java.util.Objects;
4 | import java.util.Set;
5 |
6 | import jakarta.persistence.CascadeType;
7 | import jakarta.persistence.Column;
8 | import jakarta.persistence.Entity;
9 | import jakarta.persistence.GeneratedValue;
10 | import jakarta.persistence.GenerationType;
11 | import jakarta.persistence.Id;
12 | import jakarta.persistence.OneToMany;
13 | import jakarta.persistence.OneToOne;
14 | import jakarta.persistence.PrimaryKeyJoinColumn;
15 | import jakarta.persistence.Table;
16 | import lombok.Getter;
17 | import lombok.Setter;
18 |
19 | @Entity
20 | @Table(name = "director")
21 | @Getter
22 | @Setter
23 | public class Director {
24 | @Id
25 | @GeneratedValue(strategy = GenerationType.IDENTITY)
26 | @Column(name = "director_id")
27 | private Long directorId;
28 |
29 | @Column(name = "first_name", nullable = false, length = 50)
30 | private String firstName;
31 |
32 | @Column(name = "last_name", nullable = false, length = 50)
33 | private String lastName;
34 |
35 | @OneToMany(mappedBy = "director") // Reference related entity's associated field
36 | private Set movies;
37 |
38 | @OneToOne(
39 | mappedBy = "director", // Reference related entity's associated field
40 | cascade = CascadeType.ALL, // Cascade persistence to the mapped entity
41 | orphanRemoval = true // Remove director bio from director if deleted
42 | )
43 | // The primary key of the Director entity is used as the foreign key of the DirectorBio entity.
44 | @PrimaryKeyJoinColumn
45 | private DirectorBio directorBio;
46 |
47 | public void setDirectorBio(DirectorBio directorBio) {
48 | this.directorBio = directorBio;
49 | if (directorBio != null) {
50 | directorBio.setDirector(this);
51 | }
52 | }
53 |
54 | @Override
55 | public final boolean equals(Object o) {
56 | if (!(o instanceof Director director)) return false;
57 |
58 | return Objects.equals(getDirectorId(), director.getDirectorId());
59 | }
60 |
61 | @Override
62 | public int hashCode() {
63 | return Objects.hashCode(getDirectorId());
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/spring-jpa/src/main/java/com/example/relationships/model/DirectorBio.java:
--------------------------------------------------------------------------------
1 | package com.example.relationships.model;
2 |
3 | import jakarta.persistence.*;
4 | import java.util.Objects;
5 | import lombok.Getter;
6 | import lombok.Setter;
7 |
8 | @Entity
9 | @Table(name = "director_bio")
10 | @Getter
11 | @Setter
12 | public class DirectorBio {
13 |
14 | @Id
15 | @Column(name = "director_id")
16 | private Long directorId;
17 |
18 | @OneToOne(fetch = FetchType.LAZY)
19 | // The primary key will be copied from the director entity
20 | @MapsId
21 | @JoinColumn(name = "director_id")
22 | private Director director;
23 |
24 | @Column(name = "biography", columnDefinition = "CLOB")
25 | private String biography;
26 |
27 | @Override
28 | public final boolean equals(Object o) {
29 | if (!(o instanceof DirectorBio directorBio)) return false;
30 | return Objects.equals(getDirectorId(), directorBio.getDirectorId());
31 | }
32 |
33 | @Override
34 | public int hashCode() {
35 | return Objects.hashCode(getDirectorId());
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/spring-jpa/src/main/java/com/example/relationships/model/Movie.java:
--------------------------------------------------------------------------------
1 | package com.example.relationships.model;
2 |
3 | import java.util.Objects;
4 | import java.util.Set;
5 |
6 | import jakarta.persistence.Column;
7 | import jakarta.persistence.Entity;
8 | import jakarta.persistence.GeneratedValue;
9 | import jakarta.persistence.GenerationType;
10 | import jakarta.persistence.Id;
11 | import jakarta.persistence.JoinColumn;
12 | import jakarta.persistence.JoinTable;
13 | import jakarta.persistence.ManyToMany;
14 | import jakarta.persistence.ManyToOne;
15 | import jakarta.persistence.Table;
16 | import lombok.Getter;
17 | import lombok.Setter;
18 |
19 | @Entity
20 | @Table(name = "movie")
21 | @Getter
22 | @Setter
23 | public class Movie {
24 | @Id
25 | @GeneratedValue(strategy = GenerationType.IDENTITY)
26 | @Column(name = "movie_id")
27 | private Long movieId;
28 |
29 | @Column(name = "title", nullable = false, length = 100)
30 | private String title;
31 |
32 | @Column(name = "release_year")
33 | private Integer releaseYear;
34 |
35 | @Column(name = "genre", length = 50)
36 | private String genre;
37 |
38 | @ManyToOne
39 | @JoinColumn(name = "director_id")
40 | private Director director;
41 |
42 | @ManyToMany
43 | @JoinTable(
44 | name = "movie_actor",
45 | joinColumns = @JoinColumn(name = "movie_id"),
46 | inverseJoinColumns = @JoinColumn(name = "actor_id")
47 | )
48 | private Set actors;
49 |
50 | @Override
51 | public final boolean equals(Object o) {
52 | if (!(o instanceof Movie movie)) return false;
53 |
54 | return Objects.equals(movieId, movie.movieId);
55 | }
56 |
57 | @Override
58 | public int hashCode() {
59 | return Objects.hashCode(movieId);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/spring-jpa/src/main/java/com/example/relationships/repository/ActorRepository.java:
--------------------------------------------------------------------------------
1 | package com.example.relationships.repository;
2 |
3 | import java.util.Optional;
4 |
5 | import com.example.relationships.model.Actor;
6 | import org.springframework.data.jpa.repository.JpaRepository;
7 |
8 | public interface ActorRepository extends JpaRepository {
9 | Optional findByFirstNameAndLastName(String firstName, String lastName);
10 | }
11 |
--------------------------------------------------------------------------------
/spring-jpa/src/main/java/com/example/relationships/repository/DirectorBioRepository.java:
--------------------------------------------------------------------------------
1 | package com.example.relationships.repository;
2 |
3 | import com.example.relationships.model.DirectorBio;
4 | import org.springframework.data.jpa.repository.JpaRepository;
5 |
6 | public interface DirectorBioRepository extends JpaRepository {
7 | }
8 |
--------------------------------------------------------------------------------
/spring-jpa/src/main/java/com/example/relationships/repository/DirectorRepository.java:
--------------------------------------------------------------------------------
1 | package com.example.relationships.repository;
2 |
3 | import java.util.Optional;
4 |
5 | import com.example.relationships.model.Director;
6 | import org.springframework.data.jpa.repository.JpaRepository;
7 |
8 | public interface DirectorRepository extends JpaRepository {
9 | Optional findByFirstNameAndLastName(String firstName, String lastName);
10 | }
11 |
--------------------------------------------------------------------------------
/spring-jpa/src/main/java/com/example/relationships/repository/MovieRepository.java:
--------------------------------------------------------------------------------
1 | package com.example.relationships.repository;
2 |
3 | import java.util.Optional;
4 |
5 | import com.example.relationships.model.Movie;
6 | import org.springframework.data.jpa.repository.JpaRepository;
7 |
8 | public interface MovieRepository extends JpaRepository {
9 | Optional findByTitle(String title);
10 | }
11 |
--------------------------------------------------------------------------------
/spring-jpa/src/main/java/com/example/repository/StudentRepository.java:
--------------------------------------------------------------------------------
1 | package com.example.repository;
2 |
3 | import java.util.UUID;
4 |
5 | import com.example.model.Student;
6 | import org.springframework.data.jpa.repository.JpaRepository;
7 |
8 | public interface StudentRepository extends JpaRepository {
9 | }
10 |
--------------------------------------------------------------------------------
/spring-jpa/src/main/resources/application.yaml:
--------------------------------------------------------------------------------
1 | spring:
2 | jpa:
3 | # Prints SQL generated by JPA.
4 | show-sql: true
5 | hibernate:
6 | # We are running our own DDL scripts for strict schema control.
7 | ddl-auto: none
8 |
9 | datasource:
10 | username: ${USERNAME}
11 | password: ${PASSWORD}
12 | url: ${JDBC_URL}
13 |
14 | # Set these to use UCP over Hikari.
15 | driver-class-name: oracle.jdbc.OracleDriver
16 | type: oracle.ucp.jdbc.PoolDataSource
17 | oracleucp:
18 | initial-pool-size: 1
19 | min-pool-size: 1
20 | max-pool-size: 30
21 | connection-pool-name: UCPSampleApplication
22 | connection-factory-class-name: oracle.jdbc.pool.OracleDataSource
23 |
--------------------------------------------------------------------------------
/spring-jpa/src/test/java/com/example/SpringJPATest.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import java.time.Duration;
4 | import java.util.Optional;
5 |
6 | import com.example.model.Student;
7 | import com.example.repository.StudentRepository;
8 | import org.junit.jupiter.api.Test;
9 | import org.springframework.beans.factory.annotation.Autowired;
10 | import org.springframework.boot.test.context.SpringBootTest;
11 | import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
12 | import org.testcontainers.junit.jupiter.Container;
13 | import org.testcontainers.junit.jupiter.Testcontainers;
14 | import org.testcontainers.oracle.OracleContainer;
15 |
16 | import static org.assertj.core.api.Assertions.assertThat;
17 | import static org.junit.jupiter.api.Assertions.assertFalse;
18 | import static org.junit.jupiter.api.Assertions.assertTrue;
19 |
20 | @SpringBootTest
21 | @Testcontainers
22 | public class SpringJPATest {
23 | @Container
24 | @ServiceConnection
25 | static OracleContainer oracleContainer = new OracleContainer("gvenzl/oracle-free:23.7-slim-faststart")
26 | .withStartupTimeout(Duration.ofMinutes(2))
27 | .withUsername("testuser")
28 | .withPassword("testpwd")
29 | .withInitScript("student.sql");
30 |
31 | // Autowire JPA repositories
32 | @Autowired
33 | StudentRepository studentRepository;
34 |
35 | @Test
36 | void crudExample() {
37 | Student s = new Student();
38 | s.setFirstName("John");
39 | s.setLastName("Doe");
40 | s.setCredits(60);
41 | s.setMajor("Computer Science");
42 | s.setEmail("john.doe@example.edu");
43 | s.setGpa(3.77);
44 |
45 | // Create a new student using the student repository.
46 | Student saved = studentRepository.save(s);
47 | assertThat(saved.getId()).isNotNull();
48 |
49 | // Update the student credits and GPA.
50 | saved.setCredits(64);
51 | saved.setGpa(3.79);
52 | studentRepository.save(saved);
53 | studentRepository.flush();
54 |
55 | // Verify the student was updated successfully.
56 | Optional byId = studentRepository.findById(saved.getId());
57 | assertTrue(byId.isPresent());
58 | assertThat(byId.get().getCredits()).isEqualTo(64);
59 | assertThat(byId.get().getGpa()).isEqualTo(3.79);
60 |
61 | studentRepository.deleteById(saved.getId());
62 | assertFalse(studentRepository.findById(saved.getId()).isPresent());
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/spring-jpa/src/test/resources/student.sql:
--------------------------------------------------------------------------------
1 | create table student (
2 | id raw(16) default sys_guid() primary key,
3 | first_name varchar2(50) not null,
4 | last_name varchar2(50) not null,
5 | email varchar2(100),
6 | major varchar2(20) not null,
7 | credits number(10),
8 | gpa number(3,2) check (gpa between 0.00 and 4.00)
9 | );
10 |
11 | insert into student (first_name, last_name, email, major, credits, gpa)
12 | values ('Alice', 'Cooper', 'alice.cooper@example.com', 'Computer Science', 90, 3.8);
13 |
14 | insert into student (first_name, last_name, email, major, credits, gpa)
15 | values ('Jane', 'Smith', 'jane.smith@example.com', 'Biology', 75, 3.6);
16 |
17 | insert into student (first_name, last_name, email, major, credits, gpa)
18 | values ('Michael', 'Johnson', 'michael.j@example.com', 'Mathematics', 110, 3.9);
19 |
20 | insert into student (first_name, last_name, email, major, credits, gpa)
21 | values ('Emily', 'Davis', 'emily.davis@example.com', 'History', 45, 3.4);
22 |
23 | insert into student (first_name, last_name, email, major, credits, gpa)
24 | values ('William', 'Brown', 'william.brown@example.com', 'Physics', 120, 3.7);
25 |
--------------------------------------------------------------------------------
/spring-resource-sample/README.md:
--------------------------------------------------------------------------------
1 | # Spring Resource Loader Example
2 |
3 | This example demonstrates how to implement a custom [Spring Resource Loader](https://docs.spring.io/spring-framework/reference/core/resources.html#resources-resourceloader) to idiomatically load blob resources (such as images or files) at application startup from an arbitrary storage backend.
4 |
5 | In this example our storage backend is an Oracle Database table using a BLOB column, but the design applies to any storage backend with a programmable API.
6 |
7 | - DatabaseResource.java defines custom Resource type extending AbstractResource
8 | - DatabaseResourceResolver.java implements the Spring ResourceLoaderAware and ProtocolResolver interfaces to provide a Spring Compnent capable of instantiating DatabaseResorce objects.
9 | - DatabaseLocation.java is a data class that maps a Resource location to a BLOB in the database.
10 |
11 | ## Prerequisites
12 |
13 | - Java 21+, Maven
14 |
15 | ## Run the sample
16 |
17 | The sample provides an all-in-one test leveraging Testcontainers and Oracle Database to do the following:
18 |
19 | 1. Start and configure a database server using Testcontainers
20 | 2. Load Spring Resources from the database
21 | 3. Verify the resources are loaded and accessible
22 |
23 | You can run the test like so, from the project's root directory:
24 |
25 | `mvn test`
26 |
27 |
--------------------------------------------------------------------------------
/spring-resource-sample/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | com.example
7 | oracle-database-java-samples
8 | 1.0.0-SNAPSHOT
9 |
10 |
11 | spring-boot-resource-example
12 | 1.0.0-SNAPSHOT
13 | spring-boot-resource-example
14 | Example implementation of Spring Boot Resources
15 |
16 |
17 |
18 | org.springframework.boot
19 | spring-boot-starter-data-jdbc
20 |
21 |
22 | com.oracle.database.spring
23 | oracle-spring-boot-starter-ucp
24 | ${oracle.starters.version}
25 |
26 |
27 |
28 |
29 | org.springframework.boot
30 | spring-boot-starter-test
31 | test
32 |
33 |
34 | org.springframework.boot
35 | spring-boot-testcontainers
36 | test
37 |
38 |
39 | org.testcontainers
40 | oracle-free
41 | ${testcontainers.version}
42 | test
43 |
44 |
45 | org.testcontainers
46 | junit-jupiter
47 | test
48 |
49 |
50 |
51 |
52 |
53 |
54 | org.springframework.boot
55 | spring-boot-maven-plugin
56 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/spring-resource-sample/src/main/java/com/example/DatabaseLocation.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | public class DatabaseLocation {
4 | static final String PROTOCOL_PREFIX = "oracledatabase://";
5 |
6 | private final String table;
7 | private final String blobColumn;
8 | private final String fileNameColumn;
9 | private final String fileName;
10 |
11 | public DatabaseLocation(String table, String blobColumn, String fileNameColumn, String fileName) {
12 | this.table = table;
13 | this.blobColumn = blobColumn;
14 | this.fileNameColumn = fileNameColumn;
15 | this.fileName = fileName;
16 | }
17 |
18 | public String getFileName() {
19 | return fileName;
20 | }
21 |
22 | public String getBlobColumn() {
23 | return blobColumn;
24 | }
25 |
26 | public String query() {
27 | return """
28 | select %s from %s where %s = ?
29 | """.formatted(blobColumn, table, fileNameColumn);
30 | }
31 |
32 | public String description() {
33 | return "%s%s/%s".formatted(PROTOCOL_PREFIX, table, fileName);
34 | }
35 | }
--------------------------------------------------------------------------------
/spring-resource-sample/src/main/java/com/example/DatabaseResource.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import java.io.IOException;
4 | import java.io.InputStream;
5 | import java.sql.Blob;
6 | import java.sql.ResultSet;
7 |
8 | import org.springframework.core.io.AbstractResource;
9 | import org.springframework.jdbc.core.JdbcTemplate;
10 |
11 | public class DatabaseResource extends AbstractResource {
12 | private final JdbcTemplate jdbcTemplate;
13 | private final DatabaseLocation location;
14 |
15 | public DatabaseResource(JdbcTemplate jdbcTemplate, DatabaseLocation location) {
16 | this.jdbcTemplate = jdbcTemplate;
17 | this.location = location;
18 | }
19 |
20 | @Override
21 | public InputStream getInputStream() throws IOException {
22 | return jdbcTemplate.query(location.query(), rs -> {
23 | if (rs.next()) {
24 | Blob blob = rs.getBlob(location.getBlobColumn());
25 | return blob.getBinaryStream();
26 | }
27 | return null;
28 | }, location.getFileName());
29 | }
30 |
31 | @Override
32 | public long contentLength() throws IOException {
33 | InputStream is = getInputStream();
34 | if (is == null) {
35 | return 0;
36 | }
37 | try {
38 | long size = 0L;
39 | int read;
40 | for(byte[] buf = new byte[256];
41 | (read = is.read(buf)) != -1;
42 | size += read) {}
43 | return size;
44 | } catch (IOException e) {
45 | throw new RuntimeException(e);
46 | }
47 | }
48 |
49 | @Override
50 | public boolean exists() {
51 | return Boolean.TRUE.equals(jdbcTemplate.query(location.query(), ResultSet::next, location.getFileName()));
52 | }
53 |
54 | @Override
55 | public String getDescription() {
56 | return location.description();
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/spring-resource-sample/src/main/java/com/example/DatabaseResourceResolver.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import org.springframework.beans.factory.annotation.Value;
4 | import org.springframework.context.ResourceLoaderAware;
5 | import org.springframework.core.io.DefaultResourceLoader;
6 | import org.springframework.core.io.ProtocolResolver;
7 | import org.springframework.core.io.Resource;
8 | import org.springframework.core.io.ResourceLoader;
9 | import org.springframework.jdbc.core.JdbcTemplate;
10 | import org.springframework.stereotype.Component;
11 |
12 | @Component
13 | public class DatabaseResourceResolver implements ResourceLoaderAware, ProtocolResolver {
14 |
15 | private final JdbcTemplate jdbcTemplate;
16 | private final String table;
17 | private final String blobColumn;
18 | private final String fileNameColumn;
19 |
20 | public DatabaseResourceResolver(JdbcTemplate jdbcTemplate,
21 | @Value("${databaseResource.table:spring_resource}") String table,
22 | @Value("${databaseResource.blobColumn:file_data}") String blobColumn,
23 | @Value("${databaseResource.fileNameColumn:file_name}") String fileNameColumn) {
24 | this.jdbcTemplate = jdbcTemplate;
25 | this.table = table;
26 | this.blobColumn = blobColumn;
27 | this.fileNameColumn = fileNameColumn;
28 | }
29 |
30 | @Override
31 | public Resource resolve(String location, ResourceLoader resourceLoader) {
32 | if (location.startsWith(DatabaseLocation.PROTOCOL_PREFIX)) {
33 | String fileName = location.substring(DatabaseLocation.PROTOCOL_PREFIX.length());
34 | DatabaseLocation databaseLocation = new DatabaseLocation(table, blobColumn, fileNameColumn, fileName);
35 | return new DatabaseResource(jdbcTemplate, databaseLocation);
36 | }
37 | return null;
38 | }
39 |
40 | @Override
41 | public void setResourceLoader(ResourceLoader resourceLoader) {
42 | if (DefaultResourceLoader.class.isAssignableFrom(resourceLoader.getClass())) {
43 | ((DefaultResourceLoader) resourceLoader).addProtocolResolver(this);
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/spring-resource-sample/src/main/java/com/example/SampleApp.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 |
6 | @SpringBootApplication
7 | public class SampleApp {
8 | public static void main(String[] args) {
9 | SpringApplication.run(SampleApp.class, args);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/spring-resource-sample/src/main/resources/application.yaml:
--------------------------------------------------------------------------------
1 | spring:
2 | datasource:
3 | username: ${USERNAME}
4 | password: ${PASSWORD}
5 | url: ${JDBC_URL}
6 |
7 | # Set these to use UCP over Hikari.
8 | driver-class-name: oracle.jdbc.OracleDriver
9 | type: oracle.ucp.jdbc.PoolDataSource
10 | oracleucp:
11 | initial-pool-size: 1
12 | min-pool-size: 1
13 | max-pool-size: 30
14 | connection-pool-name: UCPSampleApplication
15 | connection-factory-class-name: oracle.jdbc.pool.OracleDataSource
16 |
--------------------------------------------------------------------------------
/spring-resource-sample/src/test/resources/cat.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anders-swanson/oracle-database-java-samples/7f3175c3eacbd462de3ba10775d9070b2d9613a9/spring-resource-sample/src/test/resources/cat.jpg
--------------------------------------------------------------------------------
/spring-vault-oracle-app/README.md:
--------------------------------------------------------------------------------
1 | # spring-vault-oracle-app
2 |
3 | The Spring Vault Oracle App sample demonstrates dynamically loading secrets from [OCI Vault](https://docs.oracle.com/en-us/iaas/Content/KeyManagement/home.htm) into the Spring Boot context during startup. All secrets loaded this way are made available by name as application properties, accessible using `@Value` annotations.
4 |
5 | ## Prerequisites
6 |
7 | - Access to an OCI Account. You can use [Free Tier](https://signup.oraclecloud.com/).
8 | - Java 21+, Maven
9 | - [OCI Config file](https://docs.oracle.com/en-us/iaas/Content/API/Concepts/sdkconfig.htm) for authentication
10 |
11 | ## Setup
12 |
13 | 1. Create an [OCI Vault](https://docs.oracle.com/en-us/iaas/Content/KeyManagement/home.htm), and add two secrets to the vault named `s1` and `s2`.
14 |
15 | 2. Set the following environment variables, using values for your OCI Compartment, Region, and Vault.
16 |
17 | ```bash
18 | OCI_COMPARTMENT_ID=
19 | OCI_REGION=
20 | OCI_VAULT_ID=
21 | ```
22 |
23 | ## Run the app
24 |
25 | Using Maven, start the app:
26 |
27 | `mvn spring-boot:run`
28 |
29 | Now, query the app to see if the Vault secrets were successfully loaded:
30 |
31 | ```bash
32 | curl localhost:9001/values
33 | ```
--------------------------------------------------------------------------------
/spring-vault-oracle-app/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 |
7 | com.example
8 | oracle-database-java-samples
9 | 1.0.0-SNAPSHOT
10 |
11 |
12 | spring-oracle-vault-app
13 | spring-oracle-vault-app
14 | 1.0.0-SNAPSHOT
15 | Example App using OCI Vault and Spring
16 |
17 |
18 | 21
19 | 21
20 |
21 |
22 |
23 |
24 | org.springframework.boot
25 | spring-boot-starter-web
26 |
27 |
28 | com.oracle.cloud.spring
29 | spring-cloud-oci-starter-vault
30 | ${spring.cloud.oracle.version}
31 |
32 |
33 |
34 |
35 |
36 |
37 | org.springframework.boot
38 | spring-boot-maven-plugin
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/spring-vault-oracle-app/src/main/java/com/example/AppController.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import java.util.Base64;
4 | import java.util.List;
5 |
6 | import com.oracle.bmc.vault.model.Base64SecretContentDetails;
7 | import com.oracle.bmc.vault.model.CreateSecretDetails;
8 | import com.oracle.cloud.spring.vault.VaultTemplate;
9 | import org.springframework.beans.factory.annotation.Value;
10 | import org.springframework.http.HttpStatus;
11 | import org.springframework.http.ResponseEntity;
12 | import org.springframework.web.bind.annotation.GetMapping;
13 | import org.springframework.web.bind.annotation.PostMapping;
14 | import org.springframework.web.bind.annotation.RequestParam;
15 | import org.springframework.web.bind.annotation.RestController;
16 |
17 | @RestController
18 | public class AppController {
19 | private final VaultTemplate vaultTemplate;
20 |
21 | // The value of the Vault secret "mysecret" will be injected into "mySecretValue"
22 | // by the Spring property source loader.
23 | @Value("${s1}")
24 | private String secret1;
25 |
26 | @Value("${s2}")
27 | private String secret2;
28 |
29 | public AppController(VaultTemplate vaultTemplate) {
30 | this.vaultTemplate = vaultTemplate;
31 | }
32 |
33 | @GetMapping("/values")
34 | public ResponseEntity> showValues() {
35 | return ResponseEntity.ok(List.of(
36 | secret1, secret2
37 | ));
38 | }
39 |
40 | @PostMapping("/secret")
41 | public ResponseEntity> createSecret(@RequestParam String name, @RequestParam String content) {
42 | Base64SecretContentDetails contentDetails = Base64SecretContentDetails.builder()
43 | .content(Base64.getEncoder().encodeToString(content.getBytes()))
44 | .name(name)
45 | .build();
46 | CreateSecretDetails createSecretDetails = CreateSecretDetails.builder()
47 | .secretContent(contentDetails)
48 | .build();
49 | vaultTemplate.createSecret(name, createSecretDetails);
50 | return ResponseEntity.status(HttpStatus.CREATED).build();
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/spring-vault-oracle-app/src/main/java/com/example/SampleApp.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 |
6 | @SpringBootApplication
7 | public class SampleApp {
8 | public static void main(String[] args) {
9 | SpringApplication.run(SampleApp.class, args);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/spring-vault-oracle-app/src/main/resources/application.yaml:
--------------------------------------------------------------------------------
1 | spring:
2 | cloud:
3 | oci:
4 | config:
5 | type: file
6 | region:
7 | static: ${OCI_REGION}
8 | vault:
9 | # OCI Compartment containing OCI Vault instance(s)
10 | compartment: ${OCI_COMPARTMENT_ID}
11 | enabled: true
12 | # How often to refresh properties loaded from OCI Vault
13 | property-refresh-interval: 10000ms
14 | # OCI Vault instance(s) loaded as spring property sources
15 | property-sources:
16 | - vault-id: ${OCI_VAULT_ID}
--------------------------------------------------------------------------------
/testcontainers/README.md:
--------------------------------------------------------------------------------
1 | # Oracle Database Testcontainers
2 |
3 | This module provides examples using [Testcontainers](https://java.testcontainers.org/) with [Oracle Database Free](https://www.oracle.com/database/free/) to test your Oracle Database Java applications using dispoable containers.
4 |
5 | The `gvenzl/oracle-free` Oracle Database container images are recommended for use with Testcontainers and Java. The examples in this module use the Oracle Database 23ai Free image `gvenzl/oracle-free:23.7-slim-faststart`.
6 |
7 | ### [GetDatabaseConnectionTest](./src/test/java/com/example/GetDatabaseConnectionTest.java)
8 |
9 | This test implements a basic Oracle Database test with Testcontainers. The version of the containerized database is queried to verify the test connection works.
10 |
11 | ### [InitializedDatabaseTest](./src/test/java/com/example/InitializedDatabaseTest.java)
12 |
13 | This test demonstrates how to run an initialization script in the containerized database to configure a table schema and insert test data.
14 |
15 | ### [SpringBootDatabaseTest](./src/test/java/com/example/SpringBootDatabaseTest.java)
16 |
17 | This test uses a containerized database as a Spring Boot datasource within the context of a `@SpringBootTest`, initializing the Spring Boot datasource properties at test startup.
18 |
19 | The pattern shown allows developers to test their Spring Boot applications with Oracle Database using a fully featured Spring context.
20 |
21 | ### [SysdbaInitTest](./src/test/java/com/example/SysdbaInitTest.java)
22 |
23 | This test demonstrates how to mount a SQL script on a containerized database and run that script as `sysdba` before the test suite begins. This pattern is useful for DBA-level setup before the test, like applying user grants or creating PDBs.
24 |
25 | The SysdbaInitTest setup script applies grants to a test user to manage Oracle Database Transacational Event Queues (JMS), and then creates a JMS queue.
26 |
27 | ### [reusable package](./src/test/java/com/example/reusable/README.md)
28 |
29 | This package provides sample tests with a reusable Oracle Database container, allowing you to reduce startup time between database test suites.
30 |
31 |
--------------------------------------------------------------------------------
/testcontainers/src/main/java/com/example/Application.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 |
6 | @SpringBootApplication
7 | public class Application {
8 | public static void main(String[] args) {
9 | SpringApplication.run(Application.class, args);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/testcontainers/src/main/resources/application.yaml:
--------------------------------------------------------------------------------
1 | spring:
2 | datasource:
3 | username: ${USERNAME}
4 | password: ${PASSWORD}
5 | url: ${JDBC_URL}
6 |
7 | # Set these to use UCP over Hikari.
8 | driver-class-name: oracle.jdbc.OracleDriver
9 | type: oracle.ucp.jdbc.PoolDataSource
10 | oracleucp:
11 | initial-pool-size: 1
12 | min-pool-size: 1
13 | max-pool-size: 30
14 | connection-pool-name: UCPSampleApplication
15 | connection-factory-class-name: oracle.jdbc.pool.OracleDataSource
16 |
--------------------------------------------------------------------------------
/testcontainers/src/test/java/com/example/GetDatabaseConnectionTest.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import java.sql.Connection;
4 | import java.sql.SQLException;
5 | import java.sql.Statement;
6 | import java.time.Duration;
7 |
8 | import oracle.jdbc.pool.OracleDataSource;
9 | import org.junit.jupiter.api.BeforeAll;
10 | import org.junit.jupiter.api.Test;
11 | import org.testcontainers.junit.jupiter.Testcontainers;
12 | import org.testcontainers.oracle.OracleContainer;
13 |
14 | @Testcontainers
15 | public class GetDatabaseConnectionTest {
16 | /**
17 | * Use a containerized Oracle Database instance for testing.
18 | */
19 | static OracleContainer oracleContainer = new OracleContainer("gvenzl/oracle-free:23.7-slim-faststart")
20 | .withStartupTimeout(Duration.ofMinutes(5))
21 | .withUsername("testuser")
22 | .withPassword("testpwd");
23 |
24 | static OracleDataSource ds;
25 |
26 | @BeforeAll
27 | static void setUp() throws SQLException {
28 | oracleContainer.start();
29 | // Configure the OracleDataSource to use the database container
30 | ds = new OracleDataSource();
31 | ds.setURL(oracleContainer.getJdbcUrl());
32 | ds.setUser(oracleContainer.getUsername());
33 | ds.setPassword(oracleContainer.getPassword());
34 | }
35 |
36 | /**
37 | * Verifies the containerized database connection.
38 | * @throws SQLException
39 | */
40 | @Test
41 | void getConnection() throws SQLException {
42 | // Query Database version to verify connection
43 | try (Connection conn = ds.getConnection();
44 | Statement stmt = conn.createStatement()) {
45 | stmt.executeQuery("select * from v$version");
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/testcontainers/src/test/java/com/example/InitializedDatabaseTest.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import java.sql.Connection;
4 | import java.sql.ResultSet;
5 | import java.sql.SQLException;
6 | import java.sql.Statement;
7 | import java.time.Duration;
8 |
9 | import oracle.jdbc.pool.OracleDataSource;
10 | import org.junit.jupiter.api.Assertions;
11 | import org.junit.jupiter.api.BeforeAll;
12 | import org.junit.jupiter.api.Test;
13 | import org.testcontainers.oracle.OracleContainer;
14 |
15 | import static org.assertj.core.api.Assertions.assertThat;
16 |
17 | public class InitializedDatabaseTest {
18 | /**
19 | * Use a containerized Oracle Database instance for testing.
20 | */
21 | static OracleContainer oracleContainer = new OracleContainer("gvenzl/oracle-free:23.7-slim-faststart")
22 | .withStartupTimeout(Duration.ofMinutes(5))
23 | .withUsername("testuser")
24 | .withPassword("testpwd")
25 | .withInitScript("students.sql");
26 |
27 | static OracleDataSource ds;
28 |
29 | @BeforeAll
30 | static void setUp() throws SQLException {
31 | oracleContainer.start();
32 | // Configure the OracleDataSource to use the database container
33 | ds = new OracleDataSource();
34 | ds.setURL(oracleContainer.getJdbcUrl());
35 | ds.setUser(oracleContainer.getUsername());
36 | ds.setPassword(oracleContainer.getPassword());
37 | }
38 |
39 | /**
40 | * Verifies the database is initialized with a student
41 | * @throws SQLException
42 | */
43 | @Test
44 | void getStudent() throws SQLException {
45 | // Query Database version to verify connection
46 | try (Connection conn = ds.getConnection();
47 | Statement stmt = conn.createStatement();
48 | ResultSet rs = stmt.executeQuery("select * from students where first_name = 'Alice'")) {
49 | Assertions.assertTrue(rs.next());
50 | assertThat(rs.getString(2)).isEqualTo("Alice");
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/testcontainers/src/test/java/com/example/SpringBootDatabaseTest.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import javax.sql.DataSource;
4 | import java.sql.Connection;
5 | import java.sql.SQLException;
6 | import java.sql.Statement;
7 | import java.time.Duration;
8 |
9 | import org.junit.jupiter.api.Test;
10 | import org.springframework.beans.factory.annotation.Autowired;
11 | import org.springframework.boot.test.context.SpringBootTest;
12 | import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
13 | import org.testcontainers.junit.jupiter.Container;
14 | import org.testcontainers.junit.jupiter.Testcontainers;
15 | import org.testcontainers.oracle.OracleContainer;
16 |
17 | @Testcontainers
18 | @SpringBootTest
19 | public class SpringBootDatabaseTest {
20 | /**
21 | * Use a containerized Oracle Database instance for testing.
22 | */
23 | @Container
24 | @ServiceConnection
25 | static OracleContainer oracleContainer = new OracleContainer("gvenzl/oracle-free:23.7-slim-faststart")
26 | .withStartupTimeout(Duration.ofMinutes(5))
27 | .withUsername("testuser")
28 | .withPassword("testpwd");
29 |
30 | @Autowired
31 | DataSource dataSource;
32 |
33 | @Test
34 | void springDatasourceConnection() throws SQLException {
35 | // Query Database version to verify Spring DataSource connection
36 | try (Connection conn = dataSource.getConnection();
37 | Statement stmt = conn.createStatement()) {
38 | stmt.executeQuery("select * from v$version");
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/testcontainers/src/test/java/com/example/SysdbaInitTest.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import java.sql.Connection;
4 | import java.sql.SQLException;
5 | import java.sql.Statement;
6 | import java.time.Duration;
7 |
8 | import oracle.jdbc.pool.OracleDataSource;
9 | import org.junit.jupiter.api.BeforeAll;
10 | import org.junit.jupiter.api.Test;
11 | import org.testcontainers.junit.jupiter.Container;
12 | import org.testcontainers.oracle.OracleContainer;
13 | import org.testcontainers.utility.MountableFile;
14 |
15 | public class SysdbaInitTest {
16 | /**
17 | * Use a containerized Oracle Database instance for testing.
18 | */
19 | @Container
20 | static OracleContainer oracleContainer = new OracleContainer("gvenzl/oracle-free:23.7-slim-faststart")
21 | .withStartupTimeout(Duration.ofMinutes(5))
22 | .withUsername("testuser")
23 | .withPassword("testpwd");
24 |
25 | static OracleDataSource ds;
26 |
27 | @BeforeAll
28 | static void setUp() throws Exception {
29 | oracleContainer.start();
30 | // Configure the OracleDataSource to use the database container
31 | ds = new OracleDataSource();
32 | ds.setURL(oracleContainer.getJdbcUrl());
33 | ds.setUser(oracleContainer.getUsername());
34 | ds.setPassword(oracleContainer.getPassword());
35 |
36 | // Mounts the "dbainit.sql" script on the containerized database, and runs it as syadba.
37 | // This pattern is useful for configuring user grants or other DBA actions before tests begin
38 | oracleContainer.copyFileToContainer(MountableFile.forClasspathResource("dbainit.sql"), "/tmp/init.sql");
39 | oracleContainer.execInContainer("sqlplus", "sys / as sysdba", "@/tmp/init.sql");
40 | }
41 |
42 | /**
43 | * Verifies the containerized database connection.
44 | * @throws SQLException
45 | */
46 | @Test
47 | void verifyQueue() throws SQLException {
48 | // Query Database version to verify connection
49 | try (Connection conn = ds.getConnection();
50 | Statement stmt = conn.createStatement()) {
51 | stmt.executeQuery("select * from testqueue");
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/testcontainers/src/test/java/com/example/reusable/README.md:
--------------------------------------------------------------------------------
1 | ## Reusable Oracle Database Container(s)
2 |
3 | This package runs two database test classes against the same Oracle Database container by utilizing [Testcontainers reuse](https://java.testcontainers.org/features/reuse/). Reusuable containers are advantageous for faster test startup time, at the cost of potential cross-contamination of test data between disparate suites.
4 |
5 | The ReusableDatabaseTest statically configures and starts a reusable database container, which is shared by an child test classes.
6 |
7 | In your home directory, ensure the `.testcontainers.properties` file contains the following parameter:
8 |
9 | ```
10 | testcontainers.reuse.enable=true
11 | ```
12 |
13 | Or set the `TESTCONTAINERS_REUSE_ENABLE=true` environment variable.
14 |
--------------------------------------------------------------------------------
/testcontainers/src/test/java/com/example/reusable/ResuableDatabaseTest.java:
--------------------------------------------------------------------------------
1 | package com.example.reusable;
2 |
3 | import java.sql.SQLException;
4 | import java.time.Duration;
5 |
6 | import oracle.jdbc.pool.OracleDataSource;
7 | import org.junit.jupiter.api.BeforeAll;
8 | import org.testcontainers.oracle.OracleContainer;
9 |
10 | public class ResuableDatabaseTest {
11 | /**
12 | * Use a containerized Oracle Database instance for testing.
13 | */
14 | static OracleContainer oracleContainer = new OracleContainer("gvenzl/oracle-free:23.7-slim-faststart")
15 | .withStartupTimeout(Duration.ofMinutes(5))
16 | .withUsername("testuser")
17 | .withPassword("testpwd")
18 | .withReuse(true);
19 |
20 | static OracleDataSource ds;
21 |
22 | @BeforeAll
23 | static void setUp() throws SQLException {
24 | // With reusable, containers must be manually started
25 | oracleContainer.start();
26 | // Configure the OracleDataSource to use the database container
27 | ds = new OracleDataSource();
28 | ds.setURL(oracleContainer.getJdbcUrl());
29 | ds.setUser(oracleContainer.getUsername());
30 | ds.setPassword(oracleContainer.getPassword());
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/testcontainers/src/test/java/com/example/reusable/ReusableSelectTest.java:
--------------------------------------------------------------------------------
1 | package com.example.reusable;
2 |
3 | import java.sql.Connection;
4 | import java.sql.ResultSet;
5 | import java.sql.SQLException;
6 | import java.sql.Statement;
7 |
8 | import org.junit.jupiter.api.Assertions;
9 | import org.junit.jupiter.api.Test;
10 |
11 | public class ReusableSelectTest extends ResuableDatabaseTest {
12 | @Test
13 | public void testSimpleQuery() throws SQLException {
14 | try (Connection connection = ResuableDatabaseTest.ds.getConnection();
15 | Statement stmt = connection.createStatement();
16 | ResultSet rs = stmt.executeQuery("select 1")) {
17 | Assertions.assertTrue(rs.next(), "Result set should have at least one row");
18 | Assertions.assertEquals(1, rs.getInt(1), "Query result should be 1");
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/testcontainers/src/test/java/com/example/reusable/ReusableVersionTest.java:
--------------------------------------------------------------------------------
1 | package com.example.reusable;
2 |
3 | import java.sql.Connection;
4 | import java.sql.SQLException;
5 | import java.sql.Statement;
6 |
7 | import org.junit.jupiter.api.Test;
8 |
9 | public class ReusableVersionTest extends ResuableDatabaseTest {
10 | @Test
11 | void getVersion() throws SQLException {
12 | // Query Database version to verify connection
13 | try (Connection conn = ResuableDatabaseTest.ds.getConnection();
14 | Statement stmt = conn.createStatement()) {
15 | stmt.executeQuery("select * from v$version");
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/testcontainers/src/test/resources/dbainit.sql:
--------------------------------------------------------------------------------
1 | -- Set as appropriate for your database. "freepdb1" is the default PDB in Oracle Database Free
2 | alter session set container = freepdb1;
3 |
4 | -- Configure testuser with the necessary privileges to use Transactional Event Queues.
5 | grant aq_user_role to testuser;
6 | grant execute on dbms_aq to testuser;
7 | grant execute on dbms_aqadm to testuser;
8 | grant execute ON dbms_aqin TO testuser;
9 | grant execute ON dbms_aqjms TO testuser;
10 |
11 | -- Create a Transactional Event Queue
12 | begin
13 | -- See https://docs.oracle.com/en/database/oracle/oracle-database/23/arpls/DBMS_AQADM.html#GUID-93B0FF90-5045-4437-A9C4-B7541BEBE573
14 | -- For documentation on creating Transactional Event Queues.
15 | dbms_aqadm.create_transactional_event_queue(
16 | queue_name => 'testuser.testqueue',
17 | -- Payload can be RAW, JSON, DBMS_AQADM.JMS_TYPE, or an object type.
18 | -- Default is DBMS_AQADM.JMS_TYPE.
19 | queue_payload_type => DBMS_AQADM.JMS_TYPE,
20 | -- FALSE means queues can only have one consumer for each message. This is the default.
21 | -- TRUE means queues created in the table can have multiple consumers for each message.
22 | multiple_consumers => false
23 | );
24 |
25 | -- Start the queue
26 | dbms_aqadm.start_queue(
27 | queue_name => 'testuser.testqueue'
28 | );
29 | end;
30 | /
31 |
--------------------------------------------------------------------------------
/testcontainers/src/test/resources/students.sql:
--------------------------------------------------------------------------------
1 | create table students (
2 | id varchar2(36) default sys_guid() primary key,
3 | first_name varchar2(50) not null,
4 | last_name varchar2(50) not null,
5 | email varchar2(100),
6 | major varchar2(50) not null,
7 | credits number(10),
8 | gpa binary_double
9 | );
10 |
11 | create table lecture_halls (
12 | id varchar2(36) default sys_guid() primary key,
13 | name varchar2(50) not null
14 | );
15 |
16 | create table courses (
17 | id varchar2(36) default sys_guid() primary key,
18 | lecture_hall_id varchar2(36) not null,
19 | name varchar2(50) not null,
20 | description varchar2(250) not null,
21 | credits number check (credits between 0 and 10),
22 | constraint lecture_hall_fk foreign key (lecture_hall_id)
23 | references lecture_halls(id)
24 | );
25 |
26 | create table enrollments (
27 | id varchar2(36) default sys_guid() primary key,
28 | student_id varchar2(36) not null,
29 | course_id varchar2(36) not null,
30 | constraint student_fk foreign key (student_id)
31 | references students(id),
32 | constraint course_fk foreign key (course_id)
33 | references courses(id)
34 | );
35 |
36 | insert into students (first_name, last_name, email, major, credits, gpa)
37 | values ('Alice', 'Smith', 'alice.smith@example.edu', 'Computer Science', 77, 3.86);
38 | insert into lecture_halls (name)
39 | values ('Hoffman Hall');
40 | insert into courses (lecture_hall_id, name, description, credits)
41 | values (
42 | (select id from lecture_halls where name = 'Hoffman Hall'),
43 | 'Introduction to Computer Science',
44 | 'A foundational course covering basic principles of computer science and programming.',
45 | 4
46 | );
47 | insert into courses (lecture_hall_id, name, description, credits)
48 | values (
49 | (select id from lecture_halls where name = 'Hoffman Hall'),
50 | 'Data Structures and Algorithms',
51 | 'An in-depth study of various data structures and fundamental algorithms in computer science.',
52 | 5
53 | );
54 |
--------------------------------------------------------------------------------
/txeventq-examples/README.md:
--------------------------------------------------------------------------------
1 | # Oracle Database Transactional Event Queues Examples
2 |
3 | Prerequisites: An Oracle Database instance. The examples are configured to use a local, Oracle Database Free container running on port 1521, but any 23ai instance will work if configured accordingly.
4 |
5 | Like all my samples, you can run it on Oracle Database Free: https://medium.com/@anders.swanson.93/oracle-database-23ai-free-11abf827ab37
6 |
7 | ### [Kafka API](https://github.com/oracle/okafka)
8 |
9 | The [okafka.sql](./okafka.sql) script creates a table named `okafka_messages` used to demonstrate transactional messaging capabilities of the OKafka producer.
10 |
11 | The [OKafkaProducer](./src/main/java/com/example/txeventq/OKafkaProducer.java) implements a basic Oracle Database Kafka Producer. You can start the producer like so:
12 |
13 | ```bash
14 | mvn compile exec:java -Pkafkaproducer
15 | ```
16 |
17 | The [OKafkaConsumer](./src/main/java/com/example/txeventq/OKafkaConsumer.java) implements a basic Oracle Database Kafka Consumer. You can start the consumer like so:
18 |
19 | ```bash
20 | mvn compile exec:java -Pkafkaconsumer
21 | ```
22 |
23 | ### Spring JMS
24 |
25 | The [springjms.sql](./springjms.sql) script contains necessary code to create and start JMS Queue using the `dbms_aq` package.
26 |
27 | The [SpringJMSProducer](./src/main/java/com/example/txeventq/SpringJMSProducer.java) implements a basic Spring JMS producer. You can start the producer like so:
28 |
29 | ```bash
30 | mvn spring-boot:run -Pjmsproducer
31 | ```
32 |
33 | The [SpringJMSConsumer](./src/main/java/com/example/txeventq/SpringJMSConsumer.java) implements a basic Spring JMS consumer. You can start the consumer like so:
34 |
35 | ```bash
36 | mvn spring-boot:run -Pjmsconsumer
37 | ```
38 |
39 | Once both the producer and consumer are started, you can
40 |
41 | ### PL/SQL
42 |
43 | To use PL/SQL with TxEventQ, see the [PL/SQL](./txeventq.sql) example.
44 |
45 | ### [Oracle REST Data Services (ORDS)](https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/25.1/orrst/api-oracle-transactional-event-queues.html)
46 |
47 | To use ORDS with TxEventQ to produce and consume messages, see the [ORDS](./ords.md) example.
48 |
--------------------------------------------------------------------------------
/txeventq-examples/okafka.sql:
--------------------------------------------------------------------------------
1 | -- create a basic table to store messages in the order we produce them.
2 | create table if not exists okafka_messages (
3 | id number(10) generated always as identity primary key,
4 | message varchar2(1000)
5 | );
6 |
--------------------------------------------------------------------------------
/txeventq-examples/ords.md:
--------------------------------------------------------------------------------
1 | # TxEventQ with ORDS
2 |
3 | ### Environment variables
4 |
5 | The ORDS commands in this document use the following environment variables. Configure these as appropriate for your ORDS instance.
6 |
7 | ```bash
8 | export ORDS_URL=
9 | export DB_USERNAME=
10 | export DB_PASSWORD=''
11 | export DB_NAME=
12 | ```
13 |
14 | ### (1) Create a new topic
15 |
16 | The following cURL command creates a new topic named `ords_test` with 10 partitions:
17 |
18 | ```bash
19 | curl -X POST -u "$DB_USERNAME:$DB_PASSWORD" \
20 | -H 'Content-Type: application/json' \
21 | "${ORDS_URL}admin/_/db-api/stable/database/txeventq/clusters/${DB_NAME}/topics" -d '{
22 | "topic_name": "ords_test",
23 | "partitions_count": "10"
24 | }'
25 | ```
26 |
27 | #### (2) Create a consumer group
28 |
29 | The following cURL commands creates a consumer group named `my_grp` for the `ords_test` topic:
30 |
31 | ```bash
32 | curl -X POST -u "$DB_USERNAME:$DB_PASSWORD" \
33 | -H 'Content-Type: application/json' \
34 | "${ORDS_URL}admin/_/db-api/stable/database/txeventq/clusters/${DB_NAME}/consumer-groups/my_grp" -d '{
35 | "topic_name": "ords_test"
36 | }'
37 | ```
38 |
39 | ### (3) Create a consumer
40 |
41 | The following cURL command creates a consumer for the `my_grp` consumer group:
42 |
43 | ```bash
44 | curl -X POST -u "$DB_USERNAME:$DB_PASSWORD" \
45 | "${ORDS_URL}admin/_/db-api/stable/database/txeventq/consumers/my_grp"
46 | ```
47 |
48 | Response:
49 | ```bash
50 | {"instance_id":"my_grp_Cons_c27b15cc560a8c2b64500d80d64c594e"}
51 | ```
52 |
53 | The "create consumer" response contains the consumer's instance ID. save this value to the `CONSUMER_ID` environment variable. In this example, the value is `my_grp_Cons_c27b15cc560a8c2b64500d80d64c594e`; you will have your own, unique instance id.
54 |
55 | ```bash
56 | export CONSUMER_ID=
57 | ```
58 |
59 | ### (4) Produce records
60 |
61 | The following cURL command produces a record to the `ords_test` topic:
62 |
63 | ```bash
64 | curl -X POST -u "$DB_USERNAME:$DB_PASSWORD" \
65 | -H 'Content-Type: application/json' \
66 | "${ORDS_URL}admin/_/db-api/stable/database/txeventq/topics/ords_test" -d '{
67 | "records": [
68 | { "key": "abc", "value": "abc"}
69 | ]
70 | }'
71 | ```
72 |
73 | ### (5) Consume records
74 |
75 | The following cURL command consumes a record using the `my_grp` consumer group and the consumer instance ID from (3):
76 |
77 | ```bash
78 | curl -X GET -u "$DB_USERNAME:$DB_PASSWORD" \
79 | "${ORDS_URL}admin/_/db-api/stable/database/txeventq/consumers/my_grp/instances/${CONSUMER_ID}/records"
80 | ```
81 |
--------------------------------------------------------------------------------
/txeventq-examples/springjms.sql:
--------------------------------------------------------------------------------
1 | -- Delete a Transactional Event Queue
2 | begin
3 | dbms_aqadm.stop_queue(
4 | queue_name => 'jms_test'
5 | );
6 | dbms_aqadm.drop_transactional_event_queue(
7 | queue_name => 'jms_test'
8 | );
9 | end;
10 | /
11 |
12 | -- Create a Transactional Event Queue
13 | begin
14 | dbms_aqadm.create_transactional_event_queue(
15 | queue_name => 'jms_test',
16 | queue_payload_type => DBMS_AQADM.JMS_TYPE
17 | );
18 |
19 | -- Start the queue
20 | dbms_aqadm.start_queue(
21 | queue_name => 'jms_test'
22 | );
23 | end;
24 | /
25 |
--------------------------------------------------------------------------------
/txeventq-examples/src/main/java/com/example/txeventq/OKafkaConsumer.java:
--------------------------------------------------------------------------------
1 | package com.example.txeventq;
2 |
3 | import java.io.File;
4 | import java.sql.Connection;
5 | import java.sql.PreparedStatement;
6 | import java.sql.ResultSet;
7 | import java.sql.SQLException;
8 | import java.time.Duration;
9 | import java.util.Arrays;
10 | import java.util.List;
11 | import java.util.Properties;
12 |
13 | import org.apache.kafka.clients.consumer.ConsumerRecord;
14 | import org.apache.kafka.clients.consumer.ConsumerRecords;
15 | import org.oracle.okafka.clients.consumer.KafkaConsumer;
16 |
17 | import static com.example.txeventq.Prompt.prompt;
18 | import static com.example.txeventq.Values.OKAFKA_TOPIC_NAME;
19 |
20 | public class OKafkaConsumer {
21 | public static void main(String[] args) throws SQLException {
22 | Properties props = new Properties();
23 |
24 | props.setProperty("bootstrap.servers", "localhost:1521");
25 | props.setProperty("security.protocol", "PLAINTEXT");
26 |
27 | // Database service name / TNS Alias
28 | props.put("oracle.service.name", "freepdb1");
29 | // If using Oracle Database wallet, pass wallet directory
30 | String resourcesDir = new File(OKafkaProducer.class.getClassLoader()
31 | .getResource("")
32 | .getFile())
33 | .getAbsolutePath();
34 | props.put("oracle.net.tns_admin", resourcesDir);
35 |
36 | props.put("group.id" , "MY_CONSUMER_GROUP");
37 | props.put("enable.auto.commit","false");
38 | props.put("max.poll.records", 2000);
39 | props.put("auto.offset.reset", "earliest");
40 | props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
41 | props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
42 | KafkaConsumer consumer = new KafkaConsumer<>(props);
43 |
44 | consumer.subscribe(List.of(OKAFKA_TOPIC_NAME));
45 | while (true) {
46 | ConsumerRecords records = consumer.poll(Duration.ofSeconds(1));
47 |
48 | for (ConsumerRecord record : records) {
49 | String value = record.value();
50 | System.out.println("Consumed message: " + value);
51 | }
52 | // Blocking commit on the current batch of records. For non-blocking, use commitAsync()
53 | consumer.commitSync();
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/txeventq-examples/src/main/java/com/example/txeventq/Prompt.java:
--------------------------------------------------------------------------------
1 | package com.example.txeventq;
2 |
3 | import java.util.Scanner;
4 | import java.util.function.Consumer;
5 |
6 | public class Prompt implements Runnable {
7 | private final Consumer promptConsumer;
8 |
9 |
10 | public Prompt(Consumer promptConsumer) {
11 | this.promptConsumer = promptConsumer;
12 | }
13 |
14 |
15 | @Override
16 | public void run() {
17 | Scanner scanner = new Scanner(System.in);
18 | while (true) {
19 | System.out.print("> ");
20 |
21 | String ln = scanner.nextLine();
22 | if (ln.isEmpty()) {
23 | continue;
24 | }
25 | if (ln.equals("exit")) {
26 | return;
27 | }
28 | if (ln.startsWith("series")) {
29 | int series = Integer.valueOf(ln.split("series ")[1]);
30 | for (int i = 0; i < series; i++) {
31 | promptConsumer.accept("message #" + (i+1));
32 | }
33 | System.out.printf("Accepted %d series messages%n", series);
34 | } else {
35 | promptConsumer.accept(ln);
36 | }
37 | }
38 | }
39 |
40 | public static void prompt(Consumer promptConsumer) {
41 | new Prompt(promptConsumer).run();
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/txeventq-examples/src/main/java/com/example/txeventq/SpringJMSConsumer.java:
--------------------------------------------------------------------------------
1 | package com.example.txeventq;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 | import org.springframework.context.annotation.Profile;
6 | import org.springframework.jms.annotation.JmsListener;
7 | import org.springframework.stereotype.Component;
8 |
9 | import static com.example.txeventq.Values.JMS_QUEUE_NAME;
10 |
11 | @SpringBootApplication
12 | // run with spring.profiles.active=jms-consumer
13 | public class SpringJMSConsumer {
14 | public static void main(String[] args) {
15 | SpringApplication.run(SpringJMSConsumer.class, args);
16 | }
17 |
18 | @Component
19 | @Profile("jms-consumer")
20 | public static class Consumer {
21 | // Asynchronously receive messages from a given JMS queue
22 | @JmsListener(destination = JMS_QUEUE_NAME)
23 | public void receive(String message) {
24 | System.out.println("received message: " + message);
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/txeventq-examples/src/main/java/com/example/txeventq/SpringJMSProducer.java:
--------------------------------------------------------------------------------
1 | package com.example.txeventq;
2 |
3 | import jakarta.annotation.PostConstruct;
4 | import jakarta.jms.ConnectionFactory;
5 | import jakarta.jms.JMSException;
6 | import javax.sql.DataSource;
7 | import oracle.jakarta.jms.AQjmsFactory;
8 | import org.springframework.boot.SpringApplication;
9 | import org.springframework.boot.autoconfigure.SpringBootApplication;
10 | import org.springframework.context.annotation.Bean;
11 | import org.springframework.context.annotation.Profile;
12 | import org.springframework.jms.core.JmsTemplate;
13 | import org.springframework.stereotype.Component;
14 |
15 | import static com.example.txeventq.Prompt.prompt;
16 | import static com.example.txeventq.Values.JMS_QUEUE_NAME;
17 |
18 | @SpringBootApplication
19 | // run with spring.profiles.active=jms-producer
20 | public class SpringJMSProducer {
21 | public static void main(String[] args) {
22 | SpringApplication.run(SpringJMSProducer.class, args);
23 | }
24 |
25 | @Component
26 | @Profile("jms-producer")
27 | public static class Producer {
28 | private final JmsTemplate jmsTemplate;
29 |
30 | public Producer(JmsTemplate jmsTemplate) {
31 | this.jmsTemplate = jmsTemplate;
32 | }
33 |
34 | @PostConstruct
35 | public void init() {
36 | prompt((s) ->
37 | // Produce messages to a JMS queue
38 | jmsTemplate.convertAndSend(JMS_QUEUE_NAME, s)
39 | );
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/txeventq-examples/src/main/java/com/example/txeventq/Values.java:
--------------------------------------------------------------------------------
1 | package com.example.txeventq;
2 |
3 | public interface Values {
4 | String OKAFKA_TOPIC_NAME = "okafka_test";
5 | String JMS_QUEUE_NAME = "jms_test";
6 | }
7 |
--------------------------------------------------------------------------------
/txeventq-examples/src/main/resources/application-jms-consumer.yaml:
--------------------------------------------------------------------------------
1 | spring:
2 | datasource:
3 | oracleucp:
4 | connection-pool-name: JMSConsumer
5 | banner:
6 | location: classpath:banner-jms-consumer.txt
7 | server:
8 | port: 8999
--------------------------------------------------------------------------------
/txeventq-examples/src/main/resources/application-jms-producer.yaml:
--------------------------------------------------------------------------------
1 | spring:
2 | datasource:
3 | oracleucp:
4 | connection-pool-name: JMSProducer
5 | banner:
6 | location: classpath:banner-jms-producer.txt
7 |
8 | server:
9 | port: 8998
--------------------------------------------------------------------------------
/txeventq-examples/src/main/resources/application.yaml:
--------------------------------------------------------------------------------
1 | spring:
2 | datasource:
3 | username: ${USERNAME:testuser}
4 | password: ${PASSWORD:testpwd}
5 | url: ${JDBC_URL:jdbc:oracle:thin:@localhost:1521/freepdb1}
6 |
7 | # Set these to use UCP over Hikari.
8 | driver-class-name: oracle.jdbc.OracleDriver
9 | type: oracle.ucp.jdbc.PoolDataSource
10 | oracleucp:
11 | initial-pool-size: 1
12 | min-pool-size: 1
13 | max-pool-size: 30
14 | connection-pool-name: DefaultConnectionPool
15 | connection-factory-class-name: oracle.jdbc.pool.OracleDataSource
16 | logging:
17 | level:
18 | root: error
19 |
--------------------------------------------------------------------------------
/txeventq-examples/src/main/resources/banner-jms-consumer.txt:
--------------------------------------------------------------------------------
1 | _ __ __ ____ ____
2 | | | \/ / ___| / ___|___ _ __ ___ _ _ _ __ ___ ___ _ __
3 | _ | | |\/| \___ \ | | / _ \| '_ \/ __| | | | '_ ` _ \ / _ \ '__|
4 | | |_| | | | |___) | | |__| (_) | | | \__ \ |_| | | | | | | __/ |
5 | \___/|_| |_|____/ \____\___/|_| |_|___/\__,_|_| |_| |_|\___|_|
6 |
--------------------------------------------------------------------------------
/txeventq-examples/src/main/resources/banner-jms-producer.txt:
--------------------------------------------------------------------------------
1 | _ __ __ ____ ____ _
2 | | | \/ / ___| | _ \ _ __ ___ __| |_ _ ___ ___ _ __
3 | _ | | |\/| \___ \ | |_) | '__/ _ \ / _` | | | |/ __/ _ \ '__|
4 | | |_| | | | |___) | | __/| | | (_) | (_| | |_| | (_| __/ |
5 | \___/|_| |_|____/ |_| |_| \___/ \__,_|\__,_|\___\___|_|
6 |
--------------------------------------------------------------------------------
/txeventq-examples/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%kvp- %msg%n
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/txeventq-examples/src/main/resources/ojdbc.properties:
--------------------------------------------------------------------------------
1 | user = testuser
2 | password = testpwd
--------------------------------------------------------------------------------
/txeventq-examples/txeventq.sql:
--------------------------------------------------------------------------------
1 | -- Delete a Transactional Event Queue.
2 | begin
3 | dbms_aqadm.stop_queue(
4 | queue_name => 'sql_test'
5 | );
6 | dbms_aqadm.drop_transactional_event_queue(
7 | queue_name => 'sql_test'
8 | );
9 | end;
10 | /
11 |
12 | -- Create a Transactional Event Queue
13 | begin
14 | -- See https://docs.oracle.com/en/database/oracle/oracle-database/23/arpls/DBMS_AQADM.html#GUID-93B0FF90-5045-4437-A9C4-B7541BEBE573
15 | -- For documentation on creating Transactional Event Queues.
16 | dbms_aqadm.create_transactional_event_queue(
17 | queue_name => 'sql_test',
18 | -- Payload can be RAW, JSON, DBMS_AQADM.JMS_TYPE, or an object type.
19 | -- Default is DBMS_AQADM.JMS_TYPE.
20 | queue_payload_type => 'JSON'
21 | );
22 | -- Start the queue
23 | dbms_aqadm.start_queue(
24 | queue_name => 'sql_test'
25 | );
26 | end;
27 | /
28 |
29 | -- Procedure to produce a JSON event to the event_stream queue.
30 | create or replace procedure produce_json_event (
31 | event in json
32 | ) as
33 | enqueue_options dbms_aq.enqueue_options_t;
34 | message_properties dbms_aq.message_properties_t;
35 | msg_id raw(16);
36 | begin
37 | enqueue_options := dbms_aq.enqueue_options_t();
38 | message_properties := dbms_aq.message_properties_t();
39 | dbms_aq.enqueue(
40 | queue_name => 'sql_test',
41 | enqueue_options => enqueue_options,
42 | message_properties => message_properties,
43 | payload => event,
44 | msgid => msg_id
45 | );
46 | commit;
47 | end;
48 | /
49 |
50 | -- Procedure to consume a JSON event from the event_stream queue.
51 | create or replace function consume_json_event return json is
52 | dequeue_options dbms_aq.dequeue_options_t;
53 | message_properties dbms_aq.message_properties_t;
54 | msg_id raw(16);
55 | event json;
56 | begin
57 | dequeue_options := dbms_aq.dequeue_options_t();
58 | message_properties := dbms_aq.message_properties_t();
59 | dequeue_options.navigation := dbms_aq.first_message;
60 | dequeue_options.wait := dbms_aq.no_wait;
61 |
62 | dbms_aq.dequeue(
63 | queue_name => 'sql_test',
64 | dequeue_options => dequeue_options,
65 | message_properties => message_properties,
66 | payload => event,
67 | msgid => msg_id
68 | );
69 | return event;
70 | end;
71 | /
72 |
73 | -- Produce a JSON message.
74 | begin
75 | produce_json_event(json('{"content": "my first message"}'));
76 | end;
77 | /
78 |
79 | -- Consume a JSON message and send the 'content' field to server output.
80 | declare
81 | message json;
82 | message_buffer varchar2(500);
83 | begin
84 | message := consume_json_event();
85 | select json_value(message, '$.content') into message_buffer;
86 | dbms_output.put_line('message: ' || message_buffer);
87 | end;
88 | /
89 |
--------------------------------------------------------------------------------