Java中實(shí)現(xiàn)事務(wù)的幾種方法代碼示例
前言
事務(wù)是數(shù)據(jù)庫操作中的重要概念,它確保了一組操作要么全部成功,要么全部失敗,從而保證數(shù)據(jù)的一致性和完整性。在 Java 中,我們有多種方式來實(shí)現(xiàn)事務(wù)管理。本文將詳細(xì)介紹幾種常用的事務(wù)實(shí)現(xiàn)方法,并提供相應(yīng)的代碼示例。
1. JDBC 原生事務(wù)
JDBC 提供了最基礎(chǔ)的事務(wù)控制方式,通過 Connection 對(duì)象來管理事務(wù)。默認(rèn)情況下,JDBC 是自動(dòng)提交事務(wù)的,我們可以通過手動(dòng)設(shè)置來控制事務(wù)。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class JDBCTransactionExample {
// 數(shù)據(jù)庫連接信息
private static final String URL = "jdbc:mysql://localhost:3306/testdb";
private static final String USER = "root";
private static final String PASSWORD = "password";
public void transferMoney(int fromAccountId, int toAccountId, double amount) {
Connection connection = null;
PreparedStatement debitStmt = null;
PreparedStatement creditStmt = null;
try {
// 獲取數(shù)據(jù)庫連接
connection = DriverManager.getConnection(URL, USER, PASSWORD);
// 關(guān)閉自動(dòng)提交,開始事務(wù)
connection.setAutoCommit(false);
// 從源賬戶扣款
String debitSql = "UPDATE accounts SET balance = balance - ? WHERE id = ?";
debitStmt = connection.prepareStatement(debitSql);
debitStmt.setDouble(1, amount);
debitStmt.setInt(2, fromAccountId);
debitStmt.executeUpdate();
// 向目標(biāo)賬戶存款
String creditSql = "UPDATE accounts SET balance = balance + ? WHERE id = ?";
creditStmt = connection.prepareStatement(creditSql);
creditStmt.setDouble(1, amount);
creditStmt.setInt(2, toAccountId);
creditStmt.executeUpdate();
// 所有操作成功,提交事務(wù)
connection.commit();
System.out.println("轉(zhuǎn)賬成功!");
} catch (SQLException e) {
System.err.println("轉(zhuǎn)賬失敗,回滾事務(wù): " + e.getMessage());
try {
// 發(fā)生異常,回滾事務(wù)
if (connection != null) {
connection.rollback();
}
} catch (SQLException ex) {
System.err.println("回滾事務(wù)失敗: " + ex.getMessage());
}
} finally {
// 關(guān)閉資源
try {
if (debitStmt != null) debitStmt.close();
if (creditStmt != null) creditStmt.close();
if (connection != null) {
// 恢復(fù)自動(dòng)提交模式
connection.setAutoCommit(true);
connection.close();
}
} catch (SQLException e) {
System.err.println("關(guān)閉資源失敗: " + e.getMessage());
}
}
}
public static void main(String[] args) {
JDBCTransactionExample example = new JDBCTransactionExample();
// 示例:從賬戶1向賬戶2轉(zhuǎn)賬100元
example.transferMoney(1, 2, 100.0);
}
}
JDBC 事務(wù)的核心 API:
setAutoCommit(false):關(guān)閉自動(dòng)提交,開始事務(wù)commit():提交事務(wù)rollback():回滾事務(wù)setSavepoint():設(shè)置保存點(diǎn),可部分回滾
2. Spring 編程式事務(wù)
Spring 框架提供了更靈活的事務(wù)管理方式。編程式事務(wù)通過 TransactionTemplate 或直接使用 PlatformTransactionManager 來控制事務(wù)。
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
public class SpringProgrammaticTransactionExample {
private final TransactionTemplate transactionTemplate;
private final JdbcTemplate jdbcTemplate;
// 通過構(gòu)造函數(shù)注入依賴
public SpringProgrammaticTransactionExample(TransactionTemplate transactionTemplate, JdbcTemplate jdbcTemplate) {
this.transactionTemplate = transactionTemplate;
this.jdbcTemplate = jdbcTemplate;
}
public void transferMoney(int fromAccountId, int toAccountId, double amount) {
// 使用TransactionTemplate執(zhí)行事務(wù)
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
// 從源賬戶扣款
jdbcTemplate.update(
"UPDATE accounts SET balance = balance - ? WHERE id = ?",
amount, fromAccountId
);
// 向目標(biāo)賬戶存款
jdbcTemplate.update(
"UPDATE accounts SET balance = balance + ? WHERE id = ?",
amount, toAccountId
);
System.out.println("轉(zhuǎn)賬成功!");
} catch (Exception e) {
System.err.println("轉(zhuǎn)賬失敗,準(zhǔn)備回滾: " + e.getMessage());
// 標(biāo)記事務(wù)為回滾狀態(tài)
status.setRollbackOnly();
}
}
});
}
public static void main(String[] args) {
// 實(shí)際應(yīng)用中,這些會(huì)由Spring容器管理
// ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// SpringProgrammaticTransactionExample example = context.getBean(SpringProgrammaticTransactionExample.class);
// 示例調(diào)用
// example.transferMoney(1, 2, 100.0);
}
}
Spring 編程式事務(wù)的配置(XML 方式):
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 數(shù)據(jù)源配置 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/testdb"/>
<property name="username" value="root"/>
<property name="password" value="password"/>
</bean>
<!-- 事務(wù)管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置TransactionTemplate -->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"/>
<!-- 可以設(shè)置默認(rèn)的事務(wù)屬性 -->
<property name="isolationLevelName" value="ISOLATION_READ_COMMITTED"/>
<property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"/>
</bean>
<!-- JdbcTemplate配置 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 業(yè)務(wù)類 -->
<bean id="transactionExample" class="SpringProgrammaticTransactionExample">
<constructor-arg ref="transactionTemplate"/>
<constructor-arg ref="jdbcTemplate"/>
</bean>
<!-- 啟用事務(wù)注解支持 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
3. Spring 聲明式事務(wù)(注解方式)
聲明式事務(wù)是 Spring 中最常用的事務(wù)管理方式,它通過注解或 XML 配置來管理事務(wù),無需在代碼中顯式編寫事務(wù)控制邏輯。
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class AccountService {
private final JdbcTemplate jdbcTemplate;
// 構(gòu)造函數(shù)注入JdbcTemplate
public AccountService(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
/**
* 轉(zhuǎn)賬操作,使用@Transactional注解聲明事務(wù)
* propagation = Propagation.REQUIRED:如果當(dāng)前沒有事務(wù),就新建一個(gè)事務(wù);如果已經(jīng)存在一個(gè)事務(wù)中,加入到這個(gè)事務(wù)中
* isolation = Isolation.READ_COMMITTED:讀取已提交的數(shù)據(jù)
* rollbackFor = Exception.class:遇到任何異常都回滾
*/
@Transactional(
propagation = org.springframework.transaction.annotation.Propagation.REQUIRED,
isolation = org.springframework.transaction.annotation.Isolation.READ_COMMITTED,
rollbackFor = Exception.class
)
public void transferMoney(int fromAccountId, int toAccountId, double amount) {
// 從源賬戶扣款
jdbcTemplate.update(
"UPDATE accounts SET balance = balance - ? WHERE id = ?",
amount, fromAccountId
);
// 模擬可能出現(xiàn)的異常
// if (true) throw new RuntimeException("模擬異常,觸發(fā)回滾");
// 向目標(biāo)賬戶存款
jdbcTemplate.update(
"UPDATE accounts SET balance = balance + ? WHERE id = ?",
amount, toAccountId
);
System.out.println("轉(zhuǎn)賬成功!");
}
}
@Transactional 注解的主要屬性:
propagation:事務(wù)傳播行為,如REQUIRED、REQUIRES_NEW等isolation:事務(wù)隔離級(jí)別,如READ_COMMITTED、REPEATABLE_READ等rollbackFor:指定哪些異常觸發(fā)回滾noRollbackFor:指定哪些異常不觸發(fā)回滾timeout:事務(wù)超時(shí)時(shí)間readOnly:是否為只讀事務(wù)
4. EJB 事務(wù)
EJB(Enterprise JavaBeans)也提供了事務(wù)管理功能,主要通過注解來聲明事務(wù)屬性。
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;
@Stateless
public class AccountEJB {
@PersistenceContext(unitName = "accountPU")
private EntityManager em;
/**
* 轉(zhuǎn)賬操作,使用EJB的@Transactional注解
* Transactional.TxType.REQUIRED:如果當(dāng)前沒有事務(wù),就新建一個(gè)事務(wù);如果已經(jīng)存在一個(gè)事務(wù)中,加入到這個(gè)事務(wù)中
*/
@Transactional(Transactional.TxType.REQUIRED)
public void transferMoney(int fromAccountId, int toAccountId, double amount) {
Account fromAccount = em.find(Account.class, fromAccountId);
Account toAccount = em.find(Account.class, toAccountId);
if (fromAccount == null || toAccount == null) {
throw new IllegalArgumentException("賬戶不存在");
}
if (fromAccount.getBalance() < amount) {
throw new IllegalStateException("余額不足");
}
// 執(zhí)行轉(zhuǎn)賬操作
fromAccount.setBalance(fromAccount.getBalance() - amount);
toAccount.setBalance(toAccount.getBalance() + amount);
// 合并實(shí)體狀態(tài)
em.merge(fromAccount);
em.merge(toAccount);
System.out.println("轉(zhuǎn)賬成功!");
}
}
// 賬戶實(shí)體類
import javax.persistence.Entity;
import javax.persistence.Id;
@Entity
class Account {
@Id
private int id;
private String name;
private double balance;
// getter和setter方法
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public double getBalance() { return balance; }
public void setBalance(double balance) { this.balance = balance; }
}
EJB 事務(wù)的傳播級(jí)別主要有:
REQUIRED:默認(rèn)值,如果當(dāng)前有事務(wù)則加入,否則創(chuàng)建新事務(wù)REQUIRES_NEW:總是創(chuàng)建新事務(wù)SUPPORTS:如果當(dāng)前有事務(wù)則加入,否則非事務(wù)執(zhí)行MANDATORY:必須在已有事務(wù)中執(zhí)行,否則拋出異常NOT_SUPPORTED:非事務(wù)執(zhí)行,如果當(dāng)前有事務(wù)則暫停NEVER:非事務(wù)執(zhí)行,如果當(dāng)前有事務(wù)則拋出異常
5. 分布式事務(wù)
在分布式系統(tǒng)中,事務(wù)可能涉及多個(gè)數(shù)據(jù)源或服務(wù),這時(shí)需要使用分布式事務(wù)解決方案。Java 中常用的分布式事務(wù)實(shí)現(xiàn)包括 JTA(Java Transaction API)和一些開源框架如 Seata、Hmily 等。
import javax.annotation.Resource;
import javax.ejb.Stateless;
import javax.transaction.UserTransaction;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
@Stateless
public class JTADistributedTransaction {
// 注入JTA事務(wù)管理
@Resource
private UserTransaction utx;
// 數(shù)據(jù)庫1連接信息
private static final String DB1_URL = "jdbc:mysql://localhost:3306/db1";
private static final String DB1_USER = "root";
private static final String DB1_PASSWORD = "password";
// 數(shù)據(jù)庫2連接信息
private static final String DB2_URL = "jdbc:mysql://localhost:3306/db2";
private static final String DB2_USER = "root";
private static final String DB2_PASSWORD = "password";
public void distributeTransaction() throws Exception {
Connection conn1 = null;
Connection conn2 = null;
PreparedStatement stmt1 = null;
PreparedStatement stmt2 = null;
try {
// 開始JTA事務(wù)
utx.begin();
// 連接第一個(gè)數(shù)據(jù)庫并執(zhí)行操作
conn1 = DriverManager.getConnection(DB1_URL, DB1_USER, DB1_PASSWORD);
// 注意:不要設(shè)置autoCommit,由JTA管理
String sql1 = "INSERT INTO logs (message) VALUES (?)";
stmt1 = conn1.prepareStatement(sql1);
stmt1.setString(1, "操作1執(zhí)行成功");
stmt1.executeUpdate();
// 連接第二個(gè)數(shù)據(jù)庫并執(zhí)行操作
conn2 = DriverManager.getConnection(DB2_URL, DB2_USER, DB2_PASSWORD);
String sql2 = "INSERT INTO records (content) VALUES (?)";
stmt2 = conn2.prepareStatement(sql2);
stmt2.setString(1, "操作2執(zhí)行成功");
stmt2.executeUpdate();
// 提交事務(wù)
utx.commit();
System.out.println("分布式事務(wù)執(zhí)行成功!");
} catch (Exception e) {
System.err.println("分布式事務(wù)執(zhí)行失敗,準(zhǔn)備回滾: " + e.getMessage());
// 回滾事務(wù)
utx.rollback();
throw e;
} finally {
// 關(guān)閉資源
if (stmt1 != null) stmt1.close();
if (stmt2 != null) stmt2.close();
if (conn1 != null) conn1.close();
if (conn2 != null) conn2.close();
}
}
}
總結(jié)
Java 提供了多種事務(wù)實(shí)現(xiàn)方式,每種方式都有其適用場(chǎng)景:
- JDBC 原生事務(wù):適合簡(jiǎn)單應(yīng)用,直接操作數(shù)據(jù)庫連接,控制粒度細(xì)。
- Spring 編程式事務(wù):適合需要在代碼中精確控制事務(wù)邊界的場(chǎng)景。
- Spring 聲明式事務(wù):最常用的方式,通過注解或配置實(shí)現(xiàn),代碼侵入性低,適合大多數(shù)企業(yè)應(yīng)用。
- EJB 事務(wù):適合使用 EJB 技術(shù)的分布式企業(yè)應(yīng)用。
- 分布式事務(wù):適合跨多個(gè)數(shù)據(jù)源或服務(wù)的事務(wù)場(chǎng)景,實(shí)現(xiàn)復(fù)雜但必要時(shí)不可替代。
選擇合適的事務(wù)管理方式需要根據(jù)應(yīng)用的架構(gòu)、復(fù)雜度和性能要求來決定。在實(shí)際開發(fā)中,Spring 聲明式事務(wù)因其簡(jiǎn)潔性和靈活性而被廣泛采用。
到此這篇關(guān)于Java中實(shí)現(xiàn)事務(wù)的幾種方法的文章就介紹到這了,更多相關(guān)Java實(shí)現(xiàn)事務(wù)方法內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用redisTemplate的scan方式刪除批量key問題
這篇文章主要介紹了使用redisTemplate的scan方式刪除批量key問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12
Java深入數(shù)據(jù)結(jié)構(gòu)理解掌握抽象類與接口
在類中沒有包含足夠的信息來描繪一個(gè)具體的對(duì)象,這樣的類稱為抽象類,接口是Java中最重要的概念之一,它可以被理解為一種特殊的類,不同的是接口的成員沒有執(zhí)行體,是由全局常量和公共的抽象方法所組成,本文給大家介紹Java抽象類和接口,感興趣的朋友一起看看吧2022-05-05
Java構(gòu)建菜單樹的實(shí)現(xiàn)示例
本文主要介紹了Java構(gòu)建菜單樹的實(shí)現(xiàn)示例,像一級(jí)菜單,二級(jí)菜單,三級(jí)菜單甚至更多層級(jí)的菜單,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-05-05
springboot對(duì)數(shù)據(jù)庫密碼加密的實(shí)現(xiàn)
這篇文章主要介紹了springboot對(duì)數(shù)據(jù)庫密碼加密的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12
SpringBoot實(shí)現(xiàn)發(fā)送郵件任務(wù)
這篇文章主要為大家詳細(xì)介紹了SpringBoot實(shí)現(xiàn)發(fā)送郵件任務(wù),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-02-02

