一、事務的作用
將若干的數據庫操作作爲一個整體控制,一起成功或一起失敗。原子性:指事務是一個不可分割的工作單位,事務中的操作要麼都發生,要麼都不發生。
一致性:指事務前後數據的完整性必須保持一致。
隔離性:指多個用戶併發訪問數據庫時,一個用戶的事務不能被其他用戶的事務所幹擾,多個併發事務之間數據要相互隔離。
持久性:指一個事務一旦被提交,它對數據庫中數據的改變就是永久性的,即時數據庫發生故障也不應該對其有任何影響。
二、Spring事務管理高層抽象主要包括3個接口
1、Platform TransactionManager 事務管理器(提交、回滾事務)Spring爲不同的持久化框架提供了不同的Platform TransactionManager接口實現。如:
使用Spring JDBC或iBatis進行持久化數據時使用DataSourceTransactionManager
使用Hibernate3.0版本進行持久化數據時使用HibernateTransactionManager
2、TransactionDefinition 事務定義信息(隔離、傳播、超時、只讀)
髒讀:一個事務讀取了另一個事務改寫但還未提交的數據,如果這些數據被回滾,則讀到的數據是無效的。
不可重複讀:在同一事務中,多次讀取同一數據返回的結果有所不同。
幻讀:一個事務讀取了幾行記錄後,另一個事務插入一些記錄,幻讀就發生了。再後來的查詢中,第一個事務就會發現有些原來沒有的記錄。
事務隔離級別:(五種)
- DEFAULT--使用後端數據庫默認的隔離級別(Spring中的選擇項)
- READ_UNCOMMITED--允許你讀取還未提交的改變了的數據。可能導致髒、幻、不可重複讀
- READ_COMMITTED--允許在併發事務已經提交後讀取。可防止髒讀,但幻讀和不可重複讀仍可發生
- REPEATABLE_READ--對相同字段的多次讀取是一致的,除非數據被事務本身改變。可防止髒、不可重複讀,但幻讀仍可能發生
- SERIALIZABLE--完全服從ACID的隔離級別,確保不發生髒、幻、不可重複讀。這在所有的隔離級別中是最慢的,它是典型的通過完全鎖定在事務中涉及的數據表來完成的
事務傳播行爲:(七種)
- REQUIRED--支持當前事務,如果當前沒有事務,就新建一個事務。這是最常見的選擇。
- SUPPORTS--支持當前事務,如果當前沒有事務,就以非事務方式執行。
- MANDATORY--支持當前事務,如果當前沒有事務,就拋出異常。
- REQUIRES_NEW--新建事務,如果當前存在事務,把當前事務掛起。
- NOT_SUPPORTED--以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。
- NEVER--以非事務方式執行,如果當前存在事務,則拋出異常。
- NESTED--如果當前存在事務,則在嵌套事務內執行。如果當前沒有事務,則進行與REQUIRED類似的操作。擁有多個可以回滾的保存點,內部回滾不會對外部事務產生影響。只對DataSourceTransactionManager有效
三、Spring提供了以下方法控制事務
1、編程式事務管理(基於Java編程控制,很少使用)利用TransactionTemplate將多個DAO操作封裝起來
2、聲明式事務管理(基於Spring的AOP配置控制)
-基於TransactionProxyFactoryBean的方式.(很少使用)
需要爲每個進行事務管理的類,配置一個TransactionProxyFactoryBean進行增強.
a.基於XML配置(經常使用)
一旦配置好之後,類上不需要添加任何東西。
如果Action作爲目標對象切入事務,需要在<aop:config>元素裏添加proxy-target-class="true"屬性。原因是通知Spring框架採用CGLIB技術生成具有事務管理功能的Action類。
b.基於註解(配置簡單,經常使用)
在applicationContext.xml中開啓事務註解配置。(applicationContext.xml中只需定義Bean並追加以下元素)
<bean id="txManager" class="...">
<property name="sessionFactory">
</property>
<tx:annotation-driven transaction-manager="txManager"/>
在目標組件類中使用@Transactional,該標記可定義在類前或方法前。
四、示例(銀行轉賬)
1、編程式
- /**
- * @Description:轉賬案例的DAO層接口
- *
- */
- public interface AccountDao {
- /**
- * @param out
- * :轉出賬號
- * @param money
- * :轉賬金額
- */
- public void outMoney(String out, Double money);
- /**
- *
- * @param in
- * :轉入賬號
- * @param money
- * :轉賬金額
- */
- public void inMoney(String in, Double money);
- }
- /**
- * @Description:轉賬案例的DAO層實現類
- */
- public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
- /**
- * @param out
- * :轉出賬號
- * @param money
- * :轉賬金額
- */
- @Override
- public void outMoney(String out, Double money) {
- String sql = "update account set money = money-? where name = ?";
- this.getJdbcTemplate().update(sql, money, out);
- }
- /**
- * @param in
- * :轉入賬號
- * @param money
- * :轉賬金額
- */
- @Override
- public void inMoney(String in, Double money) {
- String sql = "update account set money = money+? where name = ?";
- this.getJdbcTemplate().update(sql, money, in);
- }
- }
- /**
- * @Description:轉賬案例的業務接口
- *
- */
- public interface AccountService {
- /**
- * @param out :轉出賬號
- * @param in :轉入賬號
- * @param money :轉賬金額
- */
- public void transfer(String out,String in,Double money);
- }
- /**
- * @Description:轉賬案例的業務層實現類
- */
- public class AccountServiceImpl implements AccountService {
- // 注入轉賬的DAO
- private AccountDao accountDao;
- // 注入事務管理的模板
- private TransactionTemplate transactionTemplate;
- /**
- * @param out
- * :轉出賬號
- * @param in
- * :轉入賬號
- * @param money
- * :轉賬金額
- */
- @Override
- public void transfer(final String out, final String in, final Double money) {
- // 未經事務控制的業務處理操作,如果過程中出異常,則導致前面的操作能完成,後面的不能,即轉賬成功但未收到轉賬款
- // accountDao.outMoney(out, money);
- // int i = 1/0;
- // accountDao.inMoney(in, money);
- transactionTemplate.execute(new TransactionCallbackWithoutResult() {
- @Override
- protected void doInTransactionWithoutResult(
- TransactionStatus transactionStatus) {
- accountDao.outMoney(out, money);
- // int i = 1 / 0;//事務控制,即出現異常,該段內代碼都執行失效
- accountDao.inMoney(in, money);
- }
- });
- }
- public void setAccountDao(AccountDao accountDao) {
- this.accountDao = accountDao;
- }
- public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
- this.transactionTemplate = transactionTemplate;
- }
- }
applicationContext1.xml
- <!-- 引入外部的屬性文件 -->
- <context:property-placeholder location="classpath:jdbc.properties"/>
- <!-- 配置c3p0連接池 -->
- <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
- <property name="driverClass" value="${jdbc.driverClass}" />
- <property name="jdbcUrl" value="${jdbc.url}" />
- <property name="user" value="${jdbc.username}" />
- <property name="password" value="${jdbc.password}" />
- </bean>
- <!-- 配置業務層類 -->
- <bean id="accountService" class="com.zs.spring.demo1.AccountServiceImpl">
- <property name="accountDao" ref="accountDao" />
- <!-- 注入事務管理的模板 -->
- <property name="transactionTemplate" ref="transactionTemplate" />
- </bean>
- <!-- 配置DAO類(簡化,會自動配置JdbcTemplate) -->
- <bean id="accountDao" class="com.zs.spring.demo1.AccountDaoImpl">
- <property name="dataSource" ref="dataSource" />
- </bean>
- <!-- 配置DAO類(未簡化) -->
- <!-- <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
- <property name="dataSource" ref="dataSource" />
- </bean>
- <bean id="accountDao" class="com.zs.spring.demo1.AccountDaoImpl">
- <property name="jdbcTemplate" ref="jdbcTemplate" />
- </bean> -->
- <!-- ==================================1.編程式的事務管理=============================================== -->
- <!-- 配置事務管理器 -->
- <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
- <property name="dataSource" ref="dataSource" />
- </bean>
- <!-- 配置事務管理的模板:Spring爲了簡化事務管理的代碼而提供的類 -->
- <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
- <property name="transactionManager" ref="transactionManager"/>
- </bean>
測試:
- @RunWith(SpringJUnit4ClassRunner.class)
- @ContextConfiguration("classpath:applicationContext1.xml")
- public class TransactionTest {
- @Resource(name = "accountService")
- private AccountService accountService;
- @Test
- public void demo1() {
- accountService.transfer("aaa", "bbb", 200d);
- }
- }
2、基於TransactionProxyFactoryBean的方式
- public class AccountServiceImpl implements AccountService {
- // 注入轉賬的DAO
- private AccountDao accountDao;
- /**
- * @param out
- * :轉出賬號
- * @param in
- * :轉入賬號
- * @param money
- * :轉賬金額
- */
- @Override
- public void transfer(String out, String in, Double money) {
- accountDao.outMoney(out, money);
- // int i = 1/0;
- accountDao.inMoney(in, money);
- }
- public void setAccountDao(AccountDao accountDao) {
- this.accountDao = accountDao;
- }
- }
applicationContext2.xml
- <!-- 引入外部的屬性文件 -->
- <context:property-placeholder location="classpath:jdbc.properties"/>
- <!-- 配置c3p0連接池 -->
- <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
- <property name="driverClass" value="${jdbc.driverClass}" />
- <property name="jdbcUrl" value="${jdbc.url}" />
- <property name="user" value="${jdbc.username}" />
- <property name="password" value="${jdbc.password}" />
- </bean>
- <!-- 配置業務層類 -->
- <bean id="accountService" class="com.zs.spring.demo2.AccountServiceImpl">
- <property name="accountDao" ref="accountDao" />
- </bean>
- <!-- 配置DAO類(簡化,會自動配置JdbcTemplate) -->
- <bean id="accountDao" class="com.zs.spring.demo2.AccountDaoImpl">
- <property name="dataSource" ref="dataSource" />
- </bean>
- <!-- ==================================2.使用XML配置聲明式的事務管理(原始方式)=============================================== -->
- <!-- 配置事務管理器 -->
- <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
- <property name="dataSource" ref="dataSource" />
- </bean>
- <!-- 配置業務層的代理 -->
- <bean id="accountServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
- <!-- 配置目標對象 -->
- <property name="target" ref="accountService" />
- <!-- 注入事務管理器 -->
- <property name="transactionManager" ref="transactionManager"></property>
- <!-- 注入事務的屬性 -->
- <property name="transactionAttributes">
- <props>
- <!--
- prop的格式:
- * PROPAGATION :事務的傳播行爲
- * ISOTATION :事務的隔離級別
- * readOnly :只讀
- * -EXCEPTION :發生哪些異常回滾事務
- * +EXCEPTION :發生哪些異常不回滾事務
- -->
- <prop key="transfer">PROPAGATION_REQUIRED</prop>
- <!-- <prop key="transfer">PROPAGATION_REQUIRED,readOnly</prop> -->
- <!-- <prop key="transfer">PROPAGATION_REQUIRED,+java.lang.ArithmeticException</prop> -->
- </props>
- </property>
- </bean>
測試:
- @RunWith(SpringJUnit4ClassRunner.class)
- @ContextConfiguration("classpath:applicationContext2.xml")
- public class TransactionTest {
- /**
- * 一定要注入代理類:因爲代理類進行增強的操作
- */
- // @Resource(name="accountService")
- @Resource(name = "accountServiceProxy")
- private AccountService accountService;
- @Test
- public void demo1() {
- accountService.transfer("aaa", "bbb", 200d);
- }
- }
3、基於XML配置
- public class AccountServiceImpl implements AccountService {
- // 注入轉賬的DAO
- private AccountDao accountDao;
- /**
- * @param out
- * :轉出賬號
- * @param in
- * :轉入賬號
- * @param money
- * :轉賬金額
- */
- @Override
- public void transfer(String out, String in, Double money) {
- accountDao.outMoney(out, money);
- // int i = 1/0;
- accountDao.inMoney(in, money);
- }
- public void setAccountDao(AccountDao accountDao) {
- this.accountDao = accountDao;
- }
- }
applicationContext3.xml
- <!-- 引入外部的屬性文件 -->
- <context:property-placeholder location="classpath:jdbc.properties"/>
- <!-- 配置c3p0連接池 -->
- <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
- <property name="driverClass" value="${jdbc.driverClass}" />
- <property name="jdbcUrl" value="${jdbc.url}" />
- <property name="user" value="${jdbc.username}" />
- <property name="password" value="${jdbc.password}" />
- </bean>
- <!-- 配置業務層類 -->
- <bean id="accountService" class="com.zs.spring.demo3.AccountServiceImpl">
- <property name="accountDao" ref="accountDao" />
- </bean>
- <!-- 配置DAO類(簡化,會自動配置JdbcTemplate) -->
- <bean id="accountDao" class="com.zs.spring.demo3.AccountDaoImpl">
- <property name="dataSource" ref="dataSource" />
- </bean>
- <!-- ==================================3.使用XML配置聲明式的事務管理,基於tx/aop=============================================== -->
- <!-- 配置事務管理器 -->
- <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
- <property name="dataSource" ref="dataSource" />
- </bean>
- <!-- 配置事務的通知 -->
- <tx:advice id="txAdvice" transaction-manager="transactionManager">
- <tx:attributes>
- <!--
- propagation :事務傳播行爲
- isolation :事務的隔離級別
- read-only :只讀
- rollback-for:發生哪些異常回滾
- no-rollback-for :發生哪些異常不回滾
- timeout :過期信息
- -->
- <tx:method name="transfer" propagation="REQUIRED"/>
- </tx:attributes>
- </tx:advice>
- <!-- 配置切面 -->
- <aop:config>
- <!-- 配置切入點 -->
- <aop:pointcut expression="execution(* com.zs.spring.demo3.AccountService+.*(..))" id="pointcut1"/>
- <!-- 配置切面 -->
- <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1"/>
- </aop:config>
測試:
- /**
- * @Description:Spring的聲明式事務管理的方式二:基於AspectJ的XML方式的配置
- */
- @RunWith(SpringJUnit4ClassRunner.class)
- @ContextConfiguration("classpath:applicationContext3.xml")
- public class TransactionTest {
- /**
- * 一定要注入代理類:因爲代理類進行增強的操作
- */
- @Resource(name = "accountService")
- private AccountService accountService;
- @Test
- public void demo1() {
- accountService.transfer("aaa", "bbb", 200d);
- }
- }
4、基於註解
- /**
- * @Transactional中的的屬性 propagation :事務的傳播行爲 isolation :事務的隔離級別 readOnly :只讀
- * rollbackFor :發生哪些異常回滾 noRollbackFor :發生哪些異常不回滾
- * rollbackForClassName 根據異常類名回滾
- */
- @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, readOnly = false)
- public class AccountServiceImpl implements AccountService {
- // 注入轉賬的DAO
- private AccountDao accountDao;
- /**
- * @param out
- * :轉出賬號
- * @param in
- * :轉入賬號
- * @param money
- * :轉賬金額
- */
- @Override
- public void transfer(String out, String in, Double money) {
- accountDao.outMoney(out, money);
- // int i = 1/0;
- accountDao.inMoney(in, money);
- }
- public void setAccountDao(AccountDao accountDao) {
- this.accountDao = accountDao;
- }
- }
applicationContext4.xml
- <!-- 引入外部的屬性文件 -->
- <context:property-placeholder location="classpath:jdbc.properties"/>
- <!-- 配置c3p0連接池 -->
- <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
- <property name="driverClass" value="${jdbc.driverClass}" />
- <property name="jdbcUrl" value="${jdbc.url}" />
- <property name="user" value="${jdbc.username}" />
- <property name="password" value="${jdbc.password}" />
- </bean>
- <!-- 配置業務層類 -->
- <bean id="accountService" class="com.zs.spring.demo4.AccountServiceImpl">
- <property name="accountDao" ref="accountDao" />
- </bean>
- <!-- 配置DAO類(簡化,會自動配置JdbcTemplate) -->
- <bean id="accountDao" class="com.zs.spring.demo4.AccountDaoImpl">
- <property name="dataSource" ref="dataSource" />
- </bean>
- <!-- ==================================4.使用註解配置聲明式事務============================================ -->
- <!-- 配置事務管理器 -->
- <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
- <property name="dataSource" ref="dataSource" />
- </bean>
- <!-- 開啓註解事務 -->
- <tx:annotation-driven transaction-manager="transactionManager"/>
測試:
- @RunWith(SpringJUnit4ClassRunner.class)
- @ContextConfiguration("classpath:applicationContext4.xml")
- public class TransactionTest {
- /**
- * 一定要注入代理類:因爲代理類進行增強的操作
- */
- @Resource(name = "accountService")
- private AccountService accountService;
- @Test
- public void demo1() {
- accountService.transfer("aaa", "bbb", 200d);
- }
- }