- Understand JDBC URL structure for different databases
- Establish connections using DriverManager and DataSource
- Implement connection pooling with HikariCP
Connecting to a Database
The Art of Database Connections
Before you can query, insert, or update data, you need to establish a connection. Think of a database connection as a phone call — you dial (connect), have a conversation (execute queries), and hang up (close). Managing this lifecycle properly is crucial for application performance and stability.
The JDBC URL: Your Database Address
Every database connection starts with a JDBC URL — a special string that tells Java exactly where and how to connect.
URL Anatomy
jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC
└──┬─┘ └─┬─┘ └───┬────┘ └─┬┘ └─┬┘ └──────────────┬────────────┘
│ │ │ │ │ │
Protocol Subprotocol Host Port Database Parameters
URL Patterns for Popular Databases
| Database | JDBC URL Pattern | Example |
|---|---|---|
| MySQL | jdbc:mysql://host:port/database |
jdbc:mysql://localhost:3306/shop |
| PostgreSQL | jdbc:postgresql://host:port/database |
jdbc:postgresql://localhost:5432/shop |
| Oracle | jdbc:oracle:thin:@host:port:sid |
jdbc:oracle:thin:@localhost:1521:orcl |
| SQL Server | jdbc:sqlserver://host:port;database=name |
jdbc:sqlserver://localhost:1433;database=shop |
| H2 (Memory) | jdbc:h2:mem:database |
jdbc:h2:mem:testdb |
| H2 (File) | jdbc:h2:file:path |
jdbc:h2:file:./data/mydb |
| SQLite | jdbc:sqlite:path |
jdbc:sqlite:./data.db |
Establishing a Connection
The Classic Approach
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class DatabaseConnector {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/mydb";
String user = "root";
String password = "secret";
Connection connection = null;
try {
// Establish connection
connection = DriverManager.getConnection(url, user, password);
System.out.println("✓ Connected successfully!");
// Use the connection...
} catch (SQLException e) {
System.err.println("✗ Connection failed: " + e.getMessage());
} finally {
// Always close in finally block
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
The Modern Way: Try-With-Resources
Java 7+ introduced a much cleaner approach — resources are automatically closed:
String url = "jdbc:mysql://localhost:3306/mydb";
String user = "root";
String password = "secret";
try (Connection connection = DriverManager.getConnection(url, user, password)) {
System.out.println("✓ Connected successfully!");
// Use the connection...
// No need to explicitly close — it's automatic!
} catch (SQLException e) {
System.err.println("✗ Connection failed: " + e.getMessage());
}
// Connection is automatically closed here, even if an exception occurred
💡 Best Practice: Always use try-with-resources for JDBC connections, statements, and result sets. It prevents resource leaks.
Connection Properties
Sometimes you need to configure more than just URL, username, and password:
import java.util.Properties;
Properties props = new Properties();
props.setProperty("user", "root");
props.setProperty("password", "secret");
props.setProperty("useSSL", "true");
props.setProperty("serverTimezone", "UTC");
props.setProperty("autoReconnect", "true");
props.setProperty("characterEncoding", "UTF-8");
try (Connection conn = DriverManager.getConnection(url, props)) {
// Connected with custom properties
}
Common Connection Properties
| Property | Purpose | Example |
|---|---|---|
useSSL |
Enable/disable SSL encryption | true / false |
serverTimezone |
Set server timezone | UTC |
autoReconnect |
Reconnect if connection drops | true |
characterEncoding |
Character set for data | UTF-8 |
connectTimeout |
Connection timeout (ms) | 5000 |
socketTimeout |
Socket read timeout (ms) | 30000 |
Connection Validation
Before using a connection, especially from a pool, validate it:
try (Connection conn = DriverManager.getConnection(url, user, password)) {
// Method 1: isValid() - checks if connection is alive
if (conn.isValid(5)) { // 5 second timeout
System.out.println("Connection is valid");
}
// Method 2: Check if closed
if (!conn.isClosed()) {
System.out.println("Connection is open");
}
// Method 3: Run a test query
try (Statement stmt = conn.createStatement()) {
stmt.execute("SELECT 1");
System.out.println("Connection works!");
}
}
The Problem: Connection Overhead
Creating a new connection for every operation is expensive:
┌─────────────────────────────────────────────────────────────────┐
│ Connection Lifecycle │
├─────────────────────────────────────────────────────────────────┤
│ │
│ TCP Handshake → Authentication → Session Setup → Ready │
│ ~50ms ~20ms ~30ms │
│ │
│ Total: ~100ms per connection (before any actual query!) │
│ │
└─────────────────────────────────────────────────────────────────┘
For a web application handling 1000 requests/second, this overhead is unacceptable.
The Solution: Connection Pooling
Connection pools maintain a cache of reusable connections:
┌───────────────────────────────────────────────────────────────┐
│ Connection Pool │
├───────────────────────────────────────────────────────────────┤
│ │
│ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │
│ │Conn│ │Conn│ │Conn│ │Conn│ │Conn│ ← Pre-created │
│ │ 1 │ │ 2 │ │ 3 │ │ 4 │ │ 5 │ connections │
│ └────┘ └────┘ └────┘ └────┘ └────┘ │
│ │ ↑ │
│ ↓ │ │
│ [Borrow] [Return] │
│ │
│ Request → Get from pool → Use → Return to pool → Available │
│ (~1ms) (~0ms) │
│ │
└───────────────────────────────────────────────────────────────┘
HikariCP: The Fastest Pool
HikariCP is the industry standard for connection pooling:
<!-- Maven dependency -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>5.0.1</version>
</dependency>
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
// Configure the pool
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
config.setUsername("root");
config.setPassword("secret");
// Pool settings
config.setMaximumPoolSize(10); // Max connections
config.setMinimumIdle(5); // Min idle connections
config.setConnectionTimeout(30000); // 30 seconds
config.setIdleTimeout(600000); // 10 minutes
config.setMaxLifetime(1800000); // 30 minutes
// Create the data source
HikariDataSource dataSource = new HikariDataSource(config);
// Use it
try (Connection conn = dataSource.getConnection()) {
// Fast! Connection borrowed from pool
// Use connection...
}
// Connection returned to pool, not closed
// When application shuts down
dataSource.close();
Pool Size Guidelines
| Application Type | Min Pool | Max Pool |
|---|---|---|
| Small web app | 5 | 10 |
| Medium web app | 10 | 20 |
| High-traffic app | 20 | 50 |
🎯 Rule of Thumb:
connections = ((core_count * 2) + effective_spindle_count)
For most apps:10-20is a good starting point.
DataSource vs DriverManager
| Feature | DriverManager | DataSource |
|---|---|---|
| Connection Pooling | No | Yes |
| Configuration | In code | Externalized |
| JNDI Support | No | Yes |
| Production Use | Not recommended | Recommended |
DataSource Pattern
public class DatabaseConfig {
private static HikariDataSource dataSource;
static {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
config.setUsername("root");
config.setPassword("secret");
config.setMaximumPoolSize(10);
dataSource = new HikariDataSource(config);
}
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
public static void shutdown() {
if (dataSource != null) {
dataSource.close();
}
}
}
// Usage
try (Connection conn = DatabaseConfig.getConnection()) {
// Use connection
}
Handling Connection Errors
Be prepared for things to go wrong:
try (Connection conn = dataSource.getConnection()) {
// Work with database
} catch (SQLTimeoutException e) {
// Connection timeout
System.err.println("Connection timed out: " + e.getMessage());
} catch (SQLTransientConnectionException e) {
// Temporary issue - might succeed if retried
System.err.println("Temporary connection issue: " + e.getMessage());
} catch (SQLNonTransientConnectionException e) {
// Permanent issue - wrong credentials, host down, etc.
System.err.println("Connection error: " + e.getMessage());
} catch (SQLException e) {
// General SQL error
System.err.println("Database error: " + e.getMessage());
System.err.println("SQL State: " + e.getSQLState());
System.err.println("Error Code: " + e.getErrorCode());
}
JDBC URLs contain protocol, host, port, database. DriverManager is simple but not for production. DataSource supports connection pooling. HikariCP is fast and reliable. Always use try-with-resources for automatic cleanup. Configure pool sizes based on your app's needs. Handle connection errors gracefully. Validate connections before use.
