ā˜• Unit 13 • Enterprise Data Persistence

Database Connectivity: JDBC Architecture & Transactions

Connecting Java applications to relational database management systems (RDBMS): The 4 JDBC Driver types, MySQL and Oracle connectivity, the DriverManager and Connection contracts, defense against SQL Injection via PreparedStatement, tabular cursor traversal with ResultSet, full-spectrum CRUD pipelines, and ACID transaction control with manual commits and rollbacks.

Curriculum: Enterprise Java Software Engineering
Unit: 13 of 15
Prerequisite: Unit 07 (Exceptions), Unit 09 (I/O Streams)
Target: JDK 21 LTS
01

Introduction to JDBC & Layered Architecture

šŸ”Œ What is JDBC? The Database Abstraction Standard

JDBC (Java Database Connectivity) is a standardized Java API (defined across packages java.sql and javax.sql) that enables Java applications to interact with relational databases independently of vendor-specific proprietary protocols.

JDBC acts as an abstraction layer: application code calls standard JDBC interfaces (e.g., Connection, PreparedStatement), while the vendor's JDBC Driver translates those standardized calls into native network socket protocols understood by MySQL, Oracle, PostgreSQL, or SQL Server.

Visual Architecture: JDBC Layered Communication Pipeline
Java Application
Business Logic / DAOs
── Calls Standard API ā”€ā”€āž”
JDBC API (java.sql)
Connection, Statement, ResultSet
── Dispatches via ā”€ā”€āž”
DriverManager
Locates & loads vendor driver
ā–¼ Vendor Wire Protocol (TCP Sockets) ā–¼
Type 4 Pure Java Driver
e.g., MySQL Connector/J / Oracle OJDBC
── Network Packets (Port 3306 / 1521) ā”€ā”€āž”
RDBMS Database Engine
MySQL / Oracle / PostgreSQL
02

The 4 Types of JDBC Drivers

šŸš— Evolution of Database Drivers in Java

The JDBC specification classifies database drivers into four distinct architectural tiers:

Driver Category Architecture & Translation Native Client Needed? Performance & Production Status
Type 1: JDBC-ODBC Bridge Translates JDBC calls to Microsoft ODBC calls, which then talk to DB Yes (ODBC driver must exist on client machine) Slowest. Deprecated and removed completely in Java 8.
Type 2: Native-API Driver Converts JDBC calls to client-side C/C++ vendor library (e.g., Oracle OCI) Yes (Requires vendor client binaries installed locally) Fast, but lacks portability (requires platform-specific native binaries).
Type 3: Network Protocol All-Java driver sending calls to an intermediate middleware server No (Client is 100% Java) Flexible for multi-database routing, but introduces extra middleware network hop.
Type 4: Thin Driver (Standard) 100% Pure Java driver communicating directly with database via proprietary socket protocol No (Zero native client software required) Industry Standard: Highest throughput, pure portable bytecode, zero deployment overhead.
03

Connecting Java with MySQL and Oracle

šŸ”‘ Driver Loading & Database Connection URLs

In modern JDBC (JDBC 4.0+ / JDK 6+), Class.forName("com.mysql.cj.jdbc.Driver") is no longer mandatory because the Java ServiceLoader automatically registers drivers found on the application classpath (inside META-INF/services/java.sql.Driver).

  • MySQL Connection URL:
    jdbc:mysql://<host>:<port>/<database_name>?useSSL=false&serverTimezone=UTC
    Example: jdbc:mysql://localhost:3306/devascent_db
    Driver Class: com.mysql.cj.jdbc.Driver
  • Oracle Connection URL (Thin Driver):
    jdbc:oracle:thin:@<host>:<port>:<SID> or jdbc:oracle:thin:@//<host>:<port>/<service_name>
    Example: jdbc:oracle:thin:@localhost:1521:xe
    Driver Class: oracle.jdbc.driver.OracleDriver
04

Statement vs. PreparedStatement & SQL Injection Defense

āŒ Statement (Unsafe & Unoptimized)

Used for executing static, non-parameterized SQL statements:

Statement stmt = conn.createStatement();
String sql = "SELECT * FROM users WHERE user = '" + input + "'";
ResultSet rs = stmt.executeQuery(sql);

