JDBC &Transactions

A self-contained study reference for Java Database Connectivity and database transactions, built from the session deck (49 slides). Four parts: a copy-by-hand notebook cheat sheet, detailed study notes, runnable code samples, and a graded hands-on lab. Anything added or corrected beyond the source deck is marked [+] .

Java 8+ (java.sql) MySQL driver standard library only 49-slide source

Note: the live page I was given exposed only its rendered text, not its source HTML — so this is a fresh rebuild of the styling rather than a copy of yours. All existing content is preserved and extended with full runnable code and a lab.

I

Notebook Summary — copy by hand

Fast-recall cheat sheet. Compact monospace blocks meant for transcription into a physical notebook. Comparison concepts are in tables; doodle: lines describe diagrams to sketch.

                        ═══ JDBC BASICS ═══
                        JDBC = Java DataBase Connectivity
  • standard API  → access SQL DBs from Java code
  • also a SPEC   → tells vendors how to write drivers
  • does 3 things : connect → send SQL → process results
  • vendor-neutral: swap DB, app logic stays the same

ARCHITECTURE (flow):
  App → JDBC API → DriverManager → Driver → DB
    DriverManager → routes by JDBC URL, picks the driver
    Driver        → vendor-supplied; JDBC calls → native DB calls
  doodle: [App]→[JDBC API]→[DriverManager]→[Driver]→(DB cylinder)
                        aid: JDBC = universal power adapter ; driver = the plug
  ⚠ DriverManager ≠ Driver (mgr is std lib; driver is vendor jar)
                    
                        ═══ 6 CONNECTION STEPS ═══
                        1. load driver  → Class.forName("oracle.jdbc.driver.OracleDriver")
2. build URL    → jdbc:<protocol >:<details >3. connect      → DriverManager.getConnection(url[,user,pwd]) → Connection
4. execute      → Statement: executeQuery OR executeUpdate
5. process      → walk ResultSet
6. close        → release! (else CONNECTION LEAK → pool dries up)

DRIVER CLASSES                       JDBC URLs
  com.mysql.cj.jdbc.Driver  → MySQL    MySQL  → jdbc:mysql://localhost:3306/demodb
  oracle.jdbc.driver.       → Oracle   Oracle → jdbc:oracle:thin@host:1521:demodb
    OracleDriver                       MSSQL  → jdbc:odbc:DemoDSN
  sun.jdbc.odbc.JdbcOdbc    → ODBC

ERRORS
  SQLException           → bad url / credentials
  ClassNotFoundException → driver jar not on classpath
  doodle: 6 stacked boxes, brackets {1-3}=setup {6}=cleanup
                    
                        ═══ EXECUTE &RESULTSET ═══
                        executeQuery()  → SELECT            → returns ResultSet
executeUpdate() → INSERT/UPDATE/DEL → returns int (rows affected, =1 ok)

ResultSet = rows/cols from SELECT ; cursor-based
  rs.next()        → move fwd 1 row ; true if more rows, false at end
  getXXX(colName)  → read by column name
  getXXX(colIndex) → 1-BASED (first col = 1, NOT 0!)
  ⚠ only 1 ResultSet open per Statement at a time

loop: while(rs.next()){ rs.getInt("id"); rs.getString("name"); }
  aid: ResultSet = cursor on a list ; next() steps down, stops at false
  doodle: vertical list of rows, ▸ arrow pointing at "current row"
                    

CRUD ↔ method map

Operation SQL Method Returns
Read SELECT executeQuery() ResultSet
Create INSERT executeUpdate() int rows
Update UPDATE executeUpdate() int rows
Delete DELETE executeUpdate() int rows
                        ═══ PREPARED STATEMENT ═══
                        PreparedStatement = precompiled SQL with ? placeholders
  ...WHERE salary >? AND department = ?
  → ps.setInt(1, 80000); ps.setString(2, "Legal");

BENEFITS
  • easier to set param values (typed setters)
  • PREVENTS SQL INJECTION   ★ key interview answer
  • may improve perf (compiled once, plan reused)
  works for INSERT / UPDATE / DELETE too, not just SELECT

NULL: ps.setNull(i, Types.X) to set ; rs.wasNull() to detect [+]
  aid: fill-in-the-blank form → blanks can't smuggle SQL
  doodle: a form with [____] blanks, values dropping into them
                    
Statement PreparedStatement
SQL full string template with ?
Compilation each execution precompiled once
SQL injection vulnerable protected
Repeat perf lower higher (reused plan)
Best for static one-off parameterized / repeated
                        ═══ CONFIG &METADATA ═══
                        CONFIG: don't hardcode creds → externalize
  java.util.Properties → read/write key=value config file
  db.url=... db.user=... → Properties.load() at startup

METADATA = "data about the data"
  DatabaseMetaData   ← connection.getMetaData()
    getDatabaseProductName / getDriverName / getTables / getColumns
  ResultSetMetaData  ← rs.getMetaData()
    getColumnCount / getColumnName / getColumnType
    isAutoIncrement / isNullable

API PACKAGES: java.sql + javax.sql
  javax.sql.DataSource → connection pooling (modern, preferred) [+]
                    
                        ═══ TRANSACTIONS ═══
                        Transaction = multiple ops as 1 logical unit → ALL or NOTHING
  = atomicity (the A in ACID: Atomicity Consistency Isolation Durability) [+]

EX: transfer $5  A → B
  begin → read A → A−5 → read B → B+5
  crash after A−5 but before B+5  → $5 VANISHES
  txn wraps all 5 → either all stick or none do
  doodle: 2 buckets A &B, arrow A→B, dotted box around all = 1 unit
                        END A TXN
  COMMIT   → finalize all stmts, permanent, visible to others
  ROLLBACK → abort, discard uncommitted, revert to original
  aid: COMMIT = Save-all ; ROLLBACK = Undo-all

TCL (Transaction Control Language)
  COMMIT    → make permanent
  ROLLBACK  → undo all
  SAVEPOINT → checkpoint ; ROLLBACK TO sp = partial undo [+]
  doodle: timeline ──•sp1──✗ , curved arrow back to sp1 = ROLLBACK TO
                        JDBC TXN MGMT
  ⚠ auto-commit = ON by default (each stmt commits itself!)
  con.setAutoCommit(false);          // begin
  try   { ...updates...; con.commit(); }   // all ok
  catch { con.rollback(); }                // failure → undo all
                    
