├── .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 | --------------------------------------------------------------------------------