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
studentpdbandcoursepdbPDBs. - 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.

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_offeringsEach service gets a separate JDBC URL:
jdbc:oracle:thin:@localhost:1521/studentpdb
jdbc:oracle:thin:@localhost:1521/coursepdbThe 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

The fastest way to run the sample is with the self-contained Testcontainers test, DatabasePerServiceSampleRunnerTest.java:
mvn test -pl sample -amThe test runs the full sample flow:
- Start an Oracle AI Database Free container.
- Copy
create-pdbs.sqlinto the container. - Creates
studentpdbandcoursepdb. - Starts the
studentsandcoursesSpring Boot apps on free ports. - Runs the sample runner against both services.
- 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 passedSummary
PDBs let you give each service a real database boundary, implementing service isolation within the same container database instance:
studentscan only seestudentpdb.coursescan only seecoursepdb.- 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.

Leave a Reply