CRITICAL SECURITY VULNERABILITY: SQL Injection!

If an attacker passes admin' OR '1'='1, the malicious SQL alters the database query logic, exposing entire database tables.

šŸ›”ļø PreparedStatement (Enterprise Standard)

Pre-compiles parameterized SQL using ? placeholders:

String sql = "SELECT * FROM users WHERE user = ? AND pass = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, userInput);
pstmt.setString(2, passInput);
ResultSet rs = pstmt.executeQuery();

Why PreparedStatement Wins:
1. Immune to SQL Injection: Parameter values are treated purely as raw literal values, never executable SQL tokens.
2. Pre-compiled Performance: The RDBMS parses and optimizes the query plan once; subsequent executions reuse the cached plan.

05

The ResultSet Cursor & Column Extraction

šŸ“Š Tabular Cursor Lifecycle

A ResultSet represents a database cursor pointing initially before the first row of the returned tabular dataset:

  • rs.next(): Advances the cursor to the next row. Returns true if another valid row exists, or false when exhausted.
  • Typed Getter Methods: Extract column values by 1-based integer index or column label:
    rs.getInt("id"), rs.getString("username"), rs.getDouble("salary"), rs.getTimestamp("created_at").
  • AutoCloseable: ResultSets consume database cursor handles and socket memory; always close them automatically inside a try-with-resources block!
06

CRUD Operations & ACID Transaction Management

šŸ“ CRUD SQL Execution Methods

JDBC provides distinct execution methods depending on statement intent:

  • executeQuery(sql): Used for SELECT queries. Returns a ResultSet.
  • executeUpdate(sql): Used for INSERT, UPDATE, DELETE, or DDL (CREATE, DROP). Returns an int representing the number of rows affected.
  • execute(sql): General-purpose method for dynamic SQL that may return multiple results or update counts.
šŸ’³ ACID Transaction Control: Atomic Commits & Rollbacks

By default, a JDBC Connection operates in Auto-Commit mode (true): every single SQL statement is committed immediately upon execution as an isolated mini-transaction.

To perform atomic business operations (e.g., transferring funds between two bank accounts where both debit and credit must succeed or both must fail):

  1. Disable Auto-Commit: conn.setAutoCommit(false);
  2. Execute multiple related DML statements via PreparedStatement.
  3. If all statements succeed, commit the transaction: conn.commit();
  4. If any statement throws a SQLException, roll back in the catch block: conn.rollback();
Visual Architecture: ACID Transaction Commit / Rollback State Machine
Begin Transaction
conn.setAutoCommit(false)
── Execute Step 1: Debit ā”€ā”€āž”
Step 2: Credit
Execute SQL Statements
ā–¼ Check Execution Outcome ā–¼
All Statements Succeed
conn.commit()
Changes Persisted to Disk
◀── OR Exception Occurs ──▶
SQLException Caught
conn.rollback()
All Changes Discarded
07

Unit 13 Practical Capstone: Enterprise Customer Account & Ledger DAO

šŸš€ Complete Production JDBC Integration Architecture

This complete Java application demonstrates:
1. Dynamic schema creation (DDL) and parameter binding with PreparedStatement.
2. Full CRUD workflow: inserting accounts, querying via ResultSet, updating balances, and safe deletion.
3. Atomic ACID transaction funds transfer with automated rollback() on simulated system failure.

EnterpriseBankingDAO.java — Complete Capstone Implementation
import java.sql.*;

public class EnterpriseBankingDAO {
    // Using embedded In-Memory H2 / SQLite / MySQL compatible JDBC URL
    private static final String URL = "jdbc:h2:mem:bankdb;DB_CLOSE_DELAY=-1";
    private static final String USER = "sa";
    private static final String PASS = "";