II

Detailed Study Notes

One numbered subsection per concept. Each opens with a one-line claim, then a from-scratch explanation, then callouts. [+] marks anything added or corrected beyond the deck. Each links to its matching code sample.

1. What JDBC is

JDBC (Java Database Connectivity) is a standard Java API for talking to SQL databases from Java code.

It does three things: establishes a connection to a database, sends SQL statements to it, and processes the results that come back. JDBC is also a specification — a contract telling third-party database vendors (Oracle, MySQL, etc.) how to write their drivers so any Java program can talk to their specific database using the same standard API.

Intuition Think of JDBC as a universal power adapter. Your Java code is the appliance; every database is a differently-shaped wall socket. JDBC defines the adapter's shape on the Java side, and each vendor ships a plug (the driver) that fits both your code and their socket. You write to one API; you can swap the database underneath with minimal code change.
Why it matters Without a standard like JDBC, you'd rewrite your data-access code every time you switched databases. JDBC gives you transparent connectivity to heterogeneous databases — application logic stays the same on MySQL or Oracle.

Real-world. Almost every Java backend that stores data — a banking app, an e-commerce inventory system, a payroll tool — sits on JDBC, either directly or under a higher-level framework (Spring's JdbcTemplate , or an ORM like Hibernate, which use JDBC internally). [+]

2. JDBC architecture

JDBC sits in two layers: the JDBC API your code calls, and the driver layer that translates those calls for a specific database.

Described diagram. Java App → JDBC API → DriverManager → (chooses) → Vendor Driver → Database. The DriverManager is the switchboard in the middle; it routes you to the correct driver.

Why it matters This layering is exactly why you stay vendor-neutral. Your code never speaks the database's native protocol — the driver does that translation, so the same API reaches many databases through a driver manager + database-specific drivers.
Common misconception The DriverManager is not the driver. DriverManager is part of the standard JDBC library; the driver is the vendor-specific jar you add to your classpath. [+]

3. The six connection steps

Connecting a Java app to a database follows a fixed six-step recipe — every JDBC program is a variation of it.

  1. Instantiate the proper driver — load the vendor's driver class.
  2. Open a connection — assemble the JDBC URL.
  3. Establish the connection — call DriverManager.getConnection() .
  4. Query the database — run a SELECT, or an INSERT / UPDATE / DELETE.
  5. Process the result — read rows from the ResultSet.
  6. Close the connection — release resources.
Why it matters Steps 1–3 set up access; step 6 cleans up. Forgetting step 6 leaks connections and eventually exhausts the database's connection pool — a classic production bug. [+]

→ See full runnable walkthrough: JDBCDemo.java

4. Step 1 — Instantiate the driver

You load the driver class by name with Class.forName("<driver class >") . The class name is vendor-dependent.

Driver class Purpose
sun.jdbc.odbc.JdbcOdbcDriver JDBC-ODBC bridge, to access ODBC sources
com.mysql.cj.jdbc.Driver Access a MySQL database
oracle.jdbc.driver.OracleDriver Access an Oracle database

From the deck: Class.forName("oracle.jdbc.driver.OracleDriver");

Intuition Class.forName forces the JVM to load that class. Loading it triggers the driver to register itself with the DriverManager, so the DriverManager knows it exists when you later ask for a connection.
Context / what's changed [+] Since JDBC 4.0 (Java 6+), drivers on the classpath auto-register and the explicit Class.forName call is no longer strictly required — but the deck teaches it because it makes the loading step visible, and it's still common in older code and interviews. Also: the deck's MySQL driver com.mysql.cj.jdbc.Driver is the modern one; older code used com.mysql.jdbc.Driver (the deck's Step 3 code sample uses the older name).
Common pitfall If the driver jar isn't on the classpath, this throws java.lang.ClassNotFoundException .

5. Step 2 — Open the connection (JDBC URL)

To connect you need a connection string in the form of a JDBC URL.

Basic syntax: jdbc:<driver protocol >:<driver connection details >

Database JDBC URL
MS SQL Server jdbc:odbc:DemoDSN
Oracle jdbc:oracle:thin@myserver:1521:demodb
MySQL jdbc:mysql://localhost:3306/demodb

How to read a URL. Every JDBC URL starts with jdbc: , names the protocol/vendor, then gives host, port, and database. In the MySQL example: localhost is the host, 3306 is MySQL's default port, demodb is the database. [+]

Why it matters The URL is how the DriverManager decides which driver to use — the protocol segment (mysql , oracle ) routes you to the matching driver.

6. Step 3 — Establish the connection

Get a live connection with DriverManager.getConnection(...) .

It returns a Connection object — your handle to the database for the rest of the session.

Failures throw exceptions:

Best practice [+] Use try-with-resources (try (Connection con = ...) { } ) so the connection closes automatically even if an exception is thrown — this directly prevents the step-6 leak.

→ See full runnable walkthrough: JDBCDemo.java

7. Step 4 — Query / modify (Statement)

You run SQL through a Statement object, using one of two methods depending on whether you're reading or writing.

Method Use for Returns
executeQuery() SELECT a ResultSet
executeUpdate() INSERT, UPDATE, DELETE an int = rows affected

executeUpdate() returns the count of rows changed (should be 1 for a successful single-row insert).

Intuition Reads give you data back (a result set you iterate); writes give you a number (how many rows you touched). Choosing the wrong method fails — you can't executeQuery an INSERT. [+]

→ See: JDBCInsertDemo.java

8. Step 5 — Process the ResultSet

A SELECT's rows and columns come back as a ResultSet , which you walk row-by-row using a cursor.

Key facts. A ResultSet maintains a cursor pointing at its current row; next() advances it. Only one ResultSet per Statement can be open at a time — opening a second on the same Statement closes the first.

Common pitfall Forgetting that column indexes start at 1 (not 0 like arrays) is a frequent bug and a classic interview gotcha.

→ See: JDBCDemo.java

9. Step 6 — Close the connection

Always close the connection (and ideally the Statement and ResultSet) when done.

The deck calls this out as its own step to stress its importance.

Why it matters [+] Databases allow a limited number of simultaneous connections. Unclosed connections accumulate, the pool runs dry, and new requests start failing — even though the database itself is healthy. Closing in a finally block, or using try-with-resources, guarantees cleanup regardless of errors.

10. Basic CRUD operations

