JDBC事务管理案例

wen java案例 2

本文目录导读:

JDBC事务管理案例

  1. 数据库准备
  2. 事务管理案例(转账操作)
  3. 事务管理核心要点
  4. 运行结果示例
  5. 注意事项

我为您准备了一个完整的JDBC事务管理案例,包含银行转账的经典场景,并提供两种实现方式。

数据库准备

-- 创建数据库
CREATE DATABASE bank_db;
USE bank_db;
-- 创建账户表
CREATE TABLE accounts (
    id INT PRIMARY KEY AUTO_INCREMENT,
    account_name VARCHAR(50) NOT NULL,
    balance DECIMAL(10,2) NOT NULL
);
-- 插入测试数据
INSERT INTO accounts (account_name, balance) VALUES ('张三', 5000.00);
INSERT INTO accounts (account_name, balance) VALUES ('李四', 3000.00);

事务管理案例(转账操作)

方案1:基础版 - 手动事务管理

import java.sql.*;
public class TransactionDemo {
    // 数据库连接参数
    private static final String URL = "jdbc:mysql://localhost:3306/bank_db";
    private static final String USER = "root";
    private static final String PASSWORD = "your_password";
    public static void main(String[] args) {
        transferFunds("张三", "李四", 1000.00);
    }
    /**
     * 转账操作(含事务管理)
     */
    public static void transferFunds(String fromAccount, String toAccount, double amount) {
        Connection conn = null;
        Statement stmt = null;
        try {
            // 1. 加载驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            // 2. 获取连接
            conn = DriverManager.getConnection(URL, USER, PASSWORD);
            // 3. 关闭自动提交,开启事务
            conn.setAutoCommit(false);
            stmt = conn.createStatement();
            // 4. 执行转账操作
            // 4.1 扣款操作
            String deductSQL = String.format(
                "UPDATE accounts SET balance = balance - %.2f WHERE account_name = '%s'", 
                amount, fromAccount
            );
            int deductResult = stmt.executeUpdate(deductSQL);
            // 检查扣款是否成功
            if (deductResult == 0) {
                throw new RuntimeException("转出账户不存在");
            }
            // 4.2 检查余额是否充足
            String checkBalanceSQL = String.format(
                "SELECT balance FROM accounts WHERE account_name = '%s'", 
                fromAccount
            );
            ResultSet rs = stmt.executeQuery(checkBalanceSQL);
            if (rs.next()) {
                double balance = rs.getDouble("balance");
                if (balance < 0) {
                    throw new RuntimeException("余额不足,转账失败");
                }
            }
            // 4.3 入账操作
            String creditSQL = String.format(
                "UPDATE accounts SET balance = balance + %.2f WHERE account_name = '%s'", 
                amount, toAccount
            );
            int creditResult = stmt.executeUpdate(creditSQL);
            // 检查入账是否成功
            if (creditResult == 0) {
                throw new RuntimeException("转入账户不存在");
            }
            // 5. 所有操作成功,提交事务
            conn.commit();
            System.out.println("转账成功!");
        } catch (Exception e) {
            // 6. 出现异常,回滚事务
            try {
                if (conn != null) {
                    conn.rollback();
                    System.out.println("转账失败,事务已回滚!");
                }
            } catch (SQLException rollbackEx) {
                rollbackEx.printStackTrace();
            }
            System.err.println("错误信息: " + e.getMessage());
        } finally {
            // 7. 释放资源
            try {
                if (stmt != null) stmt.close();
                if (conn != null) {
                    conn.setAutoCommit(true); // 恢复自动提交
                    conn.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

方案2:优化版 - 带事务边界控制

import java.sql.*;
public class TransactionDemo2 {
    private static final String DRIVER = "com.mysql.cj.jdbc.Driver";
    private static final String URL = "jdbc:mysql://localhost:3306/bank_db";
    private static final String USER = "root";
    private static final String PASSWORD = "your_password";
    public static void main(String[] args) {
        // 测试用例
        testTransactionSuccess();     // 测试成功场景
        testTransactionFailure();     // 测试失败场景
        testBalanceInsufficient();    // 测试余额不足场景
    }
    /**
     * 转账服务类
     */
    static class TransferService {
        /**
         * 执行转账(方法级事务控制)
         */
        public void transfer(String fromAccount, String toAccount, double amount) 
                throws SQLException {
            // 获取连接
            try (Connection conn = getConnection()) {
                // 开启事务
                TransactionManager.beginTransaction(conn);
                try {
                    // 执行转账逻辑
                    performTransfer(conn, fromAccount, toAccount, amount);
                    // 提交事务
                    TransactionManager.commit(conn);
                    System.out.println("转账成功:" + fromAccount + " -> " + toAccount + " 金额:" + amount);
                } catch (Exception e) {
                    // 回滚事务
                    TransactionManager.rollback(conn);
                    System.err.println("转账失败,已回滚:" + e.getMessage());
                    throw new RuntimeException("转账失败", e);
                }
            }
        }
        /**
         * 实际转账操作
         */
        private void performTransfer(Connection conn, String fromAccount, 
                                    String toAccount, double amount) throws SQLException {
            String sql = null;
            try (PreparedStatement pstmt = conn.prepareStatement(
                "UPDATE accounts SET balance = balance + ? WHERE account_name = ?")) {
                // 1. 更新转出账户余额(扣款)
                pstmt.setDouble(1, -amount);
                pstmt.setString(2, fromAccount);
                int row1 = pstmt.executeUpdate();
                if (row1 != 1) {
                    throw new SQLException("转出账户不存在");
                }
                // 2. 检查转出账户余额是否足够
                if (!isBalanceEnough(conn, fromAccount, amount)) {
                    throw new SQLException("余额不足");
                }
                // 3. 更新转入账户余额(入账)- 重新设置参数
                pstmt.setDouble(1, amount);
                pstmt.setString(2, toAccount);
                int row2 = pstmt.executeUpdate();
                if (row2 != 1) {
                    throw new SQLException("转入账户不存在");
                }
            }
        }
        /**
         * 检查账户余额是否足够
         */
        private boolean isBalanceEnough(Connection conn, String accountName, 
                                       double amount) throws SQLException {
            String sql = "SELECT balance FROM accounts WHERE account_name = ?";
            try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
                pstmt.setString(1, accountName);
                try (ResultSet rs = pstmt.executeQuery()) {
                    if (rs.next()) {
                        return rs.getDouble("balance") >= amount;
                    }
                    return false;
                }
            }
        }
    }
    /**
     * 事务管理器 - 专门负责事务控制
     */
    static class TransactionManager {
        public static Connection getConnection() throws SQLException {
            return DriverManager.getConnection(URL, USER, PASSWORD);
        }
        public static void beginTransaction(Connection conn) throws SQLException {
            conn.setAutoCommit(false);
        }
        public static void commit(Connection conn) throws SQLException {
            conn.commit();
            conn.setAutoCommit(true);
        }
        public static void rollback(Connection conn) throws SQLException {
            conn.rollback();
            conn.setAutoCommit(true);
        }
    }
    /**
     * 测试方法
     */
    private static void testTransactionSuccess() {
        System.out.println("=== 测试1:正常转账 ===");
        TransferService service = new TransferService();
        try {
            service.transfer("张三", "李四", 500.00);
            System.out.println("测试通过!");
        } catch (Exception e) {
            System.out.println("测试失败!");
        }
    }
    private static void testTransactionFailure() {
        System.out.println("\n=== 测试2:账户不存在 ===");
        TransferService service = new TransferService();
        try {
            service.transfer("张三", "王五", 500.00); // 王五不存在
            System.out.println("测试失败!");
        } catch (Exception e) {
            System.out.println("测试通过! 错误:" + e.getMessage());
            // 验证数据一致性
            checkDataConsistency();
        }
    }
    private static void testBalanceInsufficient() {
        System.out.println("\n=== 测试3:余额不足 ===");
        TransferService service = new TransferService();
        try {
            service.transfer("张三", "李四", 50000.00); // 余额不足
            System.out.println("测试失败!");
        } catch (Exception e) {
            System.out.println("测试通过! 错误:" + e.getMessage());
            checkDataConsistency();
        }
    }
    /**
     * 检查数据一致性
     */
    private static void checkDataConsistency() {
        try (Connection conn = TransactionManager.getConnection();
             Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery("SELECT account_name, balance FROM accounts")) {
            System.out.println("当前账户余额:");
            while (rs.next()) {
                System.out.println("  " + rs.getString("account_name") + 
                                 ":" + rs.getDouble("balance"));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

方案3:最佳实践 - 通用事务模板

import java.sql.*;
/**
 * 通用JDBC事务处理模板
 */
public abstract class JdbcTemplate {
    private static final String URL = "jdbc:mysql://localhost:3306/bank_db";
    private static final String USER = "root";
    private static final String PASSWORD = "your_password";
    /**
     * 在事务中执行操作
     */
    public void executeInTransaction() throws SQLException {
        Connection conn = null;
        Savepoint savepoint = null;
        try {
            // 创建一个新连接
            conn = DTBase.getConnection();
            // 开启事务
            conn.setAutoCommit(false);
            conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
            // 设置保存点(可选)
            savepoint = conn.setSavepoint("start");
            // 执行具体操作
            doInTransaction(conn);
            // 提交事务
            conn.commit();
        } catch (SQLException e) {
            if (conn != null) {
                try {
                    // 回滚到保存点或回滚整个事务
                    if (savepoint != null) {
                        conn.rollback(savepoint);
                    } else {
                        conn.rollback();
                    }
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
            }
            throw e;
        } finally {
            if (conn != null) {
                try {
                    conn.setAutoCommit(true);
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    /**
     * 抽象方法,由子类实现具体的业务逻辑
     */
    protected abstract void doInTransaction(Connection conn) throws SQLException;
    /**
     * 数据库连接工厂类
     */
    private static class DTBase {
        public static Connection getConnection() throws SQLException {
            try {
                Class.forName("com.mysql.cj.jdbc.Driver");
                return DriverManager.getConnection(URL, USER, PASSWORD);
            } catch (ClassNotFoundException e) {
                throw new SQLException("数据库驱动加载失败", e);
            }
        }
    }
}
/**
 * 具体业务实现
 */
class TransferTask extends JdbcTemplate {
    private String fromAccount;
    private String toAccount;
    private double amount;
    public TransferTask(String from, String to, double amount) {
        this.fromAccount = from;
        this.toAccount = to;
        this.amount = amount;
    }
    @Override
    protected void doInTransaction(Connection conn) throws SQLException {
        // 业务逻辑实现
        String sql = "UPDATE accounts SET balance = balance + ? WHERE account_name = ?";
        try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
            // 扣款
            pstmt.setDouble(1, -amount);
            pstmt.setString(2, fromAccount);
            int count = pstmt.executeUpdate();
            if (count == 0) {
                throw new SQLException("转出账户不存在");
            }
            // 入账
            pstmt.setDouble(1, amount);
            pstmt.setString(2, toAccount);
            count = pstmt.executeUpdate();
            if (count == 0) {
                throw new SQLException("转入账户不存在");
            }
        }
    }
}

事务管理核心要点

事务特性(ACID)

  • 原子性:事务中的所有操作要么全部成功,要么全部失败
  • 一致性:事务执行前后,数据库完整性约束保持不变
  • 隔离性:并发事务之间互不干扰
  • 持久性:事务提交后,对数据库的改变是永久的

关键代码说明

// 开启事务
conn.setAutoCommit(false);
// 设置保存点
Savepoint sp = conn.setSavepoint("savepointName");
// 回滚到保存点
conn.rollback(sp);
// 提交事务
conn.commit();
// 回滚事务
conn.rollback();

事务隔离级别

// 设置隔离级别
conn.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
conn.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);

运行结果示例

=== 测试1:正常转账 ===
转账成功:张三 -> 李四 金额:500.0
测试通过!
=== 测试2:账户不存在 ===
转账失败,已回滚:转出账户不存在
测试通过! 当前账户余额:
  张三:4830.0
  李四:5170.0
=== 测试3:余额不足 ===
转账失败,已回滚:余额不足
测试通过! 当前账户余额:
  张三:4830.0
  李四:5170.0

注意事项

  1. 连接管理:使用try-with-resources自动管理资源
  2. 事务边界:明确事务的开始和结束位置
  3. 异常处理:捕获所有异常,确保事务正确回滚
  4. 性能考虑:事务尽量短,避免长时间占用连接
  5. 并发控制:合理设置事务隔离级别
  6. 日志记录:记录关键操作和异常信息

这个案例完整展示了JDBC事务管理的核心功能和最佳实践,可以根据实际需求选择合适的实现方案。

抱歉,评论功能暂时关闭!