    public static void main(String[] args) {
        System.out.println("==================================================");
        System.out.println("   DEVASCENT ENTERPRISE JDBC & TRANSACTION ENGINE ");
        System.out.println("==================================================");

        try (Connection conn = DriverManager.getConnection(URL, USER, PASS)) {
            System.out.println("āœ“ Connected to Database: " + conn.getMetaData().getDatabaseProductName());

            // 1. CREATE Table (DDL)
            try (Statement stmt = conn.createStatement()) {
                stmt.execute("CREATE TABLE accounts (" +
                        "id INT PRIMARY KEY, " +
                        "holder_name VARCHAR(50), " +
                        "balance DOUBLE)");
                System.out.println("āœ“ Created table 'accounts'");
            }

            // 2. INSERT Accounts (CRUD: Create) via PreparedStatement
            String insertSql = "INSERT INTO accounts (id, holder_name, balance) VALUES (?, ?, ?)";
            try (PreparedStatement pstmt = conn.prepareStatement(insertSql)) {
                pstmt.setInt(1, 101);
                pstmt.setString(2, "Alice Vance");
                pstmt.setDouble(3, 5000.00);
                pstmt.executeUpdate();

                pstmt.setInt(1, 102);
                pstmt.setString(2, "Bob Miller");
                pstmt.setDouble(3, 1200.00);
                pstmt.executeUpdate();
                System.out.println("āœ“ Inserted 2 initial account records");
            }

            // 3. READ & Print Accounts (CRUD: Read) via ResultSet
            displayAccounts(conn, "Initial State");

            // 4. ATOMIC TRANSACTION: Transfer $1,000 from Alice (101) to Bob (102)
            System.out.println("\n--- Executing Atomic Transaction: Transfer $1000 ---");
            transferFunds(conn, 101, 102, 1000.00);

            displayAccounts(conn, "Post-Transfer State");

            // 5. DELETE Account (CRUD: Delete)
            String deleteSql = "DELETE FROM accounts WHERE id = ?";
            try (PreparedStatement pstmt = conn.prepareStatement(deleteSql)) {
                pstmt.setInt(1, 102);
                int rows = pstmt.executeUpdate();
                System.out.println("\nāœ“ Deleted Account #102 (Rows affected: " + rows + ")");
            }

            displayAccounts(conn, "Final State");
            System.out.println("==================================================");

        } catch (SQLException e) {
            System.err.println("Database Failure: " + e.getMessage());
        }
    }

    // Transaction Controller
    private static void transferFunds(Connection conn, int fromId, int toId, double amount) throws SQLException {
        String debitSql = "UPDATE accounts SET balance = balance - ? WHERE id = ?";
        String creditSql = "UPDATE accounts SET balance = balance + ? WHERE id = ?";

        boolean originalAutoCommit = conn.getAutoCommit();
        try {
            conn.setAutoCommit(false); // START TRANSACTION

            try (PreparedStatement debit = conn.prepareStatement(debitSql);
                 PreparedStatement credit = conn.prepareStatement(creditSql)) {

                // Step A: Debit Sender
                debit.setDouble(1, amount);
                debit.setInt(2, fromId);
                debit.executeUpdate();

                // Step B: Credit Receiver
                credit.setDouble(1, amount);
                credit.setInt(2, toId);
                credit.executeUpdate();

                conn.commit(); // ALL STEPS SUCCEED: PERSIST
                System.out.println("  āœ“ Transaction committed atomically!");
            }
        } catch (SQLException ex) {
            conn.rollback(); // ERROR DETECTED: REVERT ALL CHANGES
            System.err.println("  āœ— Transaction rolled back: " + ex.getMessage());
            throw ex;
        } finally {
            conn.setAutoCommit(originalAutoCommit); // Restore state
        }
    }

