1、 事務相關知識:
什么是事務:把多條數據庫操作捆綁到一起執行,要么都成功,要么都失敗;
事務的原則ACID:
原子性:事務包含的所有操作,要么全部成功,要么全部失敗回滾,成功全部應用到數據庫,失敗不能對數據庫有任何影響;
一致性:事務在執行前和執行后必須一致;例如A和B一共有100塊錢,無論A、B之間如何轉賬,他們的錢始終相加都是100;
隔離性:多用戶並發訪問同一張表時,數據庫為每一個用戶開啟新的事務,該事務不能被其他事務所影響,相互有隔離;
持久性:一個事務一旦提交,則對數據庫中數據的改變是永久的,即便系統故障也不會丟失;
並發可能引起的問題:
臟讀:一個事務讀取到另一個事務未提交的數據;
不可重復讀:一個事務讀取到另一個事務已提交(Update操作)的數據,導致前后讀取不一致;
幻讀(虛讀):一個事務中讀取到別的事務插入(Insert操作)的數據,導致前后讀取不一致;
事務的隔離級別:根據實際情況選擇;
Serializable串行化:可避免臟讀、不可重復讀和幻讀;
Repeatable read可重復讀:可避免臟讀、不可重復讀;(MySql默認值)
Read committed讀已提交:可避免臟讀;
Read uncommitted讀未提交:任何情況都無法保證;
2、 Spring-aop事務-搭建環境;
事務基本操作:打開事務、提交事務、回滾事務;
Spring中利用接口來管理不同框架的事務操作;
通過實現PlatformTransactionManager接口支持不同的框架完成各自的事務處理;
為不同平台提供對應的事務管理器的實現:
JDBC&Mybatis:DataSourceTransactionManager;
Spring-aop事務通過配置事務的隔離級別、事務傳播行為、是否只讀來操作;
隔離級別:串行化、可重復讀、讀已提交、讀未提交;
是否只讀:true:不可改變數據庫中的數據,查詢操作推薦; false:可以改變數據庫數據;
事務傳播行為:事務方法嵌套調用的規則: xService.x(); -> yService.y();
REQUIRED:如果當前沒有事務,就創建一個新事務,如果當前存在事務,就加入該事務,該設置是最常用的設置;
REQUIRES_NEW:創建新事務,無論當前存不存在事務,都創建新事務;
SUPPORTS:支持當前事務,如果當前存在事務,就加入該事務,如果當前不存在事務,就以非事務執行;
NOT_SUPPORTED:以非事務方式執行操作,如果當前存在事務,就把當前事務掛起(暫停);
MANDATORY:支持當前事務,如果當前存在事務,就加入該事務,如果當前不存在事務,就拋出異常;
NEVER:以非事務方式執行,如果當前存在事務,則拋出異常;
NESTED:如果當前存在事務,則在嵌套事務內執行。如果當前沒有事務,則執行與REQUIRED類似的操作。
3、 Spring-aop事務 – 從麻煩的事務代碼中走出之xml配置版aop事務;
使用經典的轉賬案例進行測試,准備數據:bean、service、dao;
使用事務需要額外導入tx包和tx約束;
配置事務核心管理器: DataSourceTransactionManager;
配置事務通知 tx:Advice;
配置aop;
根據以上知識點,我們來實現spring的事務管理。
數據庫創建一張表:
代碼:
/** * 賬戶 * @author hubt11585 */ public class Account { private Integer id; private String name; private Double money; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Double getMoney() { return money; } public void setMoney(Double money) { this.money = money; } }
public interface AccountDao { //扣款 void subMoney(Integer id, Double money); //加款 void addMoney(Integer id, Double money); }
import org.springframework.jdbc.core.support.JdbcDaoSupport; public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao { @Override public void subMoney(Integer id, Double money) { String sql = "update account set money = money - ? where id = ?"; getJdbcTemplate().update(sql, money, id); } @Override public void addMoney(Integer id, Double money) { String sql = "update account set money = money + ? where id = ?"; getJdbcTemplate().update(sql, money, id); } }
/** * 賬戶service * @author Joey * */ public interface AccountService { //轉賬接口 void transferAccounts(); }
import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import com.tao.dao.AccountDao; @Transactional(isolation=Isolation.DEFAULT, propagation=Propagation.REQUIRED, readOnly=true) public class AccountServiceImpl implements AccountService { //賬戶dao private AccountDao ad; public void setAd(AccountDao ad) { this.ad = ad; } @Override @Transactional(isolation=Isolation.DEFAULT, propagation=Propagation.REQUIRED, readOnly=false) public void transferAccounts() { //轉賬邏輯 //先從A賬戶扣款 ad.subMoney(1, 50d); int a = 1/0; // 除以0會出現異常 //再給B賬戶加款 ad.addMoney(2, 50d); } }
import javax.annotation.Resource; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.tao.service.AccountService; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml") public class TxTest { @Resource(name="accountService") private AccountService as; @Test public void Test1() { as.transferAccounts(); } }
配置文件:applicationContext.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:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd"> <!-- 依賴關系 dao -> -> dataSource --> <!-- 讀取配置文件 --> <context:property-placeholder location="db.properties"/> <!-- 配置 dataSource --> <bean name="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driverClass}"/> <property name="jdbcUrl" value="${jdbc.jdbcUrl}"/> <property name="user" value="${jdbc.user}"/> <property name="password" value="${jdbc.password}"/> </bean> <!-- dao --> <bean name="accountDao" class="com.tao.dao.AccountDaoImpl"> <property name="dataSource" ref="dataSource"/> </bean> <!-- service --> <bean name="accountService" class="com.tao.service.AccountServiceImpl"> <property name="ad" ref="accountDao"/> </bean> <!-- 配置事務核心管理器 不同平台不一樣 --> <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 事務通知 --> <tx:advice id="txAdivce" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="transferAccounts" isolation="DEFAULT" propagation="REQUIRED" read-only="false"/> <tx:method name="save*" isolation="DEFAULT" propagation="REQUIRED" read-only="false"/> <tx:method name="delete*" isolation="DEFAULT" propagation="REQUIRED" read-only="false"/> <tx:method name="update*" isolation="DEFAULT" propagation="REQUIRED" read-only="false"/> <tx:method name="select*" isolation="DEFAULT" propagation="REQUIRED" read-only="true"/> </tx:attributes> </tx:advice> <!-- 配置aop --> <aop:config> <aop:pointcut expression="execution(* com.tao.service.*ServiceImpl.*(..))" id="txPc"/> <aop:advisor advice-ref="txAdivce" pointcut-ref="txPc"/> </aop:config> </beans>
運行結果:
會出現異常
表里面數據被回滾,並沒有出現執行了異常前面部分,效果達到。
以下是注解版代碼:
只需要調整一下部分代碼。
配置文件中:開啟事務:
<!-- 開啟注解事務 --> <tx:annotation-driven/>
完整配置文件如下:
<?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" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd"> <!-- 依賴關系 dao -> -> dataSource --> <!-- 讀取配置文件 --> <context:property-placeholder location="db.properties"/> <!-- 配置 dataSource --> <bean name="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driverClass}"/> <property name="jdbcUrl" value="${jdbc.jdbcUrl}"/> <property name="user" value="${jdbc.user}"/> <property name="password" value="${jdbc.password}"/> </bean> <!-- dao --> <bean name="accountDao" class="com.tao.dao.AccountDaoImpl"> <property name="dataSource" ref="dataSource"/> </bean> <!-- service --> <bean name="accountService" class="com.tao.service.AccountServiceImpl"> <property name="ad" ref="accountDao"/> </bean> <!-- 配置事務核心管理器 不同平台不一樣 --> <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 開啟注解事務 --> <tx:annotation-driven/> </beans>
代碼:AccountServiceImpl類上(整個類下所有方法有效)或者方法上加上注解即可實現統一事務。
import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import com.tao.dao.AccountDao; @Transactional(isolation=Isolation.DEFAULT, propagation=Propagation.REQUIRED, readOnly=true) public class AccountServiceImpl implements AccountService { //賬戶dao private AccountDao ad; public void setAd(AccountDao ad) { this.ad = ad; } @Override @Transactional(isolation=Isolation.DEFAULT, propagation=Propagation.REQUIRED, readOnly=false) public void transferAccounts() { //轉賬邏輯 //先從A賬戶扣款 ad.subMoney(1, 50d); int a = 1/0; // 除以0會出現異常 //再給B賬戶加款 ad.addMoney(2, 50d); } }
運行結果:
會出現異常
表里面數據被回滾,並沒有出現執行了異常前面部分,效果達到。
總結:通過spring來控制事務,方便整潔。