首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Java 本地事務深度解析:JDBC 事務機制、MySQL 存儲過程對比與代碼實戰

     編輯:Java 綜合教學

事務分類與本地事務定義

  • 本地事務:單一資源(單個 DataSource/Connection)上的事務,由資源管理器(如 MySQL)原生支援。
  • 分布式事務:跨多個資源(多庫、庫+MQ)的事務,需協調器(JTA/XA)參與。

本文聚焦**本地事務**,即 JDBC 操作單一數據庫的事務控制。

JDBC 事務核心 API(Connection 介面)

MySQL 存儲過程事務範例(純資料庫層)

CREATE PROCEDURE sp_create_user_with_role(
    IN p_user_name VARCHAR(25), IN p_password VARCHAR(25),
    IN p_gender VARCHAR(1), IN p_phone VARCHAR(11), IN p_role_id INT
)
BEGIN
    DECLARE t_error INT DEFAULT 0;
    DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET t_error = 1;

    START TRANSACTION;
    INSERT INTO users (user_name, password, gender, phone_no)
    VALUES (p_user_name, p_password, p_gender, p_phone);
    INSERT INTO user_role_relation (user_id, role_id)
    VALUES (LAST_INSERT_ID(), p_role_id);

    IF t_error = 0 THEN COMMIT; ELSE ROLLBACK; END IF;
END

驗證:第二條 SQL 違反非空約束 → 兩表均無新增記錄,體現原子性。

JDBC 手動事務控制完整代碼

public class JdbcTransactionDemo {
    private static final String URL = "jdbc:mysql://192.168.0.105:3306/TEST?useSSL=false&serverTimezone=UTC";
    private static final String USER = "root", PASS = "root";

    static { try { Class.forName("com.mysql.cj.jdbc.Driver"); } catch (Exception e) { throw new RuntimeException(e); } }

    public static void main(String[] args) {
        executeTransaction();
    }

    private static void executeTransaction() {
        String sqlUser = "INSERT INTO users (user_name, password, gender, phone_no) VALUES (?, ?, ?, ?)";
        String sqlRole = "INSERT INTO user_role_relation (user_id, role_id) VALUES (?, ?)";

        try (Connection conn = DriverManager.getConnection(URL, USER, PASS)) {
            conn.setAutoCommit(false);  // 關鍵:關閉自動提交

            try (PreparedStatement psUser = conn.prepareStatement(sqlUser, Statement.RETURN_GENERATED_KEYS);
                 PreparedStatement psRole = conn.prepareStatement(sqlRole)) {

                // 1. 插入用戶
                psUser.setString(1, "zhangsan");
                psUser.setString(2, "123456");
                psUser.setString(3, "男");
                psUser.setString(4, "11111111111");
                psUser.executeUpdate();

                // 獲取自增 ID
                Long userId = null;
                try (ResultSet rs = psUser.getGeneratedKeys()) {
                    if (rs.next()) userId = rs.getLong(1);
                }

                // 2. 插入角色關聯
                psRole.setLong(1, userId);
                psRole.setInt(2, 1);
                psRole.executeUpdate();

                conn.commit();  // 成功提交
                System.out.println("事務提交成功,userId=" + userId);
            } catch (SQLException e) {
                conn.rollback();  // 失敗回滾
                throw new RuntimeException("事務回滾: " + e.getMessage(), e);
            }
        } catch (SQLException e) {
            throw new RuntimeException("連接/提交/回滾異常", e);
        }
    }
}

關鍵最佳實踐

  1. try-with-resources 自動關閉 Connection/Statement/ResultSet,杜絕洩漏。
  2. PreparedStatement 防 SQL 注入、利用預編譯緩存提升性能。
  3. 獲取自增鍵:RETURN_GENERATED_KEYS + getGeneratedKeys(),跨資料庫通用。
  4. 異常處理:catch 塊中 rollback() 亦需 try-catch 包裹,防回滾拋異常掩蓋原始錯誤。
  5. 事務邊界清晰:業務邏輯方法層面控制事務,DAO 僅提供單條 SQL 執行。

常見誤區與避坑

方法作用預設行為
setAutoCommit(boolean)開關自動提交true(每條 SQL 隱式提交)
commit()提交事務手動調用
rollback()回滾事務手動調用
setSavepoint()設定保存點支援嵌套回滾
releaseSavepoint()釋放保存點--
誤區後果正確做法
忘記 setAutoCommit(false)每條 SQL 獨立提交,無法回滾事務開始第一行關閉自動提交
多線程共用 Connection事務交織、數據錯亂Connection 線程不安全,每請求一連接
catch 後未 rollback 直接拋異常連接池歸還帶事務連接,污染池finally 或 catch 中確保 rollback + close
在循環中逐條提交性能差、部分成功難回滾批次處理或整體事務

擴展:Spring @Transactional 聲明式事務

實際專案中極少直接用 JDBC API,而是:

@Service
@RequiredArgsConstructor
public class UserService {
    private final UserMapper userMapper;
    private final UserRoleMapper roleMapper;

    @Transactional(rollbackFor = Exception.class)
    public Long createUserWithRole(UserDTO dto) {
        User user = new User(dto.getName(), dto.getPassword(), dto.getGender(), dto.getPhone());
        userMapper.insert(user);  // MyBatis 自動回填 ID
        roleMapper.insert(new UserRole(user.getId(), dto.getRoleId()));
        return user.getId();
    }
}

Spring AOP 代理自動管理 Connection 生命週期、提交/回滾、異常轉換,開發者專注業務邏輯。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved