引言
在企業級應用開發中,事務管理是一個至關重要的環節,它確保了數據的一致性和完整性。Spring 框架為我們提供了強大而靈活的事務管理功能,能夠幫助開發者更輕松地處理復雜的事務場景。本文將深入探討 Spring 框架的事務管理,包括相關的類和 API,以及聲明式事務管理的實現方式,并結合具體代碼示例進行詳細解釋。
1. Spring 框架的事務管理相關的類和 API
1.1 PlatformTransactionManager 接口
PlatformTransactionManager
?是 Spring 框架中用于管理事務的核心接口,它定義了事務的基本操作,如提交和回滾。該接口有具體的實現類,我們需要根據不同的持久層框架選擇合適的實現類。
接口方法
void commit(TransactionStatus status)
:用于提交事務。void rollback(TransactionStatus status)
:用于回滾事務。
實現類選擇
- DataSourceTransactionManager:如果使用 Spring 的 JDBC 模板或者 MyBatis 框架,應選擇這個實現類。因為這些框架都是基于數據源(DataSource)進行數據庫操作的,
DataSourceTransactionManager
?可以很好地與數據源集成,管理事務。 - HibernateTransactionManager:當使用 Hibernate 框架時,需要選擇這個實現類。它專門為 Hibernate 的事務管理而設計,能夠與 Hibernate 的會話(Session)和事務機制無縫對接。
1.2 TransactionDefinition 接口
TransactionDefinition
?接口定義了事務的一些基本屬性,包括事務隔離級別和事務傳播行為。
事務隔離級別
事務隔離級別用于控制多個事務之間的可見性和并發訪問。常見的隔離級別有:
ISOLATION_DEFAULT
:使用數據庫的默認隔離級別。ISOLATION_READ_UNCOMMITTED
:允許讀取未提交的數據,可能會導致臟讀、不可重復讀和幻讀問題。ISOLATION_READ_COMMITTED
:只能讀取已提交的數據,避免了臟讀,但仍可能出現不可重復讀和幻讀。ISOLATION_REPEATABLE_READ
:保證在同一個事務中多次讀取同一數據的結果是一致的,避免了臟讀和不可重復讀,但仍可能出現幻讀。ISOLATION_SERIALIZABLE
:最高的隔離級別,所有事務依次執行,避免了臟讀、不可重復讀和幻讀,但會降低并發性能。
事務傳播行為
事務傳播行為定義了在一個事務方法調用另一個事務方法時,事務應該如何處理。常見的傳播行為有:
PROPAGATION_REQUIRED
:如果當前存在事務,則加入該事務;如果不存在,則創建一個新的事務。PROPAGATION_SUPPORTS
:如果當前存在事務,則加入該事務;如果不存在,則以非事務方式執行。PROPAGATION_MANDATORY
:如果當前存在事務,則加入該事務;如果不存在,則拋出異常。PROPAGATION_REQUIRES_NEW
:無論當前是否存在事務,都創建一個新的事務,并掛起當前事務。PROPAGATION_NOT_SUPPORTED
:以非事務方式執行,如果當前存在事務,則掛起該事務。PROPAGATION_NEVER
:以非事務方式執行,如果當前存在事務,則拋出異常。PROPAGATION_NESTED
:如果當前存在事務,則在嵌套事務中執行;如果不存在,則創建一個新的事務。
?2. Spring 框架聲明式事務管理
2.1、XML 配置方式
1. 配置文件
jdbc.properties
:存儲數據庫連接信息,包括驅動類名、URL、用戶名和密碼。
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_db?useSSL=false&characterEncoding=utf8
jdbc.username=root
jdbc.password=root
applicationContext.xml
:核心配置文件,主要完成以下配置:- 加載屬性文件:通過?
<context:property - placeholder>
?加載?jdbc.properties
。 - 配置數據源:使用 Druid 數據源,將屬性文件中的配置注入。
- 配置平臺事務管理器:使用?
DataSourceTransactionManager
,并關聯數據源。 - 配置事務通知:通過?
<tx:advice>
?定義事務屬性,如?pay
?方法使用事務,find*
?方法只讀。 - 配置 AOP:通過?
<aop:config>
?定義切入點和通知的關聯。 - 配置 Service 和 Dao:將 Service 和 Dao 作為 Bean 注冊到 Spring 容器中,并進行依賴注入。
- 加載屬性文件:通過?
<?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:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx.xsd"><!-- 加載屬性文件 --><context:property-placeholder location="classpath:jdbc.properties"/><!-- 配置數據源 --><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="driverClassName" value="${jdbc.driverClassName}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></bean><!-- 配置平臺事務管理器 --><bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"/></bean><!-- 配置事務通知 --><tx:advice id="txAdvice" transaction-manager="transactionManager"><tx:attributes><!-- pay 方法使用事務 --><tx:method name="pay" isolation="DEFAULT" propagation="REQUIRED"/><!-- find 開頭的方法只讀 --><tx:method name="find*" read-only="true"/></tx:attributes></tx:advice><!-- 配置 AOP --><aop:config><aop:advisor advice-ref="txAdvice" pointcut="execution(public * com.qcbyjy.demo4.AccountServiceImpl.pay(..))"/></aop:config><!-- 配置 Service --><bean id="accountService" class="com.qcbyjy.demo1.AccountServiceImpl"><property name="accountDao" ref="accountDao"/></bean><!-- 配置 Dao --><bean id="accountDao" class="com.qcbyjy.demo1.AccountDaoImpl"><property name="dataSource" ref="dataSource"/></bean>
</beans>
2.Java類?
AccountDao.java
:定義數據訪問接口,包含?outMoney
、inMoney
?和?findMoney
?方法。
package com.qcbyjy.demo1;public interface AccountDao {void outMoney(String out, double money);void inMoney(String in, double money);double findMoney(String name);
}
AccountDaoImpl.java
:實現?AccountDao
?接口,通過?JdbcDaoSupport
?進行數據庫操作。
package com.qcbyjy.demo1;import org.springframework.jdbc.core.support.JdbcDaoSupport;public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {@Overridepublic void outMoney(String out, double money) {String sql = "update account set money=money-? where name=?";this.getJdbcTemplate().update(sql, money, out);}@Overridepublic void inMoney(String in, double money) {String sql = "update account set money=money+? where name=?";this.getJdbcTemplate().update(sql, money, in);}@Overridepublic double findMoney(String name) {String sql = "select money from account where name=?";return this.getJdbcTemplate().queryForObject(sql, Double.class, name);}
}
AccountService.java
:定義業務服務接口,包含?pay
?和?checkBalance
?方法。
package com.qcbyjy.demo1;public interface AccountService {void pay(String out, String in, double money);double checkBalance(String name);
}
AccountServiceImpl.java
:實現?AccountService
?接口,調用?AccountDao
?完成業務邏輯。
package com.qcbyjy.demo1;public class AccountServiceImpl implements AccountService {private AccountDao accountDao;public void setAccountDao(AccountDao accountDao) {this.accountDao = accountDao;}@Overridepublic void pay(String out, String in, double money) {accountDao.outMoney(out, money);// 模擬異常,測試事務回滾// int a = 1/0;accountDao.inMoney(in, money);}@Overridepublic double checkBalance(String name) {return accountDao.findMoney(name);}
}
3.測試?
package com.qcbyjy.test.demo1;import com.qcbyjy.demo1.AccountService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class Demo1 {@Testpublic void testXmlTransaction(){ApplicationContext context =new ClassPathXmlApplicationContext("applicationContext.xml");AccountService accountService =(AccountService) context.getBean("accountService");System.out.println("XML配置方式事務測試...");try {accountService.pay("aaa", "ccc", 100);System.out.println("轉賬成功!");} catch (Exception e) {System.out.println("轉賬失敗,事務已回滾:" + e.getMessage());}}
}
4. 重要知識點
- 事務通知和 AOP 配置:通過?
<tx:advice>
?定義事務屬性,再通過?<aop:config>
?將事務通知應用到指定的切入點,實現事務的織入。 - 屬性占位符:
<context:property - placeholder>
?可以方便地加載屬性文件,將配置信息從 XML 文件中分離出來,提高配置的可維護性。
2.2、XML + 注解方式
1. 配置文件
applicationContext_1.xml
:主要完成以下配置:- 開啟注解掃描:通過?
<context:component - scan>
?掃描指定包下的注解組件。 - 加載屬性文件:同 XML 配置方式。
- 配置數據源和事務管理器:同 XML 配置方式。
- 配置 Jdbc 模板:為 Dao 層提供數據庫操作模板。
- 開啟事務注解支持:通過?
<tx:annotation - driven>
?開啟?@Transactional
?注解的支持。
- 開啟注解掃描:通過?
<?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:context="http://www.springframework.org/schema/context"xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx.xsd"><!-- 開啟注解掃描 --><context:component-scan base-package="com.qcbyjy.demo2"/><!-- 加載屬性文件 --><context:property-placeholder location="classpath:db.properties"/><!-- 配置數據源 --><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="driverClassName" value="${jdbc.driverClassName}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></bean><!-- 配置平臺事務管理器 --><bean id="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"/></bean><!-- 配置Jdbc模板 --><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><property name="dataSource" ref="dataSource"/></bean><!-- 開啟事務注解支持 --><tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
2. Java 類
AccountDao.java
:同 XML 配置方式。AccountDaoImpl.java
:使用?@Repository
?注解將該類注冊為 Spring Bean,并通過?@Autowired
?注入?JdbcTemplate
。
package com.qcbyjy.demo2;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.stereotype.Repository;@Repository
public class AccountDaoImpl implements AccountDao {@Autowiredprivate JdbcTemplate jdbcTemplate;/***付款*@paramout*@parammoney*/public void outMoney(String out,double money){String sql="update account set money=money - ? where name = ?";jdbcTemplate.update(sql,money,out);}/***收款*@paramin*@parammoney*/public void inMoney(String in,double money){String sql="update account set money=money +? where name= ?";jdbcTemplate.update(sql,money,in);}}
AccountService.java
:同 XML 配置方式。AccountServiceImpl.java
:使用?@Service
?注解將該類注冊為 Spring Bean,并使用?@Transactional
?注解定義事務屬性。
package com.qcbyjy.demo2;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;@Service
@Transactional(isolation = Isolation.DEFAULT)
public class AccountServiceImpl implements AccountService {@Autowiredprivate AccountDao accountDao;/***轉賬方法*@paramout付款人*@paramin收款人*@parammoney金額*/public void pay(String out,String in,double money){accountDao.outMoney(out,money);// 模擬異常
// int a = 1/0;accountDao.inMoney(in,money);}}
3.測試
package com.qcbyjy.test.demo2;import com.qcbyjy.demo2.AccountService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class Demo2 {@Testpublic void testAnnotationTransaction(){ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext_1.xml");AccountService accountService=context.getBean(AccountService.class);System.out.println("XML+注解方式事務測試...");try {accountService.pay("ccc", "aaa", 100);System.out.println("轉賬成功!");} catch (Exception e) {System.out.println("轉賬失敗,事務已回滾:" + e.getMessage());}}
}
4. 重要知識點
- 注解掃描:
<context:component - scan>
?可以自動掃描指定包下的?@Component
、@Repository
、@Service
?和?@Controller
?注解的類,并將它們注冊為 Spring Bean。 @Transactional
?注解:用于定義事務屬性,如隔離級別、傳播行為、是否只讀等。可以應用在類或方法上,應用在類上時,該類的所有公共方法都將應用該事務屬性。
?2.3、純注解方式
1. Java 配置類
SpringConfig.java
:使用?@Configuration
?注解將該類標記為配置類,通過?@ComponentScan
?掃描指定包下的注解組件,使用?@EnableTransactionManagement
?開啟事務注解支持,并通過?@Bean
?方法定義數據源、Jdbc 模板和事務管理器。
package com.qcbyjy.demo3;import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;import javax.annotation.Resource;
import javax.sql.DataSource;@Configuration
@ComponentScan(basePackages = "com.qcbyjy.demo3")
@EnableTransactionManagement
public class SpringConfig {@Bean(name="dataSource")public DataSource dataSource() {// 創建連接池對象,Spring框架內置了連接池對象DruidDataSource dataSource = new DruidDataSource();// 設置4個參數dataSource.setDriverClassName("com.mysql.jdbc.Driver");dataSource.setUrl("jdbc:mysql://localhost:3306/spring_db?useSSL=false&characterEncoding=utf8");dataSource.setUsername("root");dataSource.setPassword("12345");return dataSource;}@Resource(name="dataSource")@Bean(name="jdbcTemplate")public JdbcTemplate createJdbcTemplate(DataSource dataSource){JdbcTemplate template =new JdbcTemplate(dataSource);return template;}@Resource(name="dataSource")@Bean(name="transactionManager")public PlatformTransactionManager createTransactionManager(DataSource dataSource) {DataSourceTransactionManager manager = new DataSourceTransactionManager(dataSource);return manager;}// @Bean
// public JdbcTemplate jdbcTemplate(DataSource dataSource) {
// return new JdbcTemplate(dataSource);
// }// @Bean
// public PlatformTransactionManager transactionManager(DataSource dataSource) {
// return new DataSourceTransactionManager(dataSource);
// }
}
2. Java 類
AccountDao.java
、AccountDaoImpl.java
、AccountService.java
?和?AccountServiceImpl.java
:同 XML + 注解方式。
3.測試
package com.qcbyjy.test.Demo3;import com.qcbyjy.demo3.AccountService;
import com.qcbyjy.demo3.SpringConfig;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class Demo3 {@Testpublic void testPureAnnotationTransaction() {AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(SpringConfig.class);AccountService accountService=context.getBean(AccountService.class);System.out.println("純注解方式事務測試...");try {accountService.pay("aaa", "ccc", 1000);System.out.println("轉賬成功!");} catch (Exception e) {System.out.println("轉賬失敗,事務已回滾:" + e.getMessage());}context.close();}
}
4. 重要知識點
- Java 配置類:使用?
@Configuration
?注解的類可以替代 XML 配置文件,通過?@Bean
?方法定義 Bean,提高配置的靈活性和可維護性。 @EnableTransactionManagement
:開啟 Spring 的事務注解支持,使得?@Transactional
?注解生效。
4.事務特性驗證
三種方式都可以通過取消注釋模擬異常的代碼(int a = 1/0;
)來測試事務回滾功能。每次測試前后會打印賬戶余額,驗證事務是否正常工作。同時,需要創建?spring_db
?數據庫和?account
?表,并初始化張三和李四的賬戶余額為 1000。
5.總結
- XML 配置方式:適合于對配置細節有嚴格要求,且團隊對 XML 配置比較熟悉的場景。通過 XML 可以清晰地定義事務的各個方面,但配置文件可能會變得冗長和復雜。
- XML + 注解方式:結合了 XML 配置的靈活性和注解的簡潔性,XML 負責全局配置,注解負責局部配置,是一種比較常用的方式。
- 純注解方式:適合于追求代碼簡潔性和開發效率的場景,完全基于 Java 配置和注解,減少了 XML 配置文件的使用,但對開發者的 Java 配置能力要求較高。