Database Per Service with Oracle AI Database PDBs

Using Oracle AI Database Pluggable Databases (PDBs) to give each Spring Boot service its own clear data boundary.

Key Takeaways

  • Learn database-per-service design with separate studentpdb and coursepdb PDBs.
  • Each Spring Boot service owns its own schema, tables, JDBC URL, and connection pool.
  • The runner composes student and course data through HTTP APIs, without shared schemas or cross-PDB joins.
  • The project can be run with Testcontainers or manually with Docker and Maven.

PDBs are a natural fit for microservices that need hard data ownership without turning every service into a separate database install.

In Why You Should Use Pluggable Databases (PDBs), I walked through the basic mechanics of creating and using PDBs. This post takes the next step: a runnable database-per-service sample with two Spring Boot services, two PDBs, and one small workflow that composes data at the application layer.

The sample is intentionally small. A student wants to register for a course. The students service owns the student profile and completed courses. The courses service owns the catalog, prerequisites, and offerings. The sample runner calls both services over HTTP and decides whether the student is eligible.

No shared schema. No cross-PDB join. No database link. Download the code sample here -> database-per-service example code

Why Use Database Per Service at all?

PDBs let you enforce the common constraints of the database-per-service microservices pattern:

  • Security is scoped to the PDB and local schema.
  • Connections route to the service-owned database service.
  • Lifecycle operations can target a PDB instead of a full database install.
  • Operational blast radius is smaller than a shared-schema design.
  • Resource management can be applied at the PDB level when the sample grows into a real workload.

This doesn’t magically make distributed systems simple, but it imposes a few guarantees: Applications own the composition logic, and database topology cleanly maps to service ownership.

Sample Description

The repository has three modules, students, courses, and sample:

database-per-service-example/
├── students/
├── courses/
└── sample/

The courses and students modules contain JPA entities and controllers for their respective data.

The sample module contains the runner and end-to-end test. DatabasePerServiceSampleRunner loads scenarios from a JSON file, creates a student through the students service, creates prerequisite and target courses through the courses service, and evaluates:

  • student status is ACTIVE
  • required prerequisites are present in the completed course list
  • the requested offering has available seats

The runner uses the Student and Course objects returned from the create calls. That keeps the sample tight and avoids unnecessary round trips just to re-fetch objects it already has.

Flowchart illustrating the registration scenario process in an Oracle AI database sample, detailing steps to load data, create a student and courses, process information, verify eligibility, and print the scenario summary.

PDB Layout

The sample runs two service-owned PDBs inside one Oracle AI Database Free container:

FREE CDB
├── studentpdb
│   └── students_app
│       ├── students
│       └── student_completed_courses
└── coursepdb
    └── courses_app
        ├── course_catalog
        ├── course_prerequisites
        └── course_offerings

Each service gets a separate JDBC URL:

jdbc:oracle:thin:@localhost:1521/studentpdb
jdbc:oracle:thin:@localhost:1521/coursepdb

The services don’t just get a separate username: they have their own pluggable database, with its own schema, connection pool, and database connection string.

The setup script starts from CDB$ROOT, creates the PDBs, opens them, saves their state, switches into each PDB, and creates the local application user and tables.

ALTER SESSION SET CONTAINER = CDB$ROOT;

CREATE PLUGGABLE DATABASE studentpdb
    ADMIN USER studentpdb_admin IDENTIFIED BY testpwd
    FILE_NAME_CONVERT = (
        '/opt/oracle/oradata/FREE/pdbseed/',
        '/opt/oracle/oradata/FREE/studentpdb/'
    );

CREATE PLUGGABLE DATABASE coursepdb
    ADMIN USER coursepdb_admin IDENTIFIED BY testpwd
    FILE_NAME_CONVERT = (
        '/opt/oracle/oradata/FREE/pdbseed/',
        '/opt/oracle/oradata/FREE/coursepdb/'
    );

ALTER PLUGGABLE DATABASE studentpdb OPEN;
ALTER PLUGGABLE DATABASE coursepdb OPEN;
ALTER PLUGGABLE DATABASE studentpdb SAVE STATE;
ALTER PLUGGABLE DATABASE coursepdb SAVE STATE;

The full setup script is in database-per-service-example/sample/src/test/resources/create-pdbs.sql.

Run the End-to-End Test

Diagram illustrating the setup for end-to-end testing using JUnit and Oracle AI Database. It includes components like JUnit for testing, Oracle AI Database container, and provisioning of PDBs with specified applications and services on free ports.

The fastest way to run the sample is with the self-contained Testcontainers test, DatabasePerServiceSampleRunnerTest.java:

mvn test -pl sample -am

The test runs the full sample flow:

  1. Start an Oracle AI Database Free container.
  2. Copy create-pdbs.sql into the container.
  3. Creates studentpdb and coursepdb.
  4. Starts the students and courses Spring Boot apps on free ports.
  5. Runs the sample runner against both services.
  6. Verifies eligible and ineligible scenarios.

Expected output looks like this:

Scenario: student-is-eligible
  Student: Alice Nguyen (ACTIVE)
  Course: ELIGIBLE-CS404 - Distributed Systems
  Completed courses: ELIGIBLE-CS201, ELIGIBLE-MATH101
  Prerequisites satisfied: true
  Seats available: true
  Eligible: true
  Reasons: Registration checks passed
  Assertions passed

Scenario: student-missing-prereq
  Student: Alice Nguyen (ACTIVE)
  Course: MISSINGPREREQ-CS404 - Distributed Systems
  Completed courses: MISSINGPREREQ-MATH101
  Prerequisites satisfied: false
  Seats available: true
  Eligible: false
  Reasons: Student is missing at least one required prerequisite
  Assertions passed

Summary

PDBs let you give each service a real database boundary, implementing service isolation within the same container database instance:

  • students can only see studentpdb.
  • courses can only see coursepdb.
  • the runner composes the workflow through HTTP APIs.
  • the database layout reinforces the service boundary instead of relying on convention.

FAQs

What does the sample demonstrate?

A course registration workflow where student data and course data live in separate PDBs.

Why use PDBs here?

PDBs implement strict services boundaries, not just naming conventions.

How is student-course eligibility verified?

The runner checks student status, completed prerequisites, and available seats.

How do I run it?

Use mvn -pl sample -am test, or start Oracle AI Database Free with Docker and run the services with Maven.

Multitenant Licensing

To use more than 3 PDBs per CDB on enterprise edition, you may require additional licensing. See Oracle’s Multitenant Licensing page for details.

Responses

  1. […] Database Per Service with Oracle AI Database PDBs – Anders Swanson uses pluggable databases (PDBs) to give each microservice its own clear data […]

  2. Blair Avatar

    This approach of course limits me to a total of 3 services unless I now pay for the multitenant license … so I just paid a 35% tax on my data to get microservices.

    1. Anders Swanson Avatar

      Yeah, not thrilled with that limitation either. I added the multi-tenant licensing page to the article references so it’s not a “gotcha”.

Leave a Reply

Discover more from andersswanson.dev

Subscribe now to keep reading and get access to the full archive.

Continue reading