编程式事务与声明式事务
編程式事務(wù)
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驅(qū)動包即C3P0的jar包:
c3p0-0.9.1.2.jar
mysql-connector-java-5.1.37-bin.jar
2.創(chuàng)建一份jdbc.properties文件
jdbc.user=root
jdbc.passowrd=123456
jdbc.url=jdbc:mysql://localhost:3306/tx
jdbc.driver=com.mysql.jdbc.Driver
3.在spring配置文件中配置數(shù)據(jù)源
<!-- 引入外部屬性文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置數(shù)據(jù)源 -->
<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.測試數(shù)據(jù)源:
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.創(chuàng)建Dao類
<!-- 設(shè)置掃描的包 -->
<context:component-scan base-package="com.xy"></context:component-scan>
@Repository
public class BookDao {
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* [1]根據(jù)isbn的值查詢書的價格
[2]根據(jù)isbn的值減少書的庫存,假設(shè)每次都只買1本書
[3]根據(jù)用戶名減少用戶賬戶中的余額,減少的額度就是書的價格
*/
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類中繼續(xù)編寫如下方法:
//[2]根據(jù)isbn的值減少書的庫存,假設(shè)每次都只買1本書
public void updateStockByIsbn(String isbn){
String sql = "UPDATE book_stock SET stock = stock -1 WHERE isbn = ?";
jdbcTemplate.update(sql, isbn);
}
繼續(xù)測試:
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");
}
}
繼續(xù)編寫Dao類中的方法:
//[3]根據(jù)用戶名減少用戶賬戶中的余額,減少的額度就是書的價格
public void updateBalance(String userName,int price){
String sql = "UPDATE account SET balance = balance - ? WHERE username = ?";
jdbcTemplate.update(sql, price,userName);
}
繼續(xù)測試:
@Test
public void test03() throws SQLException {
bean.updateBalance("Tom", 1000);
}
7.創(chuàng)建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方法中調(diào)用的三個方法應(yīng)該在同一個事務(wù)中,要么同時成功,要么同時失敗!
先統(tǒng)一設(shè)置一下:
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");
}
}
由于此時該方法沒在事務(wù)中,如果doCash方法調(diào)用dao層的方法的時候,在中間位置出現(xiàn)了錯誤,此時就會造成一部分?jǐn)?shù)據(jù)改掉了,而
另一部分?jǐn)?shù)據(jù)沒有改掉,這就麻煩了,所以此時應(yīng)該加入事務(wù)機(jī)制!
9.如果想開啟事務(wù),就需要在spring的配置文件中配置事務(wù)管理器
<!-- 配置事務(wù)管理器,并為事務(wù)管理器配置數(shù)據(jù)源!-->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="comboPooledDataSource"></property>
</bean>
<!-- 開啟基于注解的聲明式事務(wù)功能,需要設(shè)置transaction-manager屬性-->
<!-- 如果 事務(wù)管理器的id正好是transaction-manager的默認(rèn)值transactionManager,則可以省略-->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
然后在service層的doCash方法上加上:@Transactional注解這樣就開啟了事務(wù)!需要注意的是事務(wù)一般是加在service層的!
?
聲明式事務(wù)
?
?①基本原理:AOP
[1]前置通知:開啟事務(wù)
[2]返回通知:提交事務(wù)
[3]異常通知:回滾事務(wù)
[4]后置通知:釋放資源
②事務(wù)管理器
③導(dǎo)入jar包
[1]IOC容器需要的jar包
[2]AOP需要的jar包
[3]JdbcTemplate操作需要的jar包
[5]MySQL驅(qū)動和C3P0
④配置
[1]配置數(shù)據(jù)源
[2]配置JdbcTemplate,并裝配數(shù)據(jù)源
[3]配置事務(wù)管理器,并裝配數(shù)據(jù)源
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" >
<property name= "dataSource" ref ="dataSource"/>
</bean >
[4]開啟基于注解的聲明式事務(wù)功能
<tx:annotation-driven transaction-manager ="dataSourceTransactionManager"/>
如果事務(wù)管理器的bean的id正好是transaction-manager的默認(rèn)值transactionManager,則可以省略
[5]在事務(wù)方法上加@Transactional注解
4.事務(wù)屬性的設(shè)置
①事務(wù)的傳播行為
②事務(wù)的隔離級別
③事務(wù)根據(jù)什么異常不進(jìn)行回滾
④事務(wù)的超時屬性
⑤事務(wù)的只讀屬性
①事務(wù)的傳播行為[參見第8章world文檔]
[1]解釋:一個事務(wù)運(yùn)行在另一個有事務(wù)的方法中,那么當(dāng)前方法是開啟新事務(wù)還是在原有的事務(wù)中運(yùn)行。
[2]設(shè)置事務(wù)方法在調(diào)用其他事務(wù)方法時,自己的事務(wù)如何傳播給被調(diào)用的方法。[默認(rèn)是required]
[3]設(shè)置方式
案例演示:
在上面的dao中增加一個方法,用于演示事務(wù)!
//為了測試事務(wù)的傳播行為,需要增加一個數(shù)據(jù)庫操作
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);
}
此時當(dāng)前service類中就有兩個事務(wù)方法了,然后我們現(xiàn)在新創(chuàng)建一個service類,加入到IOC容器中,并將bookService注入,
然后新建一個事務(wù)方法,如下所示:
@Component
public class MultiTX {
@Autowired
private BookService bookService;
@Transactional
public void multiTx(){
bookService.doCash("ISBN-003","Tom");
bookService.updatePrice("ISBN-005",888);
}
}
為了演示該service方法中調(diào)用的另外兩個方法是不是使用了當(dāng)前service方法的事務(wù),這里我們將該service方法調(diào)用的第二個方法
弄出一個異常,然后看看該service方法調(diào)用的第一個service方法是不是回滾了!
先統(tǒng)一設(shè)置一下表中的數(shù)據(jù):
UPDATE `account` SET balance = 10000;
UPDATE book_stock SET stock = 1000;
UPDATE book SET price = 1000;
用測試類進(jìn)行測試:先正常測試一遍,然后再整出個異常測試一下:
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();
}
}
會發(fā)現(xiàn)@Transactional注解默認(rèn)使用的傳播屬性就是required!
此時可以將BookService類中的兩個方法都加上propagation=Propagation.REQUIRES_NEW 屬性,也就是如下所示:
@Transactional(propagation=Propagation.REQUIRES_NEW)
然后在執(zhí)行剛才的有異常的測試方法!
②事務(wù)的隔離級別 :用于解決并發(fā)問題[在兩次獲取價格的方法中間打斷點(diǎn)]
isolation=Isolation. READ_COMMITTED
先恢復(fù)數(shù)據(jù)表中的數(shù)據(jù):
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);
}
在測試方法中調(diào)用BookService類中的doCash方法,然后在doCash方法中兩次獲取價格的方法中間打斷點(diǎn),就會發(fā)現(xiàn)
第一次是1000元,然后我們修改了數(shù)據(jù)庫之后,讀出來的還是1000元,這是因?yàn)閿?shù)據(jù)庫默認(rèn)的隔離級別就是可重復(fù)讀!
如果我們在doCash方法的事務(wù)注解上加一個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);
}
就會發(fā)現(xiàn)如果在兩次獲取價格之間打了斷點(diǎn),然后修改了值的話就會讀出不一樣的效果,說明事務(wù)的隔離級別設(shè)置就生效了!
③事務(wù)根據(jù)什么異常不進(jìn)行回滾
默認(rèn)情況下,出現(xiàn)異常就回滾,noRollbackFor屬性可以設(shè)置出現(xiàn)什么異常不進(jìn)行回滾:
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);//出了異常信息
}
④事務(wù)的超時屬性【timeout=3】
[1]數(shù)據(jù)庫事務(wù)在執(zhí)行過程中,會占用數(shù)據(jù)庫資源,所以如果某一個事務(wù)執(zhí)行的時間太長,
那么就會導(dǎo)致資源被長時間占用,影響其他事務(wù)執(zhí)行。[死循環(huán),網(wǎng)絡(luò)問題]
[2]可以通過設(shè)置超時屬性,將超時沒有執(zhí)行完的事務(wù)回滾,相當(dāng)于對該操作進(jìn)行撤銷。
@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);
}
⑤事務(wù)的只讀屬性【readOnly=true】
數(shù)據(jù)庫會對事務(wù)進(jìn)行優(yōu)化,如果是一個查詢操作,那么數(shù)據(jù)庫可以有針對性的進(jìn)行優(yōu)化。我們可以通過設(shè)置事務(wù)屬性,
告訴數(shù)據(jù)庫當(dāng)前操作是一個只讀操作,便于數(shù)據(jù)庫進(jìn)行優(yōu)化。
5.基于XML的聲明式事務(wù)
<!-- 配置基于XML文件的聲明式事務(wù) -->
<aop:config>
<!-- 配置切入點(diǎn)表達(dá)式 -->
<aop:pointcut expression="execution(* com.xy.tx.service.BookService.*(String, String))" id="txPointCut"/>
<!-- 將事務(wù)切入點(diǎn)和事務(wù)建議的配置聯(lián)系起來 -->
<aop:advisor advice-ref="bookTransaction" pointcut-ref="txPointCut"/>
</aop:config>
<!-- 設(shè)置事務(wù)屬性 -->
<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" />
<!-- 將某一類方法統(tǒng)一設(shè)置為只讀 -->
<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>
轉(zhuǎn)載于:https://www.cnblogs.com/java-ssl-xy/p/7460087.html
《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的编程式事务与声明式事务的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [BZOJ] 1609: [Usaco2
- 下一篇: 前端jQuery插件库