This episode covers database access in Java: basic JDBC with connections, statements, and result sets, connection pooling and resource cleanup, an introduction to JPA and Hibernate as modern ORMs, entity mapping, the repository pattern, and transaction management.

Almost every production application stores data in a database. Episode 10 covers database access and persistence in Java — starting from JDBC as the foundation, then moving up to JPA and Hibernate as modern ORMs. You will understand both levels: full control and high productivity.
JDBC (Java Database Connectivity) is the standard API for relational database access. Add the appropriate database driver — for example PostgreSQL:
mvn dependency:get -Dartifact=org.postgresql:postgresql:42.7.4Open a connection with DriverManager, then run a query with Statement:
import java.sql.*;
public class DemoJdbc {
public static void main(String[] args) throws SQLException {
String url = "jdbc:postgresql://localhost:5432/toko";
try (Connection conn = DriverManager.getConnection(url, "user", "password");
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT id, nama FROM produk")) {
while (rs.next()) {
System.out.println(rs.getLong("id") + " - " + rs.getString("nama"));
}
}
}
}DriverManager.getConnection(url, "user", "password") opens a connection. Try-with-resources closes the connection, statement, and result set automatically.
Use PreparedStatement for parameterized queries — this prevents SQL injection:
String sql = "SELECT * FROM produk WHERE harga > ?";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setDouble(1, 50000);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
System.out.println(rs.getString("nama"));
}
}
}Opening a database connection for every query is very expensive. Connection pooling stores a set of connections that are reused. HikariCP is the most popular pool and the default in Spring Boot:
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
public class Pool {
public static void main(String[] args) {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:postgresql://localhost:5432/toko");
config.setUsername("user");
config.setPassword("password");
config.setMaximumPoolSize(10);
HikariDataSource ds = new HikariDataSource(config);
System.out.println("Pool siap dengan " + ds.getMaximumPoolSize() + " koneksi");
}
}The golden rule: close in reverse order — ResultSet, then Statement, then Connection. With try-with-resources this is handled automatically. Never leak connections; a drained pool makes the application appear hung.
JPA (Jakarta Persistence API) is the ORM (Object-Relational Mapping) specification. Hibernate is the most popular JPA implementation. With ORM, you map database tables to Java objects so you do not write manual SQL.
Add Hibernate and the driver:
mvn dependency:get -Dartifact=org.hibernate.orm:hibernate-core:6.5.2.FinalAn entity is a class mapped to a table. Use the annotations @Entity, @Table, @Id, and @GeneratedValue:
import jakarta.persistence.*;
@Entity
@Table(name = "produk")
public class Produk {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String nama;
private double harga;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getNama() { return nama; }
public void setNama(String nama) { this.nama = nama; }
public double getHarga() { return harga; }
public void setHarga(double harga) { this.harga = harga; }
}@GeneratedValue(strategy = GenerationType.IDENTITY) makes the id auto-generated by the database.
The repository pattern abstracts data access into a clean interface. In Spring Data JPA, repositories are provided automatically:
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface ProdukRepository extends JpaRepository<Produk, Long> {
List<Produk> findByHargaGreaterThan(double harga);
}The findByHargaGreaterThan method is implemented automatically based on its name.
A transaction ensures that a group of database operations runs atomically — either all succeed or all are rolled back. The @Transactional annotation groups the operations:
import org.springframework.transaction.annotation.Transactional;
public class TransferService {
@Transactional
public void transfer(Long dariId, Long keId, double jumlah) {
// kurangi saldo dariId
// tambah saldo keId
// jika satu gagal, keduanya dibatalkan
}
}@Transactional guarantees automatic rollback when an exception occurs, keeping data consistent.
Episode 10 teaches database access: JDBC with connections, statements, and result sets, connection pooling with HikariCP, JPA and Hibernate as ORMs, entity mapping with annotations, the repository pattern, and transaction management with @Transactional.
Key takeaways:
PreparedStatement prevents SQL injection on parameterized queries.@Entity, @Id, and @GeneratedValue.@Transactional guarantees atomic database operations.In the next episode, episode 11, we will discuss configuration and environment variables — application configuration with properties files, YAML, and environment variables, using java.util.Properties and modern libraries, handling multiple environment configurations, and best practices for storing secrets and credentials. Time to make an easily configurable application!