Java案例如何处理数据库事务?

wen java案例 14

本文目录导读:

Java案例如何处理数据库事务?

  1. 方式一:JDBC原生事务(低层、手动控制)
  2. 方式二:Spring声明式事务(企业级、推荐)
  3. 总结对比

在Java中处理数据库事务,核心是保证一组数据库操作要么全部成功(提交),要么全部失败(回滚),以维护数据的一致性和完整性,主要分为两种主流方式:JDBC原生事务Spring声明式事务

下面我们分别介绍这两种方式的处理案例。


JDBC原生事务(低层、手动控制)

这种方式需要手动获取数据库连接,开启事务,执行SQL,然后根据结果选择提交或回滚,适用于学习理解或小型项目。

核心步骤:

  1. connection.setAutoCommit(false); // 关闭自动提交,开启事务
  2. 执行多条SQL语句。
  3. connection.commit(); // 所有操作成功,提交事务
  4. 如果发生异常,connection.rollback(); // 回滚所有操作
  5. finally 或 try-with-resources 中关闭资源。

案例代码:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class JdbcTransactionExample {
    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/your_database";
        String username = "root";
        String password = "password";
        // SQL语句:银行转账 A账户扣100,B账户加100
        String sqlDebit = "UPDATE accounts SET balance = balance - 100 WHERE account_id = ?";
        String sqlCredit = "UPDATE accounts SET balance = balance + 100 WHERE account_id = ?";
        // 使用try-with-resources自动关闭Connection, PreparedStatement
        try (Connection conn = DriverManager.getConnection(url, username, password);
             PreparedStatement pstmtDebit = conn.prepareStatement(sqlDebit);
             PreparedStatement pstmtCredit = conn.prepareStatement(sqlCredit)) {
            // 1. 开启事务:关闭自动提交
            conn.setAutoCommit(false);
            // 2. 执行转账操作
            // 扣钱
            pstmtDebit.setInt(1, 1); // 账户1扣钱
            int affectedRowsDebit = pstmtDebit.executeUpdate();
            // 加钱
            pstmtCredit.setInt(1, 2); // 账户2加钱
            int affectedRowsCredit = pstmtCredit.executeUpdate();
            // 模拟一个异常,触发回滚(比如业务校验失败)
            // if (someCondition) { throw new RuntimeException("业务异常"); }
            // 3. 所有操作成功,提交事务
            conn.commit();
            System.out.println("转账成功!");
        } catch (SQLException e) {
            // 4. 发生异常,尝试回滚
            // 注意:这里连接对象需要提前获取,或者使用嵌套try-catch
            System.err.println("转账失败,尝试回滚...");
            // 由于try-with-resources在catch之前关闭连接,我们需要在catch中获取连接对象回滚,
            // 通常做法是将Connection声明在try外面,或者使用更复杂的嵌套。
            // 为简化演示,这里省略了回滚具体代码,实际应使用 connection.rollback();
            e.printStackTrace();
            // 更规范的写法是:在catch块中获取connection对象并rollback
        }
    }
}

注意事项:

  • 务必在 finallytry-with-resources 中关闭 Connection、Statement、ResultSet 等资源,防止内存泄漏。
  • 回滚操作需要 Connection 对象尚未被关闭,JDBC 事务通常需要将 Connection 声明在 try 块外面,以便在 catch 中回滚。

Spring声明式事务(企业级、推荐)

在Spring框架中,最常用的是使用 @Transactional 注解(声明式事务),它基于AOP实现,让开发者只需关注业务逻辑,事务管理由Spring容器自动处理。

核心步骤:

  1. 配置数据源和事务管理器(通常在Spring Boot中自动配置,如DataSourceTransactionManager或JpaTransactionManager)。
  2. 在需要事务的类或方法上添加 @Transactional 注解。
  3. Spring会在方法执行前开启事务,执行完正常提交,抛出RuntimeException(默认)时回滚。

案例代码(Spring Boot + MyBatis/Spring Data JPA):

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class TransferService {
    @Autowired
    private AccountDao accountDao; // 假设使用MyBatis的Mapper或JPA的Repository
    /**
     * 转账操作,整个方法在一个数据库事务中执行
     * @param fromAccountId 转出方账户ID
     * @param toAccountId   转入方账户ID
     * @param amount        转账金额
     */
    @Transactional(rollbackFor = Exception.class) // 可以指定哪些异常需要回滚
    public void transferMoney(int fromAccountId, int toAccountId, double amount) {
        // 1. 扣钱
        if (accountDao.debitBalance(fromAccountId, amount) != 1) {
            // 如果扣钱影响行数不为1,抛出异常触发回滚
            throw new IllegalStateException("账户扣款失败,可能账户不存在或余额不足");
        }
        // 2. 加钱
        if (accountDao.creditBalance(toAccountId, amount) != 1) {
            // 如果加钱影响行数不为1,抛出异常触发回滚(注意:此时扣钱操作也会被回滚)
            throw new IllegalStateException("账户入账失败,可能目标账户不存在");
        }
        // 3. 其他业务逻辑(如记录日志),如果不抛出异常,全部成功提交
        // ...
    }
}

@Transactional 关键属性:

  • propagation:事务传播行为(如 REQUIRED、REQUIRES_NEW、NESTED 等)。
    • REQUIRED(默认):支持当前事务,如果没有则创建新事务。
    • REQUIRES_NEW:挂起当前事务,创建新事务执行当前方法。
  • isolation:事务隔离级别(如 READ_COMMITTED、REPEATABLE_READ、SERIALIZABLE)。
  • timeout:事务超时时间(秒),超时自动回滚。
  • rollbackFor:指定哪些异常触发回滚(默认只回滚RuntimeException和Error,检查异常不受影响)。
  • noRollbackFor:指定哪些异常不触发回滚。

Spring事务失效的常见原因:

  1. 方法不能是 private:Spring AOP代理无法拦截私有方法。
  2. 方法被类内其他方法调用(自调用)this.doSomething(),没有通过代理对象调用,事务失效,解决方案:注入自身Service代理,或使用AopContext.currentProxy()。
  3. 异常被方法内 try-catch 捕获且未重新抛出:Spring无法感知异常,不会回滚。
  4. 事务管理器配置错误:数据源、事务管理器未正确注入。
  5. 数据库不支持事务(如某些MyISAM引擎的表)。

总结对比

特性 JDBC原生事务 Spring声明式事务
控制方式 手动(setAutocommit, commit, rollback) 自动(注解驱动,AOP)
代码侵入性 低(只需添加注解)
灵活性 高,可精细控制 高,通过属性配置
推荐场景 学习、简单项目、非Spring项目 企业级应用、Spring/Spring Boot项目
资源管理 需手动关闭(建议try-with-resources) 自动管理
异常回滚 需要手动捕获异常并调用rollback 默认回滚RuntimeException,可配置

最佳实践:

  • 大多数Java Web项目直接使用 Spring声明式事务,结合 @Transactional 注解,简洁高效。
  • 如果你不使用Spring框架(如纯Servlet/JDBC),则必须使用 JDBC原生事务 手动管理。
  • 对于复杂的事务场景(如嵌套事务、长事务),需要理解事务的传播行为和隔离级别,谨慎设计。

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