編程式事務與聲明式事務
編程式事務
1.加入jar包
com.springsource.net.sf.cglib-2.2.0.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
commons-logging-1.1.3.jar
spring-aop-4.0.0.RELEASE.jar
spring-aspects-4.0.0.RELEASE.jar
spring-beans-4.0.0.RELEASE.jar
spring-context-4.0.0.RELEASE.jar
spring-core-4.0.0.RELEASE.jar
spring-expression-4.0.0.RELEASE.jar
spring-jdbc-4.0.0.RELEASE.jar
spring-orm-4.0.0.RELEASE.jar
spring-tx-4.0.0.RELEASE.jar
然後是mysql驅動包即C3P0的jar包:
c3p0-0.9.1.2.jar
mysql-connector-java-5.1.37-bin.jar
2.創建一份jdbc.properties文件
jdbc.user=root
jdbc.passowrd=123456
jdbc.url=jdbc:mysql://localhost:3306/tx
jdbc.driver=com.mysql.jdbc.Driver
3.在spring配置文件中配置數據源
<!-- 引入外部屬性文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置數據源 -->
<bean id="comboPooledDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.passowrd}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="driverClass" value="${jdbc.driver}"></property>
</bean>
4.測試數據源:
public class TestDataSource {
private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
@Test
public void test() throws SQLException {
DataSource bean = ioc.getBean(DataSource.class);
System.out.println(bean.getConnection());
}
}
5.配置jdbcTemplate:
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="comboPooledDataSource"></property>
</bean>
6.創建Dao類
<!-- 設置掃描的包 -->
<context:component-scan base-package="com.xy"></context:component-scan>
@Repository
public class BookDao {
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* [1]根據isbn的值查詢書的價格
[2]根據isbn的值減少書的庫存,假設每次都只買1本書
[3]根據用戶名減少用戶賬戶中的余額,減少的額度就是書的價格
*/
public int findPriceByIsbn(String isbn){
String sql = "SELECT price FROM book WHERE isbn = ?";
Integer price = jdbcTemplate.queryForObject(sql, Integer.class, isbn);
return price;
}
}
測試上面的該方法:
public class TestDataSource {
private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
private BookDao bean = ioc.getBean(BookDao.class);
@Test
public void test01() throws SQLException {
int findPriceByIsbn = bean.findPriceByIsbn("ISBN-005");
System.out.println(findPriceByIsbn);
}
}
然後在dao類中繼續編寫如下方法:
//[2]根據isbn的值減少書的庫存,假設每次都只買1本書
public void updateStockByIsbn(String isbn){
String sql = "UPDATE book_stock SET stock = stock -1 WHERE isbn = ?";
jdbcTemplate.update(sql, isbn);
}
繼續測試:
public class TestDataSource {
private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
private BookDao bean = ioc.getBean(BookDao.class);
@Test
public void test02() throws SQLException {
bean.updateStockByIsbn("ISBN-004");
}
}
繼續編寫Dao類中的方法:
//[3]根據用戶名減少用戶賬戶中的余額,減少的額度就是書的價格
public void updateBalance(String userName,int price){
String sql = "UPDATE account SET balance = balance - ? WHERE username = ?";
jdbcTemplate.update(sql, price,userName);
}
繼續測試:
@Test
public void test03() throws SQLException {
bean.updateBalance("Tom", 1000);
}
7.創建Service層:
@Service
public class BookService {
@Autowired
private BookDao bookDao;
public void doCash(String isbn,String username){
int price = bookDao.findPriceByIsbn(isbn);
bookDao.updateStockByIsbn(isbn);
bookDao.updateBalance(username, price);
}
}
8.註意哦:上面doCash方法中調用的三個方法應該在同一個事務中,要麽同時成功,要麽同時失敗!
先統一設置一下:
UPDATE account SET balance = 10000;
UPDATE book_stock SET stock = 1000;
然後先正常測試一下service中的doCash方法:
public class TestDataSource {
private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
private BookDao bean = ioc.getBean(BookDao.class);
private BookService bookService = ioc.getBean(BookService.class);
@Test
public void test04() throws SQLException {
bookService.doCash("ISBN-001","Tom");
}
}
由於此時該方法沒在事務中,如果doCash方法調用dao層的方法的時候,在中間位置出現了錯誤,此時就會造成一部分數據改掉了,而
另一部分數據沒有改掉,這就麻煩了,所以此時應該加入事務機制!
9.如果想開啟事務,就需要在spring的配置文件中配置事務管理器
<!-- 配置事務管理器,並為事務管理器配置數據源!-->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="comboPooledDataSource"></property>
</bean>
<!-- 開啟基於註解的聲明式事務功能,需要設置transaction-manager屬性-->
<!-- 如果 事務管理器的id正好是transaction-manager的默認值transactionManager,則可以省略-->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
然後在service層的doCash方法上加上:@Transactional註解這樣就開啟了事務!需要註意的是事務一般是加在service層的!
聲明式事務
①基本原理:AOP
[1]前置通知:開啟事務
[2]返回通知:提交事務
[3]異常通知:回滾事務
[4]後置通知:釋放資源
②事務管理器
③導入jar包
[1]IOC容器需要的jar包
[2]AOP需要的jar包
[3]JdbcTemplate操作需要的jar包
[5]MySQL驅動和C3P0
④配置
[1]配置數據源
[2]配置JdbcTemplate,並裝配數據源
[3]配置事務管理器,並裝配數據源
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" >
<property name= "dataSource" ref ="dataSource"/>
</bean >
[4]開啟基於註解的聲明式事務功能
<tx:annotation-driven transaction-manager ="dataSourceTransactionManager"/>
如果事務管理器的bean的id正好是transaction-manager的默認值transactionManager,則可以省略
[5]在事務方法上加@Transactional註解
4.事務屬性的設置
①事務的傳播行為
②事務的隔離級別
③事務根據什麽異常不進行回滾
④事務的超時屬性
⑤事務的只讀屬性
①事務的傳播行為[參見第8章world文檔]
[1]解釋:一個事務運行在另一個有事務的方法中,那麽當前方法是開啟新事務還是在原有的事務中運行。
[2]設置事務方法在調用其他事務方法時,自己的事務如何傳播給被調用的方法。[默認是required]
[3]設置方式
案例演示:
在上面的dao中增加一個方法,用於演示事務!
//為了測試事務的傳播行為,需要增加一個數據庫操作
public void updatePrice(String isbn, int price){
String sql = "UPDATE book SET price = ? WHERE isbn = ?";
jdbcTemplate.update(sql, price,isbn);
}
同樣,在service類中添加一個方法,如下所示:
@Transactional
public void updatePrice(String isbn, int price){
bookDao.updatePrice(isbn, price);
}
此時當前service類中就有兩個事務方法了,然後我們現在新創建一個service類,加入到IOC容器中,並將bookService註入,
然後新建一個事務方法,如下所示:
@Component
public class MultiTX {
@Autowired
private BookService bookService;
@Transactional
public void multiTx(){
bookService.doCash("ISBN-003","Tom");
bookService.updatePrice("ISBN-005",888);
}
}
為了演示該service方法中調用的另外兩個方法是不是使用了當前service方法的事務,這裏我們將該service方法調用的第二個方法
弄出一個異常,然後看看該service方法調用的第一個service方法是不是回滾了!
先統一設置一下表中的數據:
UPDATE `account` SET balance = 10000;
UPDATE book_stock SET stock = 1000;
UPDATE book SET price = 1000;
用測試類進行測試:先正常測試一遍,然後再整出個異常測試一下:
public class TestDataSource {
private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
private BookDao bean = ioc.getBean(BookDao.class);
private BookService bookService = ioc.getBean(BookService.class);
private MultiTX multiTx = ioc.getBean(MultiTX.class);
@Test
public void test04() throws SQLException {
multiTx.multiTx();
}
}
會發現@Transactional註解默認使用的傳播屬性就是required!
此時可以將BookService類中的兩個方法都加上propagation=Propagation.REQUIRES_NEW 屬性,也就是如下所示:
@Transactional(propagation=Propagation.REQUIRES_NEW)
然後在執行剛才的有異常的測試方法!
②事務的隔離級別 :用於解決並發問題[在兩次獲取價格的方法中間打斷點]
isolation=Isolation. READ_COMMITTED
先恢復數據表中的數據:
UPDATE `account` SET balance = 10000;
UPDATE book_stock SET stock = 1000;
UPDATE book SET price = 1000;
修改service類中的doCash方法為:【即兩次獲取價格!】
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void doCash(String isbn,String username){
int price = bookDao.findPriceByIsbn(isbn);
System.out.println("price1:"+price);
bookDao.updateStockByIsbn(isbn);
bookDao.updateBalance(username, price);
price = bookDao.findPriceByIsbn(isbn);
System.out.println("price2:"+price);
}
在測試方法中調用BookService類中的doCash方法,然後在doCash方法中兩次獲取價格的方法中間打斷點,就會發現
第一次是1000元,然後我們修改了數據庫之後,讀出來的還是1000元,這是因為數據庫默認的隔離級別就是可重復讀!
如果我們在doCash方法的事務註解上加一個isolation屬性,如下所示:
@Transactional(propagation=Propagation.REQUIRES_NEW,isolation=Isolation.READ_COMMITTED)
public void doCash(String isbn,String username){
int price = bookDao.findPriceByIsbn(isbn);
System.out.println("price1:"+price);
bookDao.updateStockByIsbn(isbn);
bookDao.updateBalance(username, price);
price = bookDao.findPriceByIsbn(isbn);
System.out.println("price2:"+price);
}
就會發現如果在兩次獲取價格之間打了斷點,然後修改了值的話就會讀出不一樣的效果,說明事務的隔離級別設置就生效了!
③事務根據什麽異常不進行回滾
默認情況下,出現異常就回滾,noRollbackFor屬性可以設置出現什麽異常不進行回滾:
noRollbackFor=ArithmeticException.class
@Transactional(propagation=Propagation.REQUIRES_NEW,
isolation=Isolation.READ_COMMITTED,
noRollbackFor=ArithmeticException.class)
public void doCash(String isbn,String username){
int price = bookDao.findPriceByIsbn(isbn);
System.out.println("price1:"+price);
bookDao.updateStockByIsbn(isbn);
bookDao.updateBalance(username, price);
System.out.println(10/0);//出了異常信息
}
④事務的超時屬性【timeout=3】
[1]數據庫事務在執行過程中,會占用數據庫資源,所以如果某一個事務執行的時間太長,
那麽就會導致資源被長時間占用,影響其他事務執行。[死循環,網絡問題]
[2]可以通過設置超時屬性,將超時沒有執行完的事務回滾,相當於對該操作進行撤銷。
@Transactional(propagation=Propagation.REQUIRES_NEW,
isolation=Isolation.READ_COMMITTED,
noRollbackFor=ArithmeticException.class,
timeout=3)
public void doCash(String isbn,String username){
int price = bookDao.findPriceByIsbn(isbn);
System.out.println("price1:"+price);
bookDao.updateStockByIsbn(isbn);
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
bookDao.updateBalance(username, price);
}
⑤事務的只讀屬性【readOnly=true】
數據庫會對事務進行優化,如果是一個查詢操作,那麽數據庫可以有針對性的進行優化。我們可以通過設置事務屬性,
告訴數據庫當前操作是一個只讀操作,便於數據庫進行優化。
5.基於XML的聲明式事務
<!-- 配置基於XML文件的聲明式事務 -->
<aop:config>
<!-- 配置切入點表達式 -->
<aop:pointcut expression="execution(* com.xy.tx.service.BookService.*(String, String))" id="txPointCut"/>
<!-- 將事務切入點和事務建議的配置聯系起來 -->
<aop:advisor advice-ref="bookTransaction" pointcut-ref="txPointCut"/>
</aop:config>
<!-- 設置事務屬性 -->
<tx:advice id="bookTransaction" transaction-manager="dataSourceTransactionManager">
<tx:attributes>
<tx:method name="doCash"
propagation="REQUIRED"
isolation="READ_COMMITTED"
read-only="false"
no-rollback-for="java.lang.ArithmeticException"
timeout="3" />
<!-- 將某一類方法統一設置為只讀 -->
<tx:method name="get*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
<tx:method name="query*" read-only="true"/>
</tx:attributes>
</tx:advice>
編程式事務與聲明式事務