Accessing Databases Using JDBC
Core objects
DriverManager.getConnection(url, user, password) returns a Connection. JDBC 4+ auto-loads drivers from the classpath, so Class.forName is legacy. A URL looks like jdbc:subprotocol:subname (for example jdbc:postgresql://localhost:5432/shop). Statement runs literal SQL, PreparedStatement runs precompiled SQL with ? placeholders, and CallableStatement invokes stored procedures ({call proc(?, ?)} with registerOutParameter).
Executing
executeQuery returns a ResultSet (SELECT); executeUpdate returns an int row count for INSERT/UPDATE/DELETE and 0 for DDL; execute returns a boolean - true means a ResultSet is available via getResultSet(), false means an update count via getUpdateCount(). The cursor starts before the first row, and next() returns false once exhausted, so read columns only while it is true.
Traps
Column indexes start at 1; labels are case-insensitive. Prepared-statement parameter indexes are also 1-based and must be set before execution - index 0 throws SQLException. getObject handles any SQL type; getString converts, and wasNull() tells you whether the underlying SQL value was NULL. Close resources with try-with-resources in reverse acquisition order (ResultSet, Statement, Connection).
Transactions and metadata
setAutoCommit(false) opens a transaction; commit()/rollback() end it, and setSavepoint with rollback(savepoint) allows partial rollback. Connection.getMetaData() describes the database; ResultSet.getMetaData() describes its columns. Prefer a DataSource over DriverManager in application code, and remember SQLException is checked and exposes getSQLState() and getErrorCode().
Worked rule: if a query works in SQL but not in JDBC, check the 1-based indexes first - both parameters and columns count from 1.