CRUD Operations
CRUD Operations
CRUD — Create, Read, Update, Delete — is the backbone of nearly every database-driven application. In this lesson we take these four operations from first principles through production-grade patterns, examining the trade-offs at every step. You already know how to open a Connection and create a Statement; here we connect those pieces into real, reliable data-access code.
The Setup: a Sample Table
All examples in this lesson work against a simple products table:
We will use a helper method that returns a connection from a pool or driver manager. For now assume getConnection() is available.
Create — Inserting Rows
Always use PreparedStatement for inserts that include user-supplied data. The key point here is how to retrieve the auto-generated primary key after the insert:
affected == 0? executeUpdate() returns the row count. If a trigger or a database-level constraint silently suppressed the insert, the count would be 0 and your code would try to read a key that does not exist. Checking early makes failures loud and obvious.
Calling the method and using the returned id:
Read — Querying Rows
Reading data involves executing a SELECT and iterating the ResultSet. Two common patterns are: fetch a single row by primary key, or fetch a collection filtered by criteria.
Fetch one row by id:
ResultSet. Index-based access (rs.getString(2)) breaks silently if the SELECT list is reordered. Name-based access (rs.getString("name")) is self-documenting and resilient to query changes.
Fetch a list with a filter:
Note the >= comparison: inside a code-block the angle brackets must be entity-escaped so the browser renders them correctly, which is why the source says >=. This is a display concern, not a JDBC concern — the SQL string in your Java source code uses a plain >=.
Update — Modifying Existing Rows
Updates follow the same prepared-statement pattern. Always return the affected row count so the caller can verify the row actually existed:
Usage pattern that distinguishes "not found" from other errors:
"UPDATE products SET price = " + price + " WHERE id = " + id is vulnerable to SQL injection. Always bind values through prepared-statement parameters.
Delete — Removing Rows
Deletes are structurally identical to updates: a PreparedStatement with a WHERE clause, returning the affected row count:
Putting It All Together — A Mini CRUD Demo
Here is a short, self-contained demo that exercises every operation in sequence so you can see how they compose:
Key Trade-offs and Best Practices
- Close resources in reverse order. Always close
ResultSetbeforePreparedStatementbeforeConnection. Using nested try-with-resources guarantees this automatically. - Money columns: use
BigDecimal, notdouble.rs.getBigDecimal("price")preserves exact decimal values;getDoubleintroduces floating-point rounding errors that accumulate over time. - Check affected row counts. A count of 0 on an update or delete means the WHERE clause matched nothing — that is usually a bug, not a success.
- Single-responsibility methods. Each method does one thing (find, insert, update, delete). This keeps logic testable and makes it easy to swap the SQL later without touching callers.
- Let exceptions propagate. Catching
SQLExceptionat the data-access layer and swallowing it hides bugs. Either propagate it or wrap it in an unchecked domain exception (DataAccessException) so the caller can decide how to handle it.
Summary
Every CRUD operation follows the same three-step rhythm: prepare the SQL with placeholders, bind the parameters, execute and check the result. For inserts, capture the generated key with RETURN_GENERATED_KEYS. For reads, prefer column names over indexes and map rows to domain objects in a dedicated helper. For updates and deletes, inspect the affected row count to distinguish "row not found" from a successful zero-change operation. In the next lesson we will group multiple CRUD operations into atomic database transactions.