Connections and DataSource
Get connections through a DataSource, treat each one as a short-lived, single-threaded resource, and set timeouts before production finds them for you.
DataSource dataSource = ...; // built once at start-up
try (Connection conn = dataSource.getConnection()) {
// one unit of work
}
Connection raw = DriverManager.getConnection(url, user, password); // new socket each callThere are two ways to get a Connection. DriverManager.getConnection(url, user, password) opens a new physical connection on every call. javax.sql.DataSource is an interface with a getConnection() method, and what sits behind it is a configuration choice: a plain vendor class that also opens a fresh connection each time, or a pool that lends out one it already has.
Why services take a DataSource
Write repositories and services against DataSource and pass it in through the constructor. The query code then does not change when you move from a simple data source in a script to HikariCP in production, or to a container database in tests. It also keeps the URL and credentials in one place, built once at start-up from configuration, instead of scattered through the code.
A connection is not thread-safe
A Connection carries a socket, session settings and the current transaction. Two threads sharing one interleave their statements and commit each other's work. The usual mistake is opening a connection in a constructor and keeping it in a field. It works in a demo, breaks under concurrent requests, and dies for good the first time the database restarts or a firewall drops the idle socket.
Get a connection when a unit of work starts and close it when the work ends. With a pool, close() hands it back instead of disconnecting, so this is cheap.
Timeouts
Driver defaults are generous. The PostgreSQL driver's connectTimeout is 10 seconds and its socketTimeout is 0, meaning a read can wait forever if the server stops answering. Set a socket timeout, or setQueryTimeout(seconds) on individual statements, so a stuck query fails one request instead of holding a thread and a connection indefinitely.
Credentials
Read the URL, user and password from environment variables or a mounted secret, never from source code. The database user a web service connects as should have only the rights the service needs: no superuser, and no permission to drop tables.
Checking the connection
connection.isValid(seconds) asks the driver to confirm the connection is still alive, usually with a tiny query. A readiness probe should call it through the same DataSource the requests use, so the probe fails exactly when the application cannot reach its database.
Example
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.postgresql.ds.PGSimpleDataSource;
// Built once at start-up and passed to every repository.
final class Database {
private Database() {}
static DataSource fromEnvironment() {
PGSimpleDataSource ds = new PGSimpleDataSource();
ds.setURL(System.getenv("DB_URL")); // jdbc:postgresql://db:5432/shop
ds.setUser(System.getenv("DB_USER"));
ds.setPassword(System.getenv("DB_PASSWORD"));
ds.setApplicationName("orders-service"); // visible in pg_stat_activity
ds.setSocketTimeout(30); // seconds; 0 would wait forever
return ds;
}
}
final class OrderRepository {
private final DataSource dataSource; // a DataSource, not a Connection
OrderRepository(DataSource dataSource) {
this.dataSource = dataSource;
}
long countOpen() throws SQLException {
String sql = "SELECT count(*) FROM orders WHERE status = 'OPEN'";
try (Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement(sql);
ResultSet rs = ps.executeQuery()) {
rs.next();
return rs.getLong(1); // count(*) is bigint in PostgreSQL
}
}
}When to use it
- Build one DataSource from environment variables at start-up and inject it into every repository of an API.
- Back a Kubernetes readiness probe with Connection.isValid so traffic stops when the database is unreachable.
- Set a socket timeout so a hung database query fails one HTTP request instead of tying up a server thread forever.
More examples
DriverManager for a one-off script
DriverManager is fine for a migration script or a command-line tool that opens one connection and exits. Passing a Properties object keeps the password out of the URL, which tends to end up in logs.
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
final class ScriptConnection {
static Connection open() throws SQLException {
Properties props = new Properties();
props.setProperty("user", System.getenv("DB_USER"));
props.setProperty("password", System.getenv("DB_PASSWORD"));
props.setProperty("connectTimeout", "5"); // PostgreSQL driver option, in seconds
return DriverManager.getConnection(System.getenv("DB_URL"), props);
}
}A database readiness check
A /ready endpoint can return 200 or 503 from isUp(). Going through the shared DataSource means the check also notices an exhausted pool, not only a dead database.
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
public final class DatabaseHealth {
private final DataSource dataSource;
public DatabaseHealth(DataSource dataSource) {
this.dataSource = dataSource;
}
public boolean isUp() {
try (Connection conn = dataSource.getConnection()) {
return conn.isValid(2); // wait at most 2 seconds
} catch (SQLException e) {
return false; // log e, but do not expose it to the caller
}
}
}The mistake: one connection for the whole service
This compiles and passes a single-user demo, which is why it reaches production. Replace the Connection field with a DataSource field and borrow a connection inside each method.
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
// Do not do this.
public class UserService {
private final Connection conn; // shared by every request thread
public UserService(String url, String user, String password) throws SQLException {
this.conn = DriverManager.getConnection(url, user, password);
}
// Two requests running this at once share one transaction and one socket.
// After a database restart, every call fails until the process restarts.
}
Discussion