一、前言

本文基于个人练习项目,演示如何使用 JDBC 连接 MySQL,完成:

  • 封装连接与关闭资源的 工具类JdbcUtil
  • 查询JdbcTest
  • 增删改JdbcInsert / JdbcUpdate / JdbcDelete
  • 事务转账示例(JdbcTransaction,含 PreparedStatement
    适合刚学完 JDBC 的同学对照运行与理解。

二、环境准备

  1. JDK:建议 JDK 8 及以上
  2. MySQL:本地安装并启动服务
  3. 数据库与驱动
    • 示例中连接库名:day16(可在 JdbcUtil 中修改)
    • 需自行准备表 t_user(字段含 id, username, password, email 等,与下面 SQL 一致即可)
    • 引入 MySQL JDBC 驱动 jar(如 mysql-connector-java),并加入 classpath
  4. 驱动类名说明(重要)
    • 示例使用:com.mysql.jdbc.Driver(偏老版本写法)
    • 若使用 MySQL 8+ 官方驱动,建议改为:com.mysql.cj.jdbc.Driver,URL 中常加:?serverTimezone=Asia/Shanghai&useSSL=false 等参数,避免时区与 SSL 报错

三、项目中的类说明

类名 作用
JdbcUtil 加载驱动、获取连接、统一关闭资源
JdbcTest SELECT 查询 t_user
JdbcInsert INSERT 插入用户
JdbcUpdate UPDATE 更新邮箱
JdbcDelete DELETE 按 id 删除
JdbcTransaction 关闭自动提交、commit / rollback 演示转账

四、工具类:JdbcUtil

职责:集中配置加载驱动获取 Connection关闭 ResultSet / Statement / Connection(避免每个类里重复写一堆 close)。

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
/**
 * JDBC工具类
 * 提供数据库连接获取和资源关闭的通用方法
 */
public class JdbcUtil {
    
    // 数据库配置信息
    private static final String DRIVER = "com.mysql.jdbc.Driver";
    private static final String URL = "jdbc:mysql://localhost:3306/day16";
    private static final String USER = "root";
    private static final String PASSWORD = "123456";
    
    // 静态代码块,在类加载时执行一次
    static {
        try {
            Class.forName(DRIVER);
            System.out.println("驱动加载成功!");
        } catch (Exception e) {
            System.err.println("驱动加载失败!");
            e.printStackTrace();
        }
    }
    
    /**
     * 获取数据库连接
     * @return Connection对象
     * @throws Exception 数据库连接异常
     */
    public static Connection getConnection() throws Exception {
        return DriverManager.getConnection(URL, USER, PASSWORD);
    }
    
    /**
     * 关闭数据库资源
     * @param resultSet 结果集
     * @param statement SQL语句执行器
     * @param connection 数据库连接
     */
    public static void close(ResultSet resultSet, Statement statement, Connection connection) {
        // 关闭ResultSet
        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        
        // 关闭Statement
        if (statement != null) {
            try {
                statement.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        
        // 关闭Connection
        if (connection != null) {
            try {
                connection.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    /**
     * 关闭数据库资源(重载方法,用于增删改操作)
     * @param statement SQL语句执行器
     * @param connection 数据库连接
     */
    public static void close(Statement statement, Connection connection) {
        close(null, statement, connection);
    }
}
要点简述:

static 块里 Class.forName:加载驱动(部分新版本 JDBC 可省略,但写上更直观)。
getConnection():真正建立连接。
close(...)finally 里调用,先关 ResultSet,再 Statement,最后 Connection,顺序符合常见规范。
五、查询:JdbcTest
使用 Statement.executeQuery 执行 SELECT,用 while (resultSet.next()) 遍历结果。

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
public class JdbcTest {
    public static void main(String[] args) {
        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;
        try {
            // 获取数据库连接
            connection = JdbcUtil.getConnection();
            System.out.println("数据库连接成功!");
            
            // 创建Statement
            statement = connection.createStatement();
            
            // 执行查询
            String sql = "select * from t_user";
            resultSet = statement.executeQuery(sql);
            
            // 处理结果集
            while (resultSet.next()) {
                int id = resultSet.getInt("id");
                String username = resultSet.getString("username");
                String pwd = resultSet.getString("password");
                String email = resultSet.getString("email");
                System.out.println("ID: " + id + ", Username: " + username + ", Password: " + pwd + ", Email: " + email);
            }
        } catch (Exception e) {
            System.err.println("发生错误:");
            e.printStackTrace();
        } finally {
            // 关闭资源
            JdbcUtil.close(resultSet, statement, connection);
        }
    }
}
六、插入:JdbcInsert
增删改使用 Statement.executeUpdate,返回 受影响行数。

import java.sql.Connection;
import java.sql.Statement;
public class JdbcInsert {
    public static void main(String[] args) {
        Connection connection = null;
        Statement statement = null;
        try {
            // 获取数据库连接
            connection = JdbcUtil.getConnection();
            System.out.println("数据库连接成功!");
            
            // 创建Statement
            statement = connection.createStatement();
            
            // 执行插入操作
            String sql = "INSERT INTO t_user (username, password, email) VALUES ('test_user2', 'test456', 'test2@example.com')";
            int rows = statement.executeUpdate(sql);
            
            System.out.println("插入成功!影响了 " + rows + " 行数据");
            
        } catch (Exception e) {
            System.err.println("发生错误:");
            e.printStackTrace();
        } finally {
            // 关闭资源
            JdbcUtil.close(statement, connection);
        }
    }
}
七、更新:JdbcUpdate
import java.sql.Connection;
import java.sql.Statement;
public class JdbcUpdate {
    public static void main(String[] args) {
        Connection connection = null;
        Statement statement = null;
        try {
            // 获取数据库连接
            connection = JdbcUtil.getConnection();
            System.out.println("数据库连接成功!");
            
            // 创建Statement
            statement = connection.createStatement();
            
            // 执行更新操作 - 将id为2的用户邮箱修改
            String sql = "UPDATE t_user SET email = 'newemail@example.com' WHERE id = 2";
            int rows = statement.executeUpdate(sql);
            
            System.out.println("更新成功!影响了 " + rows + " 行数据");
            
        } catch (Exception e) {
            System.err.println("发生错误:");
            e.printStackTrace();
        } finally {
            // 关闭资源
            JdbcUtil.close(statement, connection);
        }
    }
}
八、删除:JdbcDelete
import java.sql.Connection;
import java.sql.Statement;
public class JdbcDelete {
    public static void main(String[] args) {
        Connection connection = null;
        Statement statement = null;
        try {
            // 获取数据库连接
            connection = JdbcUtil.getConnection();
            System.out.println("数据库连接成功!");
            
            // 创建Statement
            statement = connection.createStatement();
            
            // 执行删除操作 - 删除id为1的用户
            String sql = "DELETE FROM t_user WHERE id = 1";
            int rows = statement.executeUpdate(sql);
            
            System.out.println("删除成功!影响了 " + rows + " 行数据");
            
        } catch (Exception e) {
            System.err.println("发生错误:");
            e.printStackTrace();
        } finally {
            // 关闭资源
            JdbcUtil.close(statement, connection);
        }
    }
}
小结:executeQuery 用于查询返回 ResultSet;executeUpdate 用于 INSERT/UPDATE/DELETE。生产环境更推荐 PreparedStatementSQL 注入,事务示例里已用到。

九、事务:JdbcTransaction
9.1 什么是事务?
多条 SQL 组成一个业务单元:要么全部成功提交,要么出错则全部回滚,避免出现「扣款成功、入账失败」导致数据不一致。

事务常见特性 ACIDA 原子性:要么全成,要么全败
C 一致性:数据约束与业务规则不被破坏
I 隔离性:并发事务之间相互隔离(程度与隔离级别有关)
D 持久性:提交后数据应持久保存
9.2 JDBC 里要记住的三行
connection.setAutoCommit(false);  // 关闭自动提交 = 手动控制事务
connection.commit();              // 全部成功则提交
connection.rollback();            // 异常则回滚
9.3 建表与测试数据(transaction_test.sql)
在 MySQL 中执行(与项目中 transaction_test.sql 一致):

-- JDBC事务演示 - 创建账户表和测试数据
-- 创建账户表
CREATE TABLE IF NOT EXISTS t_account (
    id INT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL,
    balance DECIMAL(10, 2) DEFAULT 0.00,
    create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 插入测试数据(如果表为空)
INSERT INTO t_account (username, balance) VALUES 
('张三', 5000.00),
('李四', 3000.00),
('王五', 8000.00)
ON DUPLICATE KEY UPDATE username=username;
-- 查询初始数据
SELECT * FROM t_account;
9.4 转账示例代码(JdbcTransaction)
下面程序演示:正常转账 与 余额不足触发回滚。

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
 * JDBC事务操作演示 - 简化版
 * 
 * 什么是事务?
 * 事务就是一系列操作,要么全部成功,要么全部失败。
 * 
 * 举个例子:
 * 张三给李四转账100元
 * - 步骤1:张三的账户减少100元
 * - 步骤2:李四的账户增加100元
 * 
 * 如果步骤1成功了,但步骤2失败了怎么办?
 * 如果没有事务,张三的钱就消失了!
 * 有了事务,如果任何一步失败,所有操作都会撤销(回滚)。
 */
public class JdbcTransaction {
    
    public static void main(String[] args) {
        System.out.println("=== 开始测试事务 ===\n");
        
        // 先查看初始余额
        System.out.println("【第1步】查看初始余额:");
        showAllAccounts();
        
        // 测试1:正常转账
        System.out.println("\n【第2步】测试正常转账:张三(1号) -> 李四(2号) 转500元");
        System.out.println("----------------------------------------");
        boolean success1 = transferMoney(1, 2, 500.0);
        if (success1) {
            System.out.println("结果:✓ 转账成功!\n");
        } else {
            System.out.println("结果:✗ 转账失败!\n");
        }
        
        // 测试2:余额不足
        System.out.println("【第3步】测试余额不足:张三(1号) -> 李四(2号) 转999999元");
        System.out.println("----------------------------------------");
        boolean success2 = transferMoney(1, 2, 999999.0);
        if (success2) {
            System.out.println("结果:✓ 转账成功!\n");
        } else {
            System.out.println("结果:✗ 转账失败(已自动撤销)\n");
        }
        
        // 最后查看最终余额
        System.out.println("【第4步】查看最终余额:");
        showAllAccounts();
        
        System.out.println("\n=== 测试结束 ===");
    }
    
    /**
     * 转账方法
     * @param fromId 转出账户ID(谁的钱要减少)
     * @param toId 转入账户ID(谁的钱要增加)
     * @param amount 转账金额
     * @return 是否成功
     */
    public static boolean transferMoney(int fromId, int toId, double amount) {
        Connection connection = null;
        PreparedStatement pstmtFrom = null;  // 用于执行转出操作
        PreparedStatement pstmtTo = null;    // 用于执行转入操作
        ResultSet resultSet = null;
        
        try {
            // ========== 第一步:连接数据库 ==========
            connection = JdbcUtil.getConnection();
            System.out.println("1. 数据库连接成功");
            
            // ========== 第二步:开启事务 ==========
            // 默认情况下,每执行一条SQL就会自动保存(自动提交)
            // 我们需要关闭自动提交,这样才能控制什么时候保存
            connection.setAutoCommit(false);
            System.out.println("2. 事务已开启(现在执行的SQL不会立即保存)");
            
            // ========== 第三步:检查余额 ==========
            String checkSql = "SELECT balance FROM t_account WHERE id = ?";
            PreparedStatement checkStmt = connection.prepareStatement(checkSql);
            checkStmt.setInt(1, fromId);
            resultSet = checkStmt.executeQuery();
            
            if (!resultSet.next()) {
                throw new RuntimeException("转出账户不存在!ID: " + fromId);
            }
            
            double balance = resultSet.getDouble("balance");
            System.out.println("3. 检查余额:当前有 " + balance + " 元");
            
            if (balance < amount) {
                throw new RuntimeException("余额不足!需要 " + amount + " 元,但只有 " + balance + " 元");
            }
            System.out.println("   余额充足,可以继续");
            
            // ========== 第四步:转出操作(扣钱) ==========
            String sqlFrom = "UPDATE t_account SET balance = balance - ? WHERE id = ?";
            pstmtFrom = connection.prepareStatement(sqlFrom);
            pstmtFrom.setDouble(1, amount);      // 设置扣款金额
            pstmtFrom.setInt(2, fromId);         // 设置扣款账户
            pstmtFrom.executeUpdate();
            System.out.println("4. 执行转出:从账户" + fromId + "扣除 " + amount + " 元");
            System.out.println("   (注意:此时还没有真正保存,只是临时修改)");
            
            // ========== 第五步:转入操作(加钱) ==========
            String sqlTo = "UPDATE t_account SET balance = balance + ? WHERE id = ?";
            pstmtTo = connection.prepareStatement(sqlTo);
            pstmtTo.setDouble(1, amount);       // 设置入账金额
            pstmtTo.setInt(2, toId);            // 设置入账账户
            int rowsTo = pstmtTo.executeUpdate();
            System.out.println("5. 执行转入:向账户" + toId + "增加 " + amount + " 元");
            
            // 检查转入账户是否存在
            if (rowsTo == 0) {
                throw new RuntimeException("转入账户不存在!ID: " + toId);
            }
            
            // ========== 第六步:提交事务(真正保存) ==========
            // 所有操作都成功了,现在把所有修改永久保存到数据库
            connection.commit();
            System.out.println("6. ✓ 提交事务:所有修改已永久保存");
            System.out.println("   转账完成!金额: " + amount + " 元");
            return true;
            
        } catch (Exception e) {
            // ========== 第七步:回滚事务(撤销所有修改) ==========
            // 如果上面任何一步出错了,就撤销所有已经做的修改
            System.out.println("\n   ✗ 发生错误:" + e.getMessage());
            
            if (connection != null) {
                try {
                    connection.rollback();  // 回滚:撤销所有未提交的修改
                    System.out.println("   ✗ 已回滚:所有修改已撤销,数据恢复原样");
                } catch (SQLException ex) {
                    System.err.println("   ✗ 回滚失败!");
                    ex.printStackTrace();
                }
            }
            return false;
        } finally {
            // ========== 第八步:清理资源 ==========
            // 恢复自动提交模式(为了不影响其他操作)
            if (connection != null) {
                try {
                    connection.setAutoCommit(true);
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            // 关闭所有数据库资源
            JdbcUtil.close(resultSet, pstmtFrom, connection);
            if (pstmtTo != null) {
                try {
                    pstmtTo.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    /**
     * 查看所有账户的余额
     */
    private static void showAllAccounts() {
        Connection connection = null;
        PreparedStatement pstmt = null;
        ResultSet resultSet = null;
        
        try {
            connection = JdbcUtil.getConnection();
            String sql = "SELECT id, username, balance FROM t_account ORDER BY id";
            pstmt = connection.prepareStatement(sql);
            resultSet = pstmt.executeQuery();
            
            System.out.println("   ID | 用户名 | 余额");
            System.out.println("   -------------------");
            while (resultSet.next()) {
                int id = resultSet.getInt("id");
                String username = resultSet.getString("username");
                double balance = resultSet.getDouble("balance");
                System.out.printf("   %d  | %-6s | %.2f%n", id, username, balance);
            }
        } catch (Exception e) {
            System.err.println("   查询失败:" + e.getMessage());
        } finally {
            JdbcUtil.close(resultSet, pstmt, connection);
        }
    }
}
9.5 编译运行(示例)
javac -encoding UTF-8 JdbcUtil.java JdbcTransaction.java
java JdbcTransaction
(其他类同理,把需要编译的 .java 一起写上即可。)

十、总结
工具类把驱动、URL、账号密码和关闭资源集中管理,业务代码更干净。
查询用 executeQuery,增删改用 executeUpdate,注意在 finally 中释放连接。
事务通过 setAutoCommit(false) + commit / rollback 保证转账等业务的数据一致性;示例中用 ? 占位符 的 PreparedStatement 更安全。
上线前建议:配置外置化(properties)、使用连接池(如 HikariCP)、并尽量全面关闭所有 Statement 资源。
说明:文中代码与注释来自个人学习项目;数据库名、表名、密码请按本机环境修改后再运行。若对你有帮助,欢迎点赞收藏。
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