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

Spring JdbcTemplate 批量執行多條 SQL 實現事務一致性完整指南

     編輯:Java 相關知識

業務場景

電商下單扣款:需同時更新「賬戶餘額表」扣減金額、更新「訂單表」狀態為已支付。兩條 SQL 必須在同一事務中原子執行,任一失敗全盤回滾。

核心方案:batchUpdate + 事務管理

1. 配置事務管理器

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>

2. Service 層聲明式事務

@Service
public class OrderService {
    @Autowired JdbcTemplate jdbcTemplate;

    @Transactional(rollbackFor = Exception.class)
    public void placeOrder(String userId, String orderId, BigDecimal amount) {
        // 1. 凍結/扣減賬戶餘額
        int updated = jdbcTemplate.update(
            "UPDATE account SET balance = balance - ? WHERE user_id = ? AND balance >= ?",
            amount, userId, amount
        );
        if (updated == 0) throw new InsufficientBalanceException("餘額不足");

        // 2. 更新訂單狀態
        jdbcTemplate.update(
            "UPDATE orders SET status = 'PAID', pay_time = NOW() WHERE id = ? AND status = 'PENDING'",
            orderId
        );
        
        // 3. 記錄流水 (可選)
        jdbcTemplate.update(
            "INSERT INTO transaction_log (order_id, amount, type) VALUES (?, ?, 'PAY')",
            orderId, amount
        );
    }
}

批量執行多條 SQL 的兩種模式

模式一:同一 SQL 多組參數 (高性能)

@Transactional
public void batchInsertUsers(List<User> users) {
    String sql = "INSERT INTO users (id, name, email) VALUES (?, ?, ?)";
    List<Object[]> batchArgs = users.stream()
        .map(u -> new Object[]{u.getId(), u.getName(), u.getEmail()})
        .collect(Collectors.toList());
    
    int[] result = jdbcTemplate.batchUpdate(sql, batchArgs);
    // result[i] 為每條語句影響行數
}

模式二:多條不同 SQL 組合 (本文核心場景)

@Transactional
public void executeMultipleStatements() {
    String[] sqls = new String[]{
        "DELETE FROM role_menu WHERE role_id = 'R001'",
        "INSERT INTO role_menu (role_id, menu_id) VALUES ('R001', 'M001')",
        "INSERT INTO role_menu (role_id, menu_id) VALUES ('R001', 'M002')",
        "UPDATE role SET last_modified = NOW() WHERE id = 'R001'"
    };
    
    int[] results = jdbcTemplate.batchUpdate(sqls);
    // 任一拋出異常 → 事務回滾,全部不生效
}

對比:無事務控制的風險

// ❌ 危險示例:無 @Transactional
public void dangerousExample() {
    jdbcTemplate.update("DELETE FROM role_menu WHERE role_id = 'R001'");
    // 若此處拋異常 (如網絡中斷),DELETE 已提交,INSERT 不會執行
    // 導致數據不一致:關係被刪除但新關係未建立
    jdbcTemplate.update("INSERT INTO role_menu ...");
}

底層原理深度解析

  1. batchUpdate(String[]) 內部創建 Statement (非 PreparedStatement)
  2. 依次調用 stmt.addBatch(sql) 將所有 SQL 加入批處理
  3. 調用 stmt.executeBatch() 一次性發送到數據庫執行
  4. 數據庫在同一事務上下文中執行批次 (取決於連接 autoCommit=false)
  5. Spring 事務攔截器在方法開始前設置 connection.setAutoCommit(false)
  6. 方法正常返回 → connection.commit();拋異常 → connection.rollback()

注意事項與最佳實踐

  1. 數據庫引擎支持:InnoDB 支持事務,MyISAM 不支持
  2. 異常分類:@Transactional 默認僅回滾 RuntimeException,業務異常需指定 rollbackFor = Exception.class
  3. 批次大小控制:大量數據分批提交 (如每 1000 條),避免長事務鎖表、日志膨脹
  4. 參數化防注入:優先使用 batchUpdate(String, List<Object[]>) 參數化形式,而非拼接 SQL 字符串
  5. 只讀優化:查詢方法加 @Transactional(readOnly = true)

進階:編程式事務控制

@Autowired PlatformTransactionManager txManager;

public void complexBusinessLogic() {
    TransactionTemplate template = new TransactionTemplate(txManager);
    template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
    template.execute(status -> {
        try {
            jdbcTemplate.batchUpdate(sqlArray);
            return true;
        } catch (Exception e) {
            status.setRollbackOnly();
            throw new BusinessException("批量執行失敗", e);
        }
    });
}

總結

Spring JdbcTemplate batchUpdate(String[]) 配合 @Transactional 是實現多條 SQL 原子執行的標準方案。關鍵在於:聲明式事務邊界清晰、異常回滾機制可靠、批量執行減少網絡往返。生產環境務必驗證事務隔離級別、鎖等待超時配置,並建立事務監控告警體系。

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