Learn Spring JPA By Example With Oracle Database Free

Learn Spring Boot Data JPA by example

Spring JPA is a module of the Spring Data project that provides an abstraction over the Java Persistence API (JPA) for interacting with relational databases. Spring JPA simplifies data access, streamlining database persistence by using repository interfaces for CRUD operations.

In this article, you’ll learn how to configure and use Spring JPA with Oracle Database Free. By the end of the article, you’ll have a simple, working example that uses JPA to manage an Oracle Database table.

// ... initialize student data
Student newStudent = new Student();

// Create a new student using the student repository, saving it to the "student" table.
Student saved = studentRepository.save(newStudent);

Prefer code over an article? Check out my Spring JPA sample on GitHub for comprehensive sample code.

Project Dependencies

Add the following Maven dependencies to your pom.xml, using Oracle’s Spring Boot Starter for UCP (Universal Connection Pool) to pull in driver and connection pool dependencies.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>com.oracle.database.spring</groupId>
    <artifactId>oracle-spring-boot-starter-ucp</artifactId>
    <version>${oracle.starters.version}</version>
</dependency>

<!-- Include if using Oracle Database Wallet for TLS connections -->
<!--        <dependency>-->
<!--            <groupId>com.oracle.database.spring</groupId>-->
<!--            <artifactId>oracle-spring-boot-starter-wallet</artifactId>-->
<!--            <version>${oracle.starters.version}</version>-->
<!--        </dependency>-->

If you’re using Gradle:

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation "com.oracle.database.spring:oracle-spring-boot-starter-ucp:${oracleStartersVersion}"

    // Uncomment if using Oracle Database Wallet for TLS connections
    // implementation "com.oracle.database.spring:oracle-spring-boot-starter-wallet:${oracleStartersVersion}"
}

Application Properties for JPA

Spring JPA properties are configured similarly to other Spring Data applications, by setting up a datasource. We do so for Oracle Database in the following application.yaml file.

There are a few custom configuration options of note:

  • spring.jpa.show-sql ; When set to true, generated SQL statements are logged. This is extremely useful for debugging, be should be disabled in production instances.
  • spring.jpa.hibernate.ddl-auto ; When set to none, JPA will not autogenerate database schemas. You may use JPA to generate your schema from Java classes by setting this property to another value, but it’s not recommended outside of test and throwaway uses cases. Manually crafting your DML avoids potential surprises from JPA schema auto-generation, and maintains strict control of the table structure.
  • UCP Configuration ; We configure the database connection pool to use Oracle UCP instead of Hikari. I suggest reading more about UCP vs. Hikari in this article.
spring:
  jpa:
    # Prints SQL generated by JPA.
    show-sql: true
    hibernate:
      # We are running our own DDL scripts for strict schema control.
      ddl-auto: none

  datasource:
    username: ${USERNAME}
    password: ${PASSWORD}
    url: ${JDBC_URL}

    # Set these to use UCP over Hikari.
    driver-class-name: oracle.jdbc.OracleDriver
    type: oracle.ucp.jdbc.PoolDataSourceImpl
    oracleucp:
      initial-pool-size: 1
      min-pool-size: 1
      max-pool-size: 30
      connection-pool-name: UCPSampleApplication
      connection-factory-class-name: oracle.jdbc.pool.OracleDataSource

Spring Boot Main Class

We can’t forget our Spring Boot main class, annotated with @SpringBootApplication:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Defining a Database Schema for Spring JPA

To get started, we’ll use a simple single-table schema. Our example schema defines a student and their related data. We’ll build a Java object around this schema which will be leveraged by Spring JPA:

create table student (
id raw(16) default sys_guid() primary key,
first_name varchar2(50) not null,
last_name varchar2(50) not null,
email varchar2(100),
major varchar2(20) not null,
credits number(10),
gpa number(3,2) check (gpa between 0.00 and 4.00)
);

Implementing the JPA Entity and Repository

A JPA Entity is a Java object that represents a table (and potentially its relationships) in a database. Each instance of the class corresponds to a row in the table, and its fields map to the table’s columns. JPA entities are used to persist data to and retrieve data from a database using the Java Persistence API.

Student Entity:

import java.util.UUID;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

@Entity
@Table(name = "STUDENT")
public class Student {
    // The primary key of the default
    @Id
    // Auto-generate the ID value, in this case a UUID.
    @GeneratedValue(strategy = GenerationType.UUID)
    @Column(name = "id")
    private UUID id;
    @Column(name = "first_name") // Customize column names as appropriate for Java objects.
    private String firstName;
    @Column(name = "last_name")
    private String lastName;
    private String email;
    private String major;
    private double credits;
    private double gpa;

    public Student() {}

    public Student(String firstName, String lastName, String email, String major, double credits, double gpa) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.email = email;
        this.major = major;
        this.credits = credits;
        this.gpa = gpa;
    }

    // Getters and setters
}

A JPA Repository is an interface in Spring JPA that provides a set of ready-to-use methods for performing common database operations on JPA entities (such as CRUD). By extending the JpaRepository interface, you gain access to these methods without writing much code:

import java.util.UUID;

import com.example.model.Student;
import org.springframework.data.jpa.repository.JpaRepository;

public interface StudentRepository extends JpaRepository<Student, UUID> {
}

Trying it out with Testcontainers and Oracle Database Free

We’ll write a simple test for our JPA Entity and Repository to verify we’ve configured the application correctly for Oracle Database. The test will use Testcontainers and Oracle Database Free to run locally on your development machine, provided you have a Docker-compatible environment capable of running containers.

Add the following dependencies for Testcontainers to your project:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-testcontainers</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>testcontainers-oracle-free</artifactId>
    <version>${testcontainers.version}</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>testcontainers-junit-jupiter</artifactId>
    <scope>test</scope>
</dependency>

If you’re using Gradle:

dependencies {
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework.boot:spring-boot-testcontainers'
    testImplementation "org.testcontainers:testcontainers-oracle-free:${testcontainersVersion}"
    testImplementation 'org.testcontainers:testcontainers-junit-jupiter'
}

Test Implementation

The SpringJPATest class spins up an Oracle Database container, initializes it with the Student DML, and verifies that the JPA repository is working as expected:

package com.example;

import java.time.Duration;
import java.util.Optional;

import com.example.model.Student;
import com.example.repository.StudentRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.oracle.OracleContainer;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

@SpringBootTest
@Testcontainers
public class SpringJPATest {
    @Container
    @ServiceConnection
    static OracleContainer oracleContainer = new OracleContainer("gvenzl/oracle-free:23.6-slim-faststart")
            .withStartupTimeout(Duration.ofMinutes(2))
            .withUsername("testuser")
            .withPassword("testpwd")
            .withInitScript("student.sql");

    // Autowire JPA repositories
    @Autowired
    StudentRepository studentRepository;

    @Test
    void crudExample() {
        Student s = new Student();
        s.setFirstName("John");
        s.setLastName("Doe");
        s.setCredits(60);
        s.setMajor("Computer Science");
        s.setEmail("john.doe@example.edu");
        s.setGpa(3.77);

        // Create a new student using the student repository.
        Student saved = studentRepository.save(s);
        assertThat(saved.getId()).isNotNull();

        // Update the student credits and GPA.
        saved.setCredits(64);
        saved.setGpa(3.79);
        studentRepository.save(saved);
        studentRepository.flush();

        // Verify the student was updated successfully.
        Optional<Student> byId = studentRepository.findById(saved.getId());
        assertTrue(byId.isPresent());
        assertThat(byId.get().getCredits()).isEqualTo(64);
        assertThat(byId.get().getGpa()).isEqualTo(3.79);

        studentRepository.deleteById(saved.getId());
        assertFalse(studentRepository.findById(saved.getId()).isPresent());
    }
}

You can run the test from the spring-jpa directory of my Oracle Database Java Samples repository, like so:

mvn test -Dtest=SpringJPATest

The output should be similar to the following:


// ... Testcontainers and Spring Boot startup logging

Hibernate: insert into student (credits,email,first_name,gpa,last_name,major,id) values (?,?,?,?,?,?,?)
Hibernate: select s1_0.id,s1_0.credits,s1_0.email,s1_0.first_name,s1_0.gpa,s1_0.last_name,s1_0.major from student s1_0 where s1_0.id=?
Hibernate: update student set credits=?,email=?,first_name=?,gpa=?,last_name=?,major=? where id=?
Hibernate: select s1_0.id,s1_0.credits,s1_0.email,s1_0.first_name,s1_0.gpa,s1_0.last_name,s1_0.major from student s1_0 where s1_0.id=?
Hibernate: select s1_0.id,s1_0.credits,s1_0.email,s1_0.first_name,s1_0.gpa,s1_0.last_name,s1_0.major from student s1_0 where s1_0.id=?
Hibernate: delete from student where id=?
Hibernate: select s1_0.id,s1_0.credits,s1_0.email,s1_0.first_name,s1_0.gpa,s1_0.last_name,s1_0.major from student s1_0 where s1_0.id=?
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 40.68 s -- in com.example.SpringJPATest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 45.474 s

[INFO] Finished at: 2025-01-13T13:05:22-08:00
[INFO] ------------------------------------------------------------------------

That’s all for now! Stay tuned for follow up articles that dive into the features of Spring JPA.

Questions? Let me know in the comments, or reach out on LinkedIn!

Leave a Reply

Discover more from andersswanson.dev

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

Continue reading