The four basic JDBC operations map to the four CRUD actions, each demonstrated by its own demo class.

Operation SQL Method
Read (Query) SELECT executeQuery() → ResultSet
Create (Insert) INSERT executeUpdate() → rows affected
Update UPDATE executeUpdate() → rows affected
Delete DELETE executeUpdate() → rows affected

The session demos these as JDBCDemo.java (query), JDBCInsertDemo.java , JDBCUpdateDemo.java , JDBCDeleteDemo.java , run against a sample employees /demo database created from an employees.sql script.

Java ↔ SQL type mapping [+] Java types and SQL types must line up: Java String ↔ SQL VARCHAR , Java int ↔ SQL INTEGER , Java double ↔ SQL DOUBLE/DECIMAL , Java java.sql.Date ↔ SQL DATE . The slide showed this as a concept without a full table. The right getXXX /setXXX call depends on this mapping.

→ See: Insert , Update , Delete

11. Prepared Statements

A PreparedStatement is a precompiled SQL statement with placeholders (? ) you fill in later, instead of hardcoding values into the SQL string.

Instead of concatenating values into ... WHERE salary >80000 AND department = 'Legal' , you write placeholders ... WHERE salary >? AND department = ? and bind values: ps.setInt(1, 80000); ps.setString(2, "Legal"); [+]

Three benefits (from the deck):

Prepared statements work for INSERT, UPDATE, and DELETE too, not just SELECT.

Interview point [+] SQL injection is one of the most common and dangerous web vulnerabilities. "Use PreparedStatement with bound parameters" is the standard correct answer to "how do you prevent SQL injection in JDBC?"
Analogy A PreparedStatement is a fill-in-the-blank form. The database reads and understands the form's structure once; you then drop values into the blanks. Because blanks are clearly separated from the sentence, nobody can smuggle extra instructions in through a blank. [+]

NULL values. To set a parameter to SQL NULL use ps.setNull(index, java.sql.Types.XXX) rather than a Java null , and check rs.wasNull() after a getter to detect a NULL you just read. [+]

→ See: JDBCPreparedStatementDemo.java

12. Statement vs PreparedStatement

