Spring配置文件中關於事務配置總是由三個組成部分,分別是DataSource、TransactionManager和代理機制這三部分,無論哪種配置方式,一般變化的只是代理機制這部分。 DataSource、 TransactionManager這兩部分只是會根據數據訪問方式有所變化,比如使用Hi ...
Spring配置文件中關於事務配置總是由三個組成部分,分別是DataSource、TransactionManager和代理機制這三部分,無論哪種配置方式,一般變化的只是代理機制這部分。 DataSource、 TransactionManager這兩部分只是會根據數據訪問方式有所變化,比如使用Hibernate進行數據訪問時,DataSource實際為 SessionFactory,TransactionManager的實現為HibernateTransactionManager。
一.事務的4個特性:
原子性:一個事務中所有對資料庫的操作是一個不可分割的操作序列,要麼全做,要麼全部做。 一致性:數據不會因為事務的執行而遭到破壞。
隔離性:一個事務的執行,不受其他事務(進程)的干擾。既併發執行的個事務之間互不幹擾。
持久性:一個事務一旦提交,它對資料庫的改變將是永久的。
二:Spring事務的隔離級別
1. ISOLATION_DEFAULT: 這是一個PlatfromTransactionManager預設的隔離級別,使用資料庫預設的事務隔離級別.
另外四個與JDBC的隔離級別相對應
2. ISOLATION_READ_UNCOMMITTED: 這是事務最低的隔離級別,它充許令外一個事務可以看到這個事務未提交的數據。
這種隔離級別會產生臟讀,不可重覆讀和幻像讀。
3. ISOLATION_READ_COMMITTED: 保證一個事務修改的數據提交後才能被另外一個事務讀取。另外一個事務不能讀取該事務未提交的數據
4. ISOLATION_REPEATABLE_READ: 這種事務隔離級別可以防止臟讀,不可重覆讀。但是可能出現幻像讀。
它除了保證一個事務不能讀取另一個事務未提交的數據外,還保證了避免下麵的情況產生(不可重覆讀)。
5. ISOLATION_SERIALIZABLE 這是花費最高代價但是最可靠的事務隔離級別。事務被處理為順序執行。
除了防止臟讀,不可重覆讀外,還避免了幻像讀。
什麼是臟數據,臟讀,不可重覆讀,幻覺讀?
臟讀: 指當一個事務正在訪問數據,並且對數據進行了修改,而這種修改還沒有提交到資料庫中,這時,
另外一個事務也訪問這個數據,然後使用了這個數據。因為這個數據是還沒有提交的數據, 那麼另外一
個事務讀到的這個數據是臟數據,依據臟數據所做的操作可能是不正確的。
不可重覆讀: 指在一個事務內,多次讀同一數據。在這個事務還沒有結束時,另外一個事務也訪問該同一數據。
那麼,在第一個事務中的兩次讀數據之間,由於第二個事務的修改,那麼第一個事務兩次讀到的數據
可能是不一樣的。這樣就發生了在一個事務內兩次讀到的數據是不一樣的,因此稱為是不可重覆讀。
幻覺讀: 指當事務不是獨立執行時發生的一種現象,例如第一個事務對一個表中的數據進行了修改,這種修改涉及
到表中的全部數據行。同時,第二個事務也修改這個表中的數據,這種修改是向表中插入一行新數據。那麼,
以後就會發生操作第一個事務的用戶發現表中還有沒有修改的數據行,就好象發生了幻覺一樣。
三:Spring事務類型詳解:
PROPAGATION_REQUIRED--支持當前事務,如果當前沒有事務,就新建一個事務。這是最常見的選擇。
PROPAGATION_SUPPORTS--支持當前事務,如果當前沒有事務,就以非事務方式執行。
PROPAGATION_MANDATORY--支持當前事務,如果當前沒有事務,就拋出異常。
PROPAGATION_REQUIRES_NEW--新建事務,如果當前存在事務,把當前事務掛起。
PROPAGATION_NOT_SUPPORTED--以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。
PROPAGATION_NEVER--以非事務方式執行,如果當前存在事務,則拋出異常。
PROPAGATION_NESTED--如果當前存在事務,則在嵌套事務內執行。如果當前沒有事務,則進行與PROPAGATION_REQUIRED類似的操作。
四.事務的實現方式:
實現方式共有兩種:編碼方式;聲明式事務管理方式。
基於AOP技術實現的聲明式事務管理,實質就是:在方法執行前後進行攔截,然後在目標方法開始之前創建並加入事務,執行完目標方法後根據執行情況提交或回滾事務。
聲明式事務管理又有兩種方式:基於XML配置文件的方式;另一個是在業務方法上進行@Transactional註解,將事務規則應用到業務邏輯中。
五.實現事務的案例
下麵模擬一個用戶(Account)用錢來買股票(Stock),當用戶出錢買股要是錯誤,就需要我們的事務了。
源碼介紹(Spring框架基本jar包全的情況下):
1.首先我們要用到事務的jar包,我用的是:
spring-tx-4.2.0.RELEASE.jar
2.Account.java
package cn.tx.entity; /** * 賬戶 * @author zhangzong * */ public class Account { private Integer aid;//賬戶編號 private String aname;//用戶姓名 private int balance;//賬戶金額 public Integer getAid() { return aid; } public void setAid(Integer aid) { this.aid = aid; } public String getAname() { return aname; } public void setAname(String aname) { this.aname = aname; } public int getBalance() { return balance; } public void setBalance(int balance) { this.balance = balance; } }View Code
3.Stock.java
package cn.tx.entity; /** * 股票類 * @author zhangzong * */ public class Stock { private Integer sid;//股票編號 private String sname;//股票名稱 private int count;//股數 public Integer getSid() { return sid; } public void setSid(Integer sid) { this.sid = sid; } public String getSname() { return sname; } public void setSname(String sname) { this.sname = sname; } public int getCount() { return count; } public void setCount(int count) { this.count = count; } }View Code
4.AccountDao.java
package cn.tx.dao; //賬戶介面 import cn.tx.entity.Account; public interface AccountDao { /** * 新增賬戶 * @param account * @return */ public int createAccount(Account account); /** * 對賬戶的操作(錢買股,股收錢) * @param id 賬戶的編號 * @param money 發費的錢財 * @param isYesOrNo 是買股,還是收錢 * @return 受影響的行數 */ public int updateBalance(int id,int money,boolean isYesOrNo); /** * 根據編號查詢餘額 * @param id 編號 * @return 餘額 */ public int selectOfBalance(int id); }View Code
5.StockDao.java
package cn.tx.dao; //股票介面 import cn.tx.entity.Stock; public interface StockDao { /** * 創建股票 * @param stock 股票對象 * @return 受影響行數 */ public int createStock(Stock stock); /** * 對股票的操作(錢買股,股收錢) * @param id 股票編號 * @param num 變化的數量 * @param isYesOrNo 是買股,還是收錢 * @return 受影響的行數 */ public int updateCount(int id,int num,boolean isYesOrNo); }View Code
6.AccountDaoImpl.java
package cn.tx.dao.impl; //實現了AccountDao介面的實現類 import org.springframework.jdbc.core.support.JdbcDaoSupport; import cn.tx.dao.AccountDao; import cn.tx.entity.Account; //JdbcDaoSupport是JDBC數據訪問對象的超類 public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao { @Override public int createAccount(Account account) { String sql = "insert into account(aname,balance) values(?,?)"; int count = this.getJdbcTemplate().update(sql, account.getAname(), account.getBalance()); return count; } @Override public int updateBalance(int id, int money, boolean isBuyOrNot) { String sql = null; // isBuyOrNot 為真,金額減少 if (isBuyOrNot) { sql = "update account set balance=balance-? where aid=?"; } else { sql = "update account set balance=balance+? where aid=?"; } int count = this.getJdbcTemplate().update(sql, money, id); return count; } @Override public int selectOfBalance(int id) { String sql = "select balance from account where aid=?"; Double money = this.getJdbcTemplate().queryForObject(sql, new Object[] { id }, Double.class); return money.intValue(); } }View Code
7.StockDaoImpl.java
package cn.tx.dao.impl; //實現了StockDao介面的實現類 import org.springframework.jdbc.core.support.JdbcDaoSupport; import cn.tx.dao.StockDao; import cn.tx.entity.Stock; //JdbcDaoSupport是JDBC數據訪問對象的超類 public class StockDaoImpl extends JdbcDaoSupport implements StockDao { @Override public int createStock(Stock stock) { String sql="insert into Stock(sname,count) values(?,?)"; int count = this.getJdbcTemplate().update(sql,stock.getSname(),stock.getCount()); return count; } @Override public int updateCount(int id, int num, boolean isYesOrNo) { String sql=null; if (isYesOrNo) { sql="update Stock set count+=? where sid=?"; }else { sql="update Stock set count-=? where sid=?"; } int count = this.getJdbcTemplate().update(sql,num,id); return count; } }View Code
8.AccountService.java
package cn.tx.service; //用戶操作業務介面(用錢買股) import cn.tx.entity.Account; import cn.tx.entity.Stock; import cn.tx.util.StockException; public interface AccountService { /** * 新增賬戶 * @param account * @return */ public int createAccount(Account account); /** * 對賬戶的操作(錢買股,股收錢) * @param id 賬戶的編號 * @param money 發費的錢財 * @param isYesOrNo 是買股,還是收錢 * @return 收影響的行數 */ public int updateBalance(int id,int money,boolean isYesOrNo); /** * 創建股票 * @param stock 股票對象 * @return 受影響行數 */ public int createStock(Stock stock); /** * 對股票的操作(錢買股,股收錢) * @param id 股票編號 * @param num 變化的數量 * @param isYesOrNo 是買股,還是收錢 * @return 受影響的行數 */ public int updateCount(int id,int num,boolean isYesOrNo); /** * 購買股票的方法 * @param aid 賬戶編號 * @param money 發費的金額 * @param sid 股票的編號 * @param num 所買股數 * @throws StockException */ public void buyStock(int aid,int money,int sid,int num) throws StockException; /** * 根據編號查詢餘額 * @param id 編號 * @return 餘額 */ public int selectOfBalance(int id); }View Code
9.AccountServiceImpl.java
package cn.tx.service.impl; //用戶操作實現類 import cn.tx.dao.AccountDao; import cn.tx.dao.StockDao; import cn.tx.entity.Account; import cn.tx.entity.Stock; import cn.tx.service.AccountService; import cn.tx.util.StockException; public class AccountServiceImpl implements AccountService { //植入賬戶介面 private AccountDao adao; //植入股票介面 private StockDao sdao; @Override public int createAccount(Account account) { // TODO Auto-generated method stub return adao.createAccount(account); } @Override public int updateBalance(int id, int money, boolean isYesOrNo) { // TODO Auto-generated method stub return adao.updateBalance(id, money, isYesOrNo); } @Override public int createStock(Stock stock) { // TODO Auto-generated method stub return sdao.createStock(stock); } @Override public int updateCount(int id, int num, boolean isYesOrNo) { // TODO Auto-generated method stub return sdao.updateCount(id, num, isYesOrNo); } @Override public int selectOfBalance(int id) { // TODO Auto-generated method stub return adao.selectOfBalance(id); } //@Transactional(isolation=Isolation.DEFAULT,propagation=Propagation.REQUIRED,rollbackFor=StockException.class) public void buyStock(int aid,int money,int sid,int num) throws StockException{ boolean isBuy=true;//預設為錢買股 //更改賬戶信息 adao.updateBalance(aid, money, isBuy); //模擬異常,如果沒錢或錢為負數 if(adao.selectOfBalance(aid)<=0){ throw new StockException("異常發生了。。。。。"); } //更改股票信息 sdao.updateCount(sid, num, isBuy); } public AccountDao getAdao() { return adao; } public void setAdao(AccountDao adao) { this.adao = adao; } public StockDao getSdao() { return sdao; } public void setSdao(StockDao sdao) { this.sdao = sdao; } }View Code
10.StockException.java(製造的一個異常類)
package cn.tx.util; /** * 構造一個檢查異常 * @author zhangzong * */ public class StockException extends Exception { /** * */ private static final long serialVersionUID = 1L; public StockException() { super(); } public StockException(String message) { super(message); } }View Code
11.applicationContext.xml(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/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!-- 1.dao --> <bean id="accountDao" class="cn.tx.dao.impl.AccountDaoImpl"> <property name="dataSource" ref="dataSource"></property> </bean> <bean id="stockDao" class="cn.tx.dao.impl.StockDaoImpl"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 2.service --> <bean id="accountService" class="cn.tx.service.impl.AccountServiceImpl"> <property name="adao" ref="accountDao"></property> <property name="sdao" ref="stockDao"></property> </bean> <!-- 3.c3p0數據源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driver}"></property> <property name="jdbcUrl" value="${jdbc.url}"></property> <property name="user" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> </bean> <!-- 4.註冊jdbc屬性 --> <context:property-placeholder location="classpath:jdbc.properties" /> <!--******************************事務配置 ********************************* --> <!-- 註冊事務管理器 --> <bean id="mytxManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- *****************獲得事務代理************** --> <!--方法一: 事務自動代理 :此方法要結合註解使用,在AccountServiceImpl的buyStock上 --> <!--<tx:annotation-driven transaction-manager="mytxManager"/> --> <!-- 方法二:TransactionProxyFactoryBean 生成事務代理 --> <!-- <bean id="stockServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="target" ref="accountService"></property> <property name="transactionManager" ref="mytxManager"></property> <property name="transactionAttributes"> <props> 四種隔離級別 傳播屬性 required <prop key="create*">ISOLATION_DEFAULT,PROPAGATION_REQUIRED</prop> <prop key="buyStock">ISOLATION_DEFAULT,PROPAGATION_REQUIRED,-StockException </prop> </props> </property> </bean> --> <!-- 方法三 : aop**************** --> <tx:advice id="txAdvice" transaction-manager="mytxManager"> <tx:attributes> <tx:method name="add*" isolation="DEFAULT" propagation="REQUIRED" /> <tx:method name="buyStock" isolation="DEFAULT" propagation="REQUIRED" rollback-for="StockException" /> </tx:attributes> </tx:advice> <!-- aop配置 --> <aop:config> <aop:pointcut expression="execution(* *..service.*.*(..))" id="stockPointcut" /> <aop:advisor advice-ref="txAdvice" pointcut-ref="stockPointcut"/> </aop:config> </beans>View Code
12.jdbc.properties(連接資料庫的配置)
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc\:mysql\:///mybook
jdbc.username=root
jdbc.password=1234
View Code
13.log4j.properties
### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### direct messages to file mylog.log ###
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=c\:mylog.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### set log levels - for more verbose logging change 'info' to 'debug' ###
log4j.rootLogger=info, stdout
View Code
14.MyTest.java
package cn.tx.test; //測試類 import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import cn.tx.entity.Account; import cn.tx.entity.Stock; import cn.tx.service.AccountService; import cn.tx.util.StockException; public class MyTest { //測試餘額 @Test public void selectOfbalance(){ ApplicationContext ctx = new ClassPathXmlApplicationContext( "applicationContext.xml"); AccountService service = (AccountService) ctx.getBean("accountService"); int balance = service.selectOfBalance(1); System.out.println(balance); } @Test // 購買股票測試 public void buyStockTest() throws StockException { ApplicationContext ctx = new ClassPathXmlApplicationContext( "applicationContext.xml"); AccountService service = (AccountService) ctx.getBean("accountService"); // 花200 塊 買 2股 service.buyStock(1, 200, 1, 2); } @Test // 開戶測試 public void addData() { ApplicationContext ctx = new ClassPathXmlApplicationContext( "applicationContext.xml"); AccountService service = (AccountService) ctx.getBean("accountService"); // 銀行卡賬戶 Account account = new Account(); account.setAname("傻瞿亮"); account.setBalance(1000); service.createAccount(account); Stock stock = new Stock(); stock.setSname("腦殘教育"); stock.setCount(5); service.createStock(stock); } }View Code