spring事物中的傳播及隔離
▼
關注測試局| 會上癮
java
關於@Transactional註解:python
添加事務註解mysql
一、使用 propagation 指定事務的傳播行爲, 即當前的事務方法被另一個事務方法調用時。如何使用事務, 默認取值爲 REQUIRED, 即便用調用方法的事務REQUIRES_NEW: 事務本身的事務, 調用的事務方法的事務被掛起。spring
二、使用 isolation 指定事務的隔離級別, 最經常使用的取值爲 READ_COMMITTED。sql
三、默認狀況下 Spring 的聲明式事務對全部的運行時異常進行回滾. 也能夠經過對應的屬性進行設置. 一般狀況下去默認值便可。四、使用 readOnly 指定事務是否爲只讀. 表示這個事務只讀取數據但不更新數據,這樣能夠幫助數據庫引擎優化事務. 若真的是一個只讀取數據庫值的方法, 應設置 readOnly=true數據庫
五、使用 timeout 指定強制回滾以前事務能夠佔用的時間。微信
我的疑問有一些,結尾來講,用教程例子來講吧。ide
個人代碼以下:測試
BookShopDao接口package com.demo.spring.bean; public interface BookShopDao { //根據書的編號返回書的單價 publicint findBookPriceByIsbn(String isbn); //根據書的編號返回輸的庫存 publicvoidupdateBookStock(String isbn); //更新用戶帳戶餘額 publicvoid updateUserAccount(String username, int price); }
實現類優化
package com.demo.spring.bean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; @Repository("bookShopDao") public class BookShopDaoImpl implements BookShopDao { @Autowired private JdbcTemplate jdbcTemplate; @Override public int findBookPriceByIsbn(String isbn) { // TODO Auto-generated method stub String sql = "SELECT price FROM book WHERE isbn=?"; Integer price = jdbcTemplate.queryForObject(sql, Integer.class, isbn); return price; } @Override public void updateBookStock(String isbn) { // TODO Auto-generated method stub String sql = "SELECT stock FROM book_stock WHERE isbn= ? "; Integer stock = jdbcTemplate.queryForObject(sql, Integer.class, isbn); if (stock == 0) { throw new BookStockException("庫存不足!!"); } String sql1 = "UPDATE book_stock SET stock=stock-1 WHERE isbn=?"; jdbcTemplate.update(sql1, isbn); } @Override public void updateUserAccount(String username, int price) { // TODO Auto-generated method stub String sql1 = "SELECT balance FROM account WHERE username= ? "; Integer account = jdbcTemplate.queryForObject(sql1, Integer.class, username); if (account < price) { throw new AccountException("餘額不足!!"); } String sql = "UPDATE account SET balance=balance-? WHERE username=?"; . jdbcTemplate.update(sql, price, username);
BookShopService接口
package com.demo.spring.bean; publicinterfaceBookShopService { //購書 publicvoid purchase(String username, String isbn); }
實現類
package com.demo.spring.bean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service("bookShopService") public class BookShopServiceImpl implements BookShopService { @Autowired private BookShopDao bookShopDao; // @Transactional(propagation=Propagation.REQUIRES_NEW,isolation=Isolation.READ_COMMITTED,readOnly=true,timeout=5,noRollbackFor=AccountException.class) @Transactional @Override public void purchase(String username, String isbn) { try { Thread.sleep(5000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } // TODO Auto-generated method stub int price = bookShopDao.findBookPriceByIsbn(isbn); bookShopDao.updateBookStock(isbn); bookShopDao.updateUserAccount(username, price);
Cashier批量購書接口
package com.demo.spring.bean; import java.util.List; public interface Cashier { //批量購書 public void checkout(String username, List<String> isbns);
實現類
package com.demo.spring.bean; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service("cashier") public class CashierImpl implements Cashier { @Autowired private BookShopService bookShopService; // @Transactional @Override public void checkout(String username, List<String> isbns) { // TODO Auto-generated method stub for (String isbn : isbns) { bookShopService.purchase(username, isbn);}} } } } } }
帳戶餘額不足異常(自定義異常)
庫存不足異常(自定義異常)
.
package com.demo.spring.bean; public class AccountException extends RuntimeException { /** * */ private static final long serialVersionUID = 1L; public AccountException() { super(); // TODO Auto-generated constructor stub } public AccountException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); // TODO Auto-generated constructor stub } public AccountException(String message, Throwable cause) { super(message, cause); // TODO Auto-generated constructor stub } public AccountException(String message) { super(message); // TODO Auto-generated constructor stub } public AccountException(Throwable cause) { super(cause); // TODO Auto-generated constructor stub
測試類
package com.demo.spring.bean; import java.util.Arrays; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class MainTest { private ApplicationContext ctx; @Autowired private Cashier cashier; { ctx=new ClassPathXmlApplicationContext("bean.xml"); cashier=(Cashier) ctx.getBean("cashier"); } @Test public void test(){ // System.out.println(bookShopDao.findBookPriceByIsbn("1001")); cashier.checkout("rongrong", Arrays.asList("1001","1002")); } } } }
bean文件
<context:property-placeholder location="classpath:db.properties"/> <!-- 配置mysql數據源 --> <bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="${db.driverClassName}"></property> <property name="url" value="${db.url}"></property> <property name="username" value="${db.username}"></property> <property name="password" value="${db.password}"></property> </bean> <!-- 配置jdbc模板 --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="datasource"></property> </bean> <!-- 配置事務管理器 --> <bean id="transactionManagertest" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="datasource"></property> </bean> <!-- 啓用事務註解 --> <tx:annotation-driven transaction-manager="transactionManagertest"/> </beans>
數據源
db.driverClassName=com.mysql.jdbc. Driver db.url=jdbc:mysql: //localhost:3306/spring db.username=root db.password=root
疑問以下:
一、前提條件:用戶按照1001,1002這種順序去購書,調用checkout接口批量購書 @Transactional註解確實知足原子性操做,要麼都作,要麼不作 可是,我試驗了下,若是在public void checkout(String username, Listisbns) 上方不加Transactional註解與在public void purchase(String username, String isbn)上方加@Transactional(propagation=Propagation.REQUIRES_NEW效果同樣
二、當帳戶餘額爲120,能夠知足帳戶減去1001的單價,1001的庫存減1 可是,將帳戶餘額改成80,從單價上看能夠買1002那本書,按照上面的順序去買書,按照上面加上註解@Transactional(propagation=Propagation.REQUIRES_NEW效果同樣,根本很差使,我我的以爲由於在更新帳戶餘額,那有個判斷先查詢1001的書的單價確實大於當前帳戶餘額80,先判斷了,因此拋異常後面代碼就不走了
三、另外當前帳戶餘額能夠買1002這本書,想在不改變購書的順序狀況下,用@Transactional註解實現,能夠買1002這本書?,減去當前帳戶餘額80,更新1002書的庫存,哪位大神看到,幫我看下,怎麼用這個註解實現?
以上爲個人我的疑惑的點,有興趣的同窗能夠研究下,而後在公號留言給我便可,小編不勝感謝!
附上:sql
/* Navicat MySQL Data Transfer Source Server : myTestdata Source Server Version : 50627 Source Host : localhost:3306 Source Database : spring Target Server Type : MYSQL Target Server Version : 50627 File Encoding : 65001 Date: 2017-01-18 11:28:50 */ SET FOREIGN_KEY_CHECKS= 0 ; -- ---------------------------- -- Table structure for account -- ---------------------------- DROP TABLE IF EXISTS `account` ; CREATE TABLE `account` ( `username` varchar( 50 ) NOT NULL, `balance`int(11) DEFAULT NULL, PRIMARY KEY ( `username`)) ENGINE=InnoDB DEFAULT CHARSET=gbk; -- ---------------------------- -- Records of account -- ---------------------------- INSERT INTO `account` VALUES ('rongrong', '300'); INSERT INTO `account` VALUES ('zhangsan', '200'); -- ---------------------------- -- Table structure for book -- ---------------------------- DROP TABLE IF EXISTS `book`;CREATE TABLE `book` ( `isbn`varchar(50) NOT NULL, `book_name varchar(100) DEFAULT NULL, `price`int(11) DEFAULT NULL, PRIMARY KEY (`isbn`) ) ENGINE= InnoDB DEFAULT CHARSET=gbk; -- ---------------------------- -- Records of book -- ---------------------------- INSERT INTO `book` VALUES ('1001', 'java', '100'); INSERT INTO `book` VALUES ('1002', 'python', '60'); -- ---------------------------- -- Tablestructure for book_stock -- ---------------------------- DROP TABLE IF EXISTS `book_stock`;CREATE TABLE `book_stock` ( `isbn` varchar(50) NOT NULL, `bookname varchar(100) DEFAULT NULL, `stock int(11 ) DEFAULT NULL, PRIMARY KEY (`isbn`)) ENGINE=InnoDB DEFAULT CHARSET=gbk; -- ---------------------------- -- Recordsof book_stock -- ---------------------------- INSERT INTO `book_stock` VALUES ('1001', 'java','10'); INSERT INTO `book_stock` VALUES ('1002', 'python', '10');
還有一件重要的事情要和你們說,我不是常常看公衆號後臺,因此有時候你們在後臺發留言,時間一旦超過2天,我就沒有回覆權限了。因此,我要公佈本身的微信號了,歡迎你們來埋伏我
關於測試技術你或許還想看:
軟件測試工程師在上海的求職經歷
個人測試用例是這樣寫的
TestNg中的斷言你真的瞭解嗎
若是給你一個登錄頁面你怎麼測試?
最後,漲工資從轉發開始!!