Use Statement for one-off static SQL; use PreparedStatement whenever there are parameters or the query repeats. [+ table synthesizes the deck's points]

Statement PreparedStatement
SQL Built as a full string Template with ? placeholders
Compilation Each execution Precompiled once
SQL injection Vulnerable Protected
Performance on repeat Lower Higher (reused plan)
Best for Simple static queries Parameterized / repeated queries

13. Database configuration

Hardcoding connection details is fine for learning but bad practice; externalize them into a configuration file.

The deck notes the app has connection info hardcoded (easy for beginners), but ideally this lives in a config file. Java provides java.util.Properties to read and write such files (demo: JDBCPropertiesDemo.java ).

Why it matters [+] Externalizing config means you can point the same build at dev, test, and production databases without editing and recompiling — and you keep passwords out of source control. A .properties file holds key=value pairs like db.url=... , loaded at startup with Properties.load() .

→ See: JDBCPropertiesDemo.java

14. Metadata — Database &ResultSet

Metadata is "data about the data" — JDBC lets you ask the database about its own structure, and ask a result set about its columns.

DatabaseMetaData — describes the database itself, from the Connection: DatabaseMetaData dbMetaData = connection.getMetaData(); Useful methods: getDatabaseProductName() , getDriverName() , getTables() , getColumns() . (Demo: JDBCDatabaseMetaDataDemo.java .)

ResultSetMetaData — describes the columns of a query result, from the ResultSet: ResultSetMetaData rsMetaData = rs.getMetaData(); Useful methods: getColumnCount() , getColumnName() , getColumnType() , isAutoIncrement() , isNullable() . (The deck writes "ResultMetaData"; the correct interface is ResultSetMetaData . [+] )

Use cases [+] Metadata powers generic tools that don't know the schema in advance — e.g. a query browser that prints any result as a table needs getColumnCount() and getColumnName() to build headers dynamically.

→ See: JDBCResultSetMetaDataDemo.java

15. JDBC API debrief — key classes

The whole JDBC API lives in two packages and a small set of core types.

Packages: java.sql and javax.sql .

Type Key methods
DriverManager getConnection
Connection createStatement , prepareStatement , setAutoCommit , commit , rollback
Statement execute , executeQuery , executeBatch , executeUpdate
ResultSet next , getString , getInt , getDate , getMetaData
ResultSetMetaData getColumnCount , getColumnName , getColumnType

From javax.sql : DataSource — used for connection pooling. [+] A DataSource is the modern, preferred way to obtain connections in real applications (it reuses pooled connections instead of opening a fresh one each time, which is expensive).

16. What is a transaction?

A transaction is a sequence of multiple database operations treated as a single logical unit of work — it happens wholly or not at all.

The classic example: transferring $5 from Account A to Account B.

  1. Create a record to transfer $5 from A to B — the begin of the transaction.
  2. Read the balance from Account A.
  3. Subtract $5 from A's balance.
  4. Read the balance from Account B.
  5. Add $5 credit to Account B.

Why "all or nothing" matters. If the system crashes after step 3 (money removed from A) but before step 5 (money added to B), $5 has vanished. Wrapping all five steps in one transaction means either every step sticks, or none does — balances are never left in an inconsistent in-between state.

Interview point [+] This "all-or-nothing" property is atomicity , the A in the ACID properties (Atomicity, Consistency, Isolation, Durability). The deck focuses on atomicity via commit/rollback; ACID is the broader framework worth knowing by name.

Real-world. Any operation touching multiple rows or tables that must agree: bank transfers, placing an order (decrement stock + create order + charge payment), booking a seat. [+]

17. Transactions in a database — COMMIT &ROLLBACK

A transaction groups one or more SQL statements that execute together, and ends with either COMMIT or ROLLBACK.

Example: updating several tables due to a customer purchase — all the updates commit together, or none should.

Mental model [+] A transaction is like editing a document with one big "Save" and one big "Undo": COMMIT is Save (writes everything at once), ROLLBACK is Undo-all (throws away every change since you started).

18. TCL — Transaction Control Language

In SQL, the commands that manage transactions are called Transaction Control Language (TCL): COMMIT, ROLLBACK, and SAVEPOINT.

Command What it does
COMMIT Finalize the transaction; make all changes permanent.
ROLLBACK Abort; discard all uncommitted changes, revert to original state.
SAVEPOINT Set a named marker inside a transaction you can roll back to, without undoing the whole transaction. [+]
SAVEPOINT intuition [+] A SAVEPOINT is a checkpoint in a video game. If something goes wrong, you roll back to the last checkpoint instead of restarting the whole level. ROLLBACK TO savepoint_name undoes only the work done after that savepoint; earlier work stays pending until you COMMIT or fully ROLLBACK.

The session demonstrates these via a tcl.sql script. [+] A typical flow: do some work, SAVEPOINT sp1 , do more work, then either ROLLBACK TO sp1 (undo the later part) or COMMIT (keep everything).

→ See: tcl.sql

19. Transaction management in JDBC

By default a JDBC Connection is in auto-commit mode — every statement commits immediately. To use real transactions you must turn auto-commit off and control commit/rollback yourself.

Common pitfall / interview point Beginners forget auto-commit is ON by default, so their "transaction" actually commits each statement individually — meaning a later failure can't undo earlier statements. The fix is setAutoCommit(false) at the start. [+]
Why it matters This is the JDBC-level expression of the $5-transfer idea: the multi-step operation only becomes truly atomic once you take manual control of commit/rollback.

→ See full runnable demo: JDBCTransactionDemo.java


Recap

III

Code Samples — runnable

Standard library only (java.sql / java.util ), targeting Java 8+. Every class the notes reference is written out in full. Each block has a filename header, a run command, inline comments, and an Expected-output box. You need the MySQL Connector/J jar on the classpath and the demo database from Part IV setup .

0. employees.sql — schema &seed data

employees.sql SQL
sudo mysql <employees.sql
                        
                            CREATE DATABASE IF NOT EXISTS
                            demo;
USE
                            demo;

DROP TABLE IF EXISTS
                            employees;
CREATE TABLE
                            employees (
  id          INT
                            PRIMARY KEY AUTO_INCREMENT
                            ,   -- auto id; isAutoIncrement()=true
                            name        VARCHAR
                            (100
                            ) NOT NULL
                            ,            -- maps to Java String
                            department  VARCHAR
                            (50
                            ),
  salary      INT
                            ,                            -- maps to Java int
                            hire_date   DATE
                            -- maps to java.sql.Date
                            );

INSERT INTO
                            employees (name, department, salary, hire_date) VALUES
                            ('Asha Rao'
                            ,    'Legal'
                            ,       92000
                            , '2021-03-01'
                            ),
  ('Ben Cole'
                            ,    'Engineering'
                            , 78000
                            , '2022-07-15'
                            ),
  ('Cara Diaz'
                            ,   'Legal'
                            ,       85000
                            , '2020-11-20'
                            ),
  ('Dev Khan'
                            ,    'Engineering'
                            , 99000
                            , '2019-01-05'
                            );
                        
                    

1. JDBCDemo.java — query (Steps 1–6) ↔ notes 3,6,8

JDBCDemo.java Java
javac -cp .:mysql-connector-j.jar JDBCDemo.java &&java -cp .:mysql-connector-j.jar JDBCDemo
                        
                            import
                            java.sql.*;                          // Connection, Statement, ResultSet, SQLException
                            public class
                            JDBCDemo
                            {
  public static void
                            main
                            (String
                            [] args) {
    String
                            url  = "jdbc:mysql://localhost:3306/demo"
                            ;  // protocol:host:port/db
                            String
                            user = "student"
                            , pwd = "student"
                            ;

    // try-with-resources: con, stmt, rs all auto-close (Step 6 for free) [+]
                            try
                            (Connection
                            con = DriverManager
                            .getConnection
                            (url, user, pwd);  // Steps 2-3
                            Statement
                            stmt = con.createStatement
                            ();
         ResultSet
                            rs = stmt.executeQuery
                            (                          // Step 4: SELECT
                            "SELECT id, name, salary FROM employees ORDER BY id"
                            )) {

      while
                            (rs.next
                            ()) {                // Step 5: next() advances cursor, false at end
                            int
                            id      = rs.getInt
                            ("id"
                            );       // read by column NAME
                            String
                            name = rs.getString
                            (2
                            );       // read by 1-based INDEX (col 2 = name)
                            int
                            salary  = rs.getInt
                            ("salary"
                            );
        System
                            .out
                            .printf
                            ("%d  %-10s %d%n"
                            , id, name, salary);
      }
    } catch
                            (SQLException
                            e) {        // bad url/creds, SQL error, etc.
                            e.printStackTrace
                            ();
    }
  }
}
                        
                    
Expected output 1 Asha Rao 92000 2 Ben Cole 78000 3 Cara Diaz 85000 4 Dev Khan 99000
Why it behaves this way No explicit Class.forName — on Java 6+ the Connector/J jar auto-registers via the service-loader, so just having it on the classpath is enough (note 4). getString(2) works because indexes are 1-based; getString(0) would throw SQLException: Column Index out of range . The cursor starts before the first row, so you must call next() once before reading.

2. JDBCInsertDemo.java — create ↔ notes 7,10

JDBCInsertDemo.java Java
javac -cp .:mysql-connector-j.jar JDBCInsertDemo.java &&java -cp .:mysql-connector-j.jar JDBCInsertDemo
                        
                            import
                            java.sql.*;

public class
                            JDBCInsertDemo
                            {
  public static void
                            main
                            (String
                            [] args) {
    String
                            url = "jdbc:mysql://localhost:3306/demo"
                            ;
    String
                            sql = "INSERT INTO employees(name, department, salary, hire_date) "
                            + "VALUES ('Eli Frost', 'Sales', 71000, '2023-02-10')"
                            ;

    try
                            (Connection
                            con = DriverManager
                            .getConnection
                            (url, "student"
                            , "student"
                            );
         Statement
                            stmt = con.createStatement
                            ()) {

      int
                            rows = stmt.executeUpdate
                            (sql);   // executeUpdate → int rows affected
                            System
                            .out
                            .println
                            ("Rows inserted: "
                            + rows);  // 1 = success
                            } catch
                            (SQLException
                            e) {
      e.printStackTrace
                            ();
    }
  }
}
                        
                    
Expected output Rows inserted: 1
Why it behaves this way Using executeQuery here would throw — INSERT returns no ResultSet. The int return is the row count; for a single-row insert it's 1 (note 7). This static-string form is injection-prone if any value came from user input — use the prepared version below for that.

3. JDBCUpdateDemo.java — update ↔ note 10

JDBCUpdateDemo.java Java
java -cp .:mysql-connector-j.jar JDBCUpdateDemo
                        
                            import
                            java.sql.*;

public class
                            JDBCUpdateDemo
                            {
  public static void
                            main
                            (String
                            [] args) throws
                            SQLException
                            {
    String
                            url = "jdbc:mysql://localhost:3306/demo"
                            ;
    // give everyone in Legal a 10% raise
                            String
                            sql = "UPDATE employees SET salary = salary * 1.10 "
                            + "WHERE department = 'Legal'"
                            ;

    try
                            (Connection
                            con = DriverManager
                            .getConnection
                            (url, "student"
                            , "student"
                            );
         Statement
                            stmt = con.createStatement
                            ()) {
      int
                            rows = stmt.executeUpdate
                            (sql);  // rows matched by WHERE
                            System
                            .out
                            .println
                            ("Rows updated: "
                            + rows);
    }
  }
}
                        
                    
Expected output Rows updated: 2
Why it behaves this way Two Legal employees (Asha, Cara) match the WHERE, so the count is 2 . An UPDATE that matches no rows returns 0 — not an error. The count reflects rows matched/changed, not rows in the table.

4. JDBCDeleteDemo.java — delete ↔ note 10

JDBCDeleteDemo.java Java
java -cp .:mysql-connector-j.jar JDBCDeleteDemo
                        
                            import
                            java.sql.*;

public class
                            JDBCDeleteDemo
                            {
  public static void
                            main
                            (String
                            [] args) throws
                            SQLException
                            {
    String
                            url = "jdbc:mysql://localhost:3306/demo"
                            ;
    String
                            sql = "DELETE FROM employees WHERE name = 'Eli Frost'"
                            ;

    try
                            (Connection
                            con = DriverManager
                            .getConnection
                            (url, "student"
                            , "student"
                            );
         Statement
                            stmt = con.createStatement
                            ()) {
      int
                            rows = stmt.executeUpdate
                            (sql);
      System
                            .out
                            .println
                            ("Rows deleted: "
                            + rows);
    }
  }
}
                        
                    
Expected output Rows deleted: 1
Why it behaves this way DELETE also uses executeUpdate . Omitting the WHERE clause would delete every row and return the full count — a notorious production accident. Always scope a DELETE/UPDATE with a WHERE.

5. JDBCPreparedStatementDemo.java — prepared + NULL ↔ notes 11,12

JDBCPreparedStatementDemo.java Java
java -cp .:mysql-connector-j.jar JDBCPreparedStatementDemo
                        
                            import
                            java.sql.*;

public class
                            JDBCPreparedStatementDemo
                            {
  public static void
                            main
                            (String
                            [] args) throws
                            SQLException
                            {
    String
                            url = "jdbc:mysql://localhost:3306/demo"
                            ;
    // ? placeholders — values bound separately, so input can't be SQL
                            String
                            sql = "SELECT name, salary FROM employees "
                            + "WHERE salary >? AND department = ?"
                            ;

    try
                            (Connection
                            con = DriverManager
                            .getConnection
                            (url, "student"
                            , "student"
                            );
         PreparedStatement
                            ps = con.prepareStatement
                            (sql)) {  // precompiled once
                            ps.setInt
                            (1
                            , 80000
                            );          // bind ?#1 (1-based) — typed setter
                            ps.setString
                            (2
                            , "Legal"
                            );     // bind ?#2
                            try
                            (ResultSet
                            rs = ps.executeQuery
                            ()) {  // no SQL string passed here
                            while
                            (rs.next
                            ()) {
          System
                            .out
                            .printf
                            ("%-10s %d%n"
                            ,
              rs.getString
                            ("name"
                            ), rs.getInt
                            ("salary"
                            ));
        }
      }

      // --- Setting a SQL NULL the right way [+] ---
                            String
                            ins = "INSERT INTO employees(name, department, salary, hire_date) "
                            + "VALUES (?, ?, ?, ?)"
                            ;
      try
                            (PreparedStatement
                            p2 = con.prepareStatement
                            (ins)) {
        p2.setString
                            (1
                            , "Mara Vex"
                            );
        p2.setNull
                            (2
                            , Types
                            .VARCHAR);  // department = SQL NULL (not Java null!)
                            p2.setInt
                            (3
                            , 60000
                            );
        p2.setNull
                            (4
                            , Types
                            .DATE);
        System
                            .out
                            .println
                            ("NULL-insert rows: "
                            + p2.executeUpdate
                            ());
      }
    }
  }
}
                        
                    
Expected output Asha Rao 101200 Cara Diaz 93500 NULL-insert rows: 1
Why it behaves this way The salaries reflect the 10% raise from the UPDATE demo (run order matters). Because parameters travel to the DB separately from the SQL text, a value like ' OR '1'='1 bound via setString is treated as literal text, not SQL — that's the injection defense (note 11). setNull with the right java.sql.Types tells the driver the column's intended type; passing a Java null to a typed setter is unreliable.

6. JDBCPropertiesDemo.java + db.properties — externalized config ↔ note 13

db.properties Properties
                        
                            # key=value config — no quotes, no recompiling to switch envs
                            db.url=jdbc:mysql://localhost:3306/demo
db.user=student
db.password=student
                        
                    
JDBCPropertiesDemo.java Java
java -cp .:mysql-connector-j.jar JDBCPropertiesDemo
                        
                            import
                            java.sql.*;
import
                            java.io.*;
import
                            java.util.Properties;          // standard config reader
                            public class
                            JDBCPropertiesDemo
                            {
  public static void
                            main
                            (String
                            [] args) throws
                            Exception
                            {
    Properties
                            props = new
                            Properties
                            ();
    try
                            (FileInputStream
                            in = new
                            FileInputStream
                            ("db.properties"
                            )) {
      props.load
                            (in);              // parse key=value pairs at startup
                            }

    String
                            url  = props.getProperty
                            ("db.url"
                            );
    String
                            user = props.getProperty
                            ("db.user"
                            );
    String
                            pwd  = props.getProperty
                            ("db.password"
                            );

    try
                            (Connection
                            con = DriverManager
                            .getConnection
                            (url, user, pwd)) {
      System
                            .out
                            .println
                            ("Connected to: "
                            + con.getCatalog
                            ());
    }
  }
}
                        
                    
Expected output Connected to: demo
Why it behaves this way The same compiled class connects to dev/test/prod by swapping only db.properties — no recompile, and credentials stay out of source. getCatalog() returns the current database name, confirming the connection landed on demo .

7. JDBCResultSetMetaDataDemo.java — metadata-driven printer ↔ note 14

JDBCResultSetMetaDataDemo.java Java
java -cp .:mysql-connector-j.jar JDBCResultSetMetaDataDemo
                        
                            import
                            java.sql.*;

public class
                            JDBCResultSetMetaDataDemo
                            {
  public static void
                            main
                            (String
                            [] args) throws
                            SQLException
                            {
    String
                            url = "jdbc:mysql://localhost:3306/demo"
                            ;

    try
                            (Connection
                            con = DriverManager
                            .getConnection
                            (url, "student"
                            , "student"
                            );
         Statement
                            stmt = con.createStatement
                            ();
         ResultSet
                            rs = stmt.executeQuery
                            ("SELECT id, name FROM employees"
                            )) {

      // DatabaseMetaData — about the DB itself
                            DatabaseMetaData
                            db = con.getMetaData
                            ();
      System
                            .out
                            .println
                            ("DB: "
                            + db.getDatabaseProductName
                            ());

      // ResultSetMetaData — about the query's columns (schema unknown ahead of time)
                            ResultSetMetaData
                            md = rs.getMetaData
                            ();
      int
                            cols = md.getColumnCount
                            ();        // build headers dynamically
                            for
                            (int
                            i = 1
                            ; i <= cols; i++)      // columns are 1-based
                            System
                            .out
                            .print
                            (md.getColumnName
                            (i) + "\t"
                            );
      System
                            .out
                            .println
                            ();
    }
  }
}
                        
                    
Expected output DB: MySQL id name
Why it behaves this way The loop never names a column literally — it asks the metadata how many columns exist and what they're called. That's how a generic query browser prints any result set (note 14). Column indexes here are 1-based, same as ResultSet getters.

8. tcl.sql — TCL commands (SAVEPOINT demo) ↔ note 18

tcl.sql SQL
sudo mysql demo <tcl.sql
                        
                            USE
                            demo;
SET
                            autocommit = 0
                            ;                -- turn off auto-commit so we control the txn
                            START TRANSACTION
                            ;
UPDATE
                            employees SET
                            salary = salary + 1000
                            WHERE
                            name = 'Ben Cole'
                            ;
SAVEPOINT
                            sp1;                     -- checkpoint AFTER Ben's raise
                            UPDATE
                            employees SET
                            salary = salary + 5000
                            WHERE
                            name = 'Dev Khan'
                            ;
ROLLBACK TO
                            sp1;                   -- undo ONLY Dev's raise; Ben's stays pending
                            COMMIT
                            ;                            -- finalize: Ben +1000 sticks, Dev unchanged
                            SELECT
                            name, salary FROM
                            employees WHERE
                            name IN
                            ('Ben Cole'
                            , 'Dev Khan'
                            );
                        
                    
Expected output name salary Ben Cole 79000 Dev Khan 99000
Why it behaves this way Ben's +1000 happened before sp1 , so ROLLBACK TO sp1 leaves it intact; Dev's +5000 happened after, so it's undone. COMMIT then makes the surviving change permanent (note 18). The salaries shown are the original seed values plus only Ben's raise.

9. JDBCTransactionDemo.java — commit/rollback (the $5 transfer) ↔ notes 16,19

JDBCTransactionDemo.java Java
java -cp .:mysql-connector-j.jar JDBCTransactionDemo
                        
                            import
                            java.sql.*;

public class
                            JDBCTransactionDemo
                            {
  public static void
                            main
                            (String
                            [] args) {
    String
                            url = "jdbc:mysql://localhost:3306/demo"
                            ;

    Connection
                            con = null
                            ;
    try
                            {
      con = DriverManager
                            .getConnection
                            (url, "student"
                            , "student"
                            );
      con.setAutoCommit
                            (false
                            );     // BEGIN: group the next statements [+] (note 19)
                            // move $5 from Asha (A) to Ben (B) — treat 'salary' as a balance
                            PreparedStatement
                            debit = con.prepareStatement
                            (
          "UPDATE employees SET salary = salary - 5 WHERE name = 'Asha Rao'"
                            );
      PreparedStatement
                            credit = con.prepareStatement
                            (
          "UPDATE employees SET salary = salary + 5 WHERE name = 'Ben Cole'"
                            );

      debit.executeUpdate
                            ();          // step: A - 5
                            credit.executeUpdate
                            ();         // step: B + 5
                            con.commit
                            ();                 // both succeeded → finalize atomically
                            System
                            .out
                            .println
                            ("COMMIT: transfer complete"
                            );

    } catch
                            (SQLException
                            e) {
      try
                            {
        if
                            (con != null
                            ) con.rollback
                            ();  // any failure → undo BOTH updates
                            System
                            .out
                            .println
                            ("ROLLBACK: nothing changed"
                            );
      } catch
                            (SQLException
                            ex) { ex.printStackTrace
                            (); }
    } finally
                            {
      try
                            { if
                            (con != null
                            ) con.close
                            (); }   // Step 6: always release
                            catch
                            (SQLException
                            ignored) {}
    }
  }
}
                        
                    
Expected output (happy path) COMMIT: transfer complete
Why it behaves this way With setAutoCommit(false) , neither UPDATE is durable until commit() . If the credit threw (say Ben didn't exist and you added a guard), rollback() would undo the debit too — so $5 never vanishes (notes 16, 19). Without setAutoCommit(false) , each UPDATE would commit on its own and a later failure couldn't reverse the earlier one — the exact beginner bug from note 19.
IV

Hands-On Lab — practice

Six graded exercises, Warm-up → Core → Challenge. Each has a goal, numbered steps, a predict-then-verify prompt, and a collapsible solution. Do the one-time setup first.

Setup (one time)

  1. Check installs: java -version (need 8+), javac -version , and mysql --version . Confirm the MySQL Connector/J jar exists (e.g. mysql-connector-j.jar ).
  2. Make the folder:
    mkdir -p ~/workspace/JAVA-203/session-4 &&cd ~/workspace/JAVA-203/session-4
  3. Create the user &database:
    sudo mysql then:
    CREATE USER 'student'@'localhost' IDENTIFIED BY 'student';
    GRANT ALL PRIVILEGES ON *.* TO 'student'@'localhost' WITH GRANT OPTION;
    FLUSH PRIVILEGES;
    source employees.sql      -- the schema from Part III.0
    USE demo;  SELECT COUNT(*) FROM employees;   -- expect 4
  4. Compile &run pattern (used in every exercise):
    javac -cp .:mysql-connector-j.jar Foo.java &&java -cp .:mysql-connector-j.jar Foo
    Windows: replace : with ; in the classpath.
WARM-UP

Exercise 1 — First query

Goal: Connect and print every employee's name and department.

  1. Create Ex1.java . Open a connection to jdbc:mysql://localhost:3306/demo as student/student inside try-with-resources.
  2. Run SELECT name, department FROM employees with a Statement .
  3. Loop with rs.next() and print each row.
Predict, then verify: Before running, how many lines will print? What happens if you read a column with rs.getString(0) instead of getString(1) ?
Show solution
Ex1.java Java
                                        
                                            import
                                            java.sql.*;
public class
                                            Ex1
                                            {
  public static void
                                            main
                                            (String
                                            [] a) throws
                                            SQLException
                                            {
    try
                                            (Connection
                                            c = DriverManager
                                            .getConnection
                                            (
            "jdbc:mysql://localhost:3306/demo"
                                            , "student"
                                            , "student"
                                            );
         Statement
                                            s = c.createStatement
                                            ();
         ResultSet
                                            rs = s.executeQuery
                                            (
            "SELECT name, department FROM employees"
                                            )) {
      while
                                            (rs.next
                                            ())
        System
                                            .out
                                            .println
                                            (rs.getString
                                            ("name"
                                            ) + " — "
                                            + rs.getString
                                            ("department"
                                            ));
    }
  }
}
                                        
                                    

Answer: 4 lines. getString(0) throws SQLException: Column Index out of range, 0 <1 — JDBC columns are 1-based (note 8).

WARM-UP

Exercise 2 — Insert and count rows

Goal: Insert one new employee and print the rows-affected count.

  1. Create Ex2.java .
  2. Build an INSERT for a new employee (any values) and run it with executeUpdate .
  3. Print the returned int .
Predict, then verify: What number prints on success? What exception do you get if you mistakenly call executeQuery on the INSERT?
Show solution
Ex2.java Java
                                        
                                            import
                                            java.sql.*;
public class
                                            Ex2
                                            {
  public static void
                                            main
                                            (String
                                            [] a) throws
                                            SQLException
                                            {
    String
                                            sql = "INSERT INTO employees(name,department,salary,hire_date) "
                                            + "VALUES ('Nia Bose','Support',64000,'2024-05-01')"
                                            ;
    try
                                            (Connection
                                            c = DriverManager
                                            .getConnection
                                            (
            "jdbc:mysql://localhost:3306/demo"
                                            , "student"
                                            , "student"
                                            );
         Statement
                                            s = c.createStatement
                                            ()) {
      System
                                            .out
                                            .println
                                            ("rows = "
                                            + s.executeUpdate
                                            (sql));
    }
  }
}
                                        
                                    

Answer: prints rows = 1 . Calling executeQuery on an INSERT throws SQLException ("Can not issue data manipulation statements with executeQuery()") — reads return ResultSets, writes return counts (note 7).

CORE

Exercise 3 — Parameterize a search

Goal: Rewrite a filtered search as a PreparedStatement with two bound parameters.

  1. Create Ex3.java selecting name, salary WHERE salary >? AND department = ? .
  2. Bind 75000 and "Engineering" with setInt /setString .
  3. Print the matches.
Predict, then verify: If a malicious user supplied the department as ' OR '1'='1 , why does the prepared version return nothing instead of dumping the whole table?
Show solution
Ex3.java Java
                                        
                                            import
                                            java.sql.*;
public class
                                            Ex3
                                            {
  public static void
                                            main
                                            (String
                                            [] a) throws
                                            SQLException
                                            {
    String
                                            sql = "SELECT name,salary FROM employees WHERE salary >? AND department = ?"
                                            ;
    try
                                            (Connection
                                            c = DriverManager
                                            .getConnection
                                            (
            "jdbc:mysql://localhost:3306/demo"
                                            , "student"
                                            , "student"
                                            );
         PreparedStatement
                                            ps = c.prepareStatement
                                            (sql)) {
      ps.setInt
                                            (1
                                            , 75000
                                            );
      ps.setString
                                            (2
                                            , "Engineering"
                                            );
      try
                                            (ResultSet
                                            rs = ps.executeQuery
                                            ()) {
        while
                                            (rs.next
                                            ())
          System
                                            .out
                                            .println
                                            (rs.getString
                                            (1
                                            ) + " "
                                            + rs.getInt
                                            (2
                                            ));
      }
    }
  }
}
                                        
                                    

Answer: prints Ben Cole and Dev Khan. The injection string is bound as a literal department value , so the DB looks for a department literally named ' OR '1'='1 — none exists, so zero rows. Parameters are never parsed as SQL (note 11).

CORE

Exercise 4 — Print columns from metadata

Goal: Print a result set's column headers without hardcoding any column name.

  1. Create Ex4.java running SELECT * FROM employees .
  2. Get ResultSetMetaData from the result set.
  3. Loop from 1 to getColumnCount() printing each getColumnName(i) .
Predict, then verify: How many headers print, and in what order? If you started the loop at 0 , what would happen?
Show solution
Ex4.java Java
                                        
                                            import
                                            java.sql.*;
public class
                                            Ex4
                                            {
  public static void
                                            main
                                            (String
                                            [] a) throws
                                            SQLException
                                            {
    try
                                            (Connection
                                            c = DriverManager
                                            .getConnection
                                            (
            "jdbc:mysql://localhost:3306/demo"
                                            , "student"
                                            , "student"
                                            );
         Statement
                                            s = c.createStatement
                                            ();
         ResultSet
                                            rs = s.executeQuery
                                            ("SELECT * FROM employees"
                                            )) {
      ResultSetMetaData
                                            md = rs.getMetaData
                                            ();
      for
                                            (int
                                            i = 1
                                            ; i <= md.getColumnCount
                                            (); i++)
        System
                                            .out
                                            .print
                                            (md.getColumnName
                                            (i) + "  "
                                            );
      System
                                            .out
                                            .println
                                            ();
    }
  }
}
                                        
                                    

Answer: 5 headers — id name department salary hire_date , in table order. Starting at 0 throws an out-of-range SQLException ; metadata columns are 1-based too (note 14).

CHALLENGE

Exercise 5 — Atomic transfer with rollback

Goal: Move 5 units of salary from one employee to another atomically, and prove rollback works when the second update is impossible.

  1. Create Ex5.java . Get a connection and call setAutoCommit(false) .
  2. Debit Asha Rao by 5, then credit a non-existent employee — but add a guard: if the credit's row count is 0 , throw new SQLException("recipient missing") .
  3. In try : commit() . In catch : rollback() and print which path ran.
  4. Afterward, query Asha's salary to confirm it's unchanged.
Predict, then verify: After the run, is Asha's salary reduced by 5 or back to its original value? What would happen to her balance if you forgot setAutoCommit(false) ?
Show solution
Ex5.java Java
                                        
                                            import
                                            java.sql.*;
public class
                                            Ex5
                                            {
  public static void
                                            main
                                            (String
                                            [] a) throws
                                            SQLException
                                            {
    String
                                            url = "jdbc:mysql://localhost:3306/demo"
                                            ;
    Connection
                                            con = null
                                            ;
    try
                                            {
      con = DriverManager
                                            .getConnection
                                            (url, "student"
                                            , "student"
                                            );
      con.setAutoCommit
                                            (false
                                            );                 // begin txn
                                            Statement
                                            s = con.createStatement
                                            ();
      s.executeUpdate
                                            ("UPDATE employees SET salary=salary-5 WHERE name='Asha Rao'"
                                            );
      int
                                            credited = s.executeUpdate
                                            (
          "UPDATE employees SET salary=salary+5 WHERE name='Ghost User'"
                                            );
      if
                                            (credited == 0
                                            )                        // recipient doesn't exist
                                            throw new
                                            SQLException
                                            ("recipient missing"
                                            );

      con.commit
                                            ();
      System
                                            .out
                                            .println
                                            ("COMMIT"
                                            );
    } catch
                                            (SQLException
                                            e) {
      if
                                            (con != null
                                            ) con.rollback
                                            ();         // undo the debit too
                                            System
                                            .out
                                            .println
                                            ("ROLLBACK: "
                                            + e.getMessage
                                            ());
    } finally
                                            {
      if
                                            (con != null
                                            ) con.close
                                            ();
    }
  }
}
                                        
                                    

Answer: prints ROLLBACK: recipient missing ; Asha's salary is restored to its pre-run value because rollback() undid the debit. Without setAutoCommit(false) , the debit would have auto-committed immediately — the rollback would do nothing and Asha would permanently lose 5 (notes 16, 19).

CHALLENGE

Exercise 6 — Config + NULL handling

Goal: Read connection details from a db.properties file, insert an employee whose department is SQL NULL, then read it back and correctly report the NULL.

  1. Create db.properties with db.url , db.user , db.password .
  2. In Ex6.java , load it via Properties.load and connect.
  3. Insert with a PreparedStatement using setNull(2, Types.VARCHAR) for department.
  4. Select that row; after getString("department") , call rs.wasNull() and print "(null)" when true.
Predict, then verify: What does getString("department") return for a SQL NULL — an empty string, the text "null", or a Java null ? Why is wasNull() needed for primitive getters like getInt ?
Show solution
Ex6.java Java
                                        
                                            import
                                            java.sql.*; import
                                            java.io.*; import
                                            java.util.Properties;
public class
                                            Ex6
                                            {
  public static void
                                            main
                                            (String
                                            [] a) throws
                                            Exception
                                            {
    Properties
                                            p = new
                                            Properties
                                            ();
    try
                                            (FileInputStream
                                            in = new
                                            FileInputStream
                                            ("db.properties"
                                            )) { p.load
                                            (in); }

    try
                                            (Connection
                                            c = DriverManager
                                            .getConnection
                                            (
            p.getProperty
                                            ("db.url"
                                            ), p.getProperty
                                            ("db.user"
                                            ), p.getProperty
                                            ("db.password"
                                            ))) {

      try
                                            (PreparedStatement
                                            ins = c.prepareStatement
                                            (
          "INSERT INTO employees(name,department,salary,hire_date) VALUES (?,?,?,?)"
                                            )) {
        ins.setString
                                            (1
                                            , "Oren Pax"
                                            );
        ins.setNull
                                            (2
                                            , Types
                                            .VARCHAR);          // department = NULL
                                            ins.setInt
                                            (3
                                            , 50000
                                            );
        ins.setNull
                                            (4
                                            , Types
                                            .DATE);
        ins.executeUpdate
                                            ();
      }

      try
                                            (PreparedStatement
                                            q = c.prepareStatement
                                            (
              "SELECT department FROM employees WHERE name = ?"
                                            )) {
        q.setString
                                            (1
                                            , "Oren Pax"
                                            );
        try
                                            (ResultSet
                                            rs = q.executeQuery
                                            ()) {
          if
                                            (rs.next
                                            ()) {
            String
                                            d = rs.getString
                                            ("department"
                                            );
            System
                                            .out
                                            .println
                                            ("dept = "
                                            + (rs.wasNull
                                            () ? "(null)"
                                            : d));
          }
        }
      }
    }
  }
}
                                        
                                    

Answer: prints dept = (null) . For a SQL NULL, getString returns a Java null (not "" or "null"). wasNull() matters most for primitive getters: getInt returns 0 for a NULL — indistinguishable from a real 0 unless you check wasNull() (note 11).

Self-check rubric — how to know you've got it

  • You can recite the six connection steps and say which set up access vs. clean up.
  • You instinctively reach for executeQuery for SELECT and executeUpdate for INSERT/UPDATE/DELETE, and know what each returns.
  • You never write getXXX(0) — you remember columns are 1-based — and you loop on rs.next() .
  • You can explain in one sentence why a PreparedStatement stops SQL injection (parameters sent separately from SQL).
  • You can set and detect a SQL NULL with setNull(i, Types.X) and wasNull() .
  • You can write a transaction that survives a mid-way failure: setAutoCommit(false) → work → commit() / rollback() , and you know auto-commit is ON by default.
  • You can explain COMMIT vs ROLLBACK vs SAVEPOINT, and what ROLLBACK TO sp1 does.
  • You can name the ACID acronym and which letter atomicity is.