    // Helper: Query and Display Table
    private static void displayAccounts(Connection conn, String title) throws SQLException {
        String query = "SELECT id, holder_name, balance FROM accounts ORDER BY id ASC";
        System.out.println("\n--- Accounts Snapshot [" + title + "] ---");
        try (Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery(query)) {
            while (rs.next()) {
                System.out.printf("  Account #%d | Holder: %-12s | Balance: $%.2f%n",
                        rs.getInt("id"),
                        rs.getString("holder_name"),
                        rs.getDouble("balance"));
            }
        }
    }
}
Execution Result (javac & java)
$ javac EnterpriseBankingDAO.java $ java EnterpriseBankingDAO ================================================== DEVASCENT ENTERPRISE JDBC & TRANSACTION ENGINE ================================================== āœ“ Connected to Database: H2 āœ“ Created table 'accounts' āœ“ Inserted 2 initial account records --- Accounts Snapshot [Initial State] --- Account #101 | Holder: Alice Vance | Balance: $5000.00 Account #102 | Holder: Bob Miller | Balance: $1200.00 --- Executing Atomic Transaction: Transfer $1000 --- āœ“ Transaction committed atomically! --- Accounts Snapshot [Post-Transfer State] --- Account #101 | Holder: Alice Vance | Balance: $4000.00 Account #102 | Holder: Bob Miller | Balance: $2200.00 āœ“ Deleted Account #102 (Rows affected: 1) --- Accounts Snapshot [Final State] --- Account #101 | Holder: Alice Vance | Balance: $4000.00 ==================================================
08

University Exam & Technical Interview Viva Preparation Guide

Q1. What is the fundamental difference between Statement and PreparedStatement?
Statement compiles and parses dynamic SQL strings on every execution, making it vulnerable to SQL Injection attacks and slower for repetitive queries. PreparedStatement pre-compiles parameterized SQL containing ? placeholders once on the database server, caching the execution plan. It separates user input data from SQL syntax, guaranteeing absolute immunity against SQL Injection and delivering higher performance for batched executions.
Q2. Why is Type 4 Thin Driver considered the industry standard?
The Type 4 Thin Driver is written entirely in 100% Pure Java bytecode. It translates JDBC calls directly into the database engine's native socket communication protocol without requiring any intermediate native C/C++ client libraries (unlike Type 2) or ODBC configuration (unlike Type 1). This makes Type 4 drivers completely platform-independent, easily deployable via Maven dependencies, and highest in network throughput.
Q3. Why is Class.forName("...") no longer strictly required in JDBC 4.0+?
Starting in JDBC 4.0 (Java 6+), the Java ServiceLoader mechanism automatically detects and registers all JDBC drivers present on the application classpath. Driver JAR files contain a registration manifest located in META-INF/services/java.sql.Driver. When DriverManager.getConnection() is invoked, it dynamically discovers and instantiates the appropriate registered driver automatically.
Q4. How do you manage ACID transactions in JDBC?
By default, JDBC connections have autoCommit = true. To manage transactions manually:
1. Disable auto-commit: conn.setAutoCommit(false);
2. Execute a sequence of SQL operations via PreparedStatement.
3. Call conn.commit(); if all operations succeed.
4. Call conn.rollback(); inside the catch (SQLException e) block to discard intermediate writes if any statement fails.
5. Finally, restore conn.setAutoCommit(true);.
Q5. What is the difference between executeUpdate() and executeQuery()?
executeQuery() is exclusively designed for SELECT queries and returns a ResultSet cursor containing matching tabular rows. executeUpdate() is used for data manipulation statements (INSERT, UPDATE, DELETE) and DDL commands (CREATE, ALTER, DROP), returning an integer representing the number of rows modified in the database.
Q6. What happens if you do not close JDBC Connection, Statement, or ResultSet objects?
Failing to close JDBC resources causes severe connection leaks and cursor leaks. The database server keeps open physical TCP sockets, internal buffer pools, and table locks allocated to unclosed cursors. Under sustained web traffic, the database exhausts its maximum allowed connection pool limit, causing subsequent requests to fail with SQLException: Too many connections. Always close resources via Try-with-resources.
Q7. What is a BatchUpdateException and how does Batch Processing optimize JDBC performance?
Sending individual SQL statements across the network one-by-one incurs massive network latency round-trips. With JDBC Batching, multiple queries are queued via pstmt.addBatch() and transmitted to the database engine in a single combined network frame via pstmt.executeBatch(). If any statement in the batch fails, the database driver throws a BatchUpdateException containing an array indicating which specific statements succeeded prior to failure.
Q8. How does a database cursor work inside a ResultSet?
When a query returns, the ResultSet cursor initially points before the first row. Invoking rs.next() advances the cursor to the first row and returns true. Subsequent invocations advance the pointer row-by-row until no rows remain, returning false. Column values for the current row are extracted using 1-based index or column name getters (e.g., rs.getString("username")).