Spring(5、基於註解的事物)

Koma-forever發表於2017-06-13

Spring中的事物管理

:本文用到的jar包請在同類文章Spring(1-1、基於xml裝配Bean)中查詢。

事務管理是企業級應用程式開發中必不可少的技術,  用來確保資料的完整性和一致性.

事務就是一系列的動作, 它們被當做一個單獨的工作單元. 這些動作要麼全部完成, 要麼全部不起作用

事務的四個關鍵屬性(ACID)

原子性(atomicity): 事務是一個原子操作, 由一系列動作組成. 事務的原子性確保動作要麼全部完成要麼完全不起作用.

一致性(consistency): 一旦所有事務動作完成, 事務就被提交. 資料和資源就處於一種滿足業務規則的一致性狀態中.

隔離性(isolation): 可能有許多事務會同時處理相同的資料, 因此每個事物都應該與其他事務隔離開來, 防止資料損壞.

永續性(durability): 一旦事務完成, 無論發生什麼系統錯誤, 它的結果都不應該受到影響. 通常情況下, 事務的結果被寫到持久化儲存器中

事物的傳播屬性

REQUIRED_NEW傳播屬性

另一種常見的傳播行為是REQUIRES_NEW. 它表示該方法必須啟動一個新事務, 並在自己的事務內執行. 如果有事務在執行, 就應該先掛起它

方法上面的標註:

@Transactional(propagation = Propagation.REQUIRES_NEW)
	public void purchase(String username, String isbn) {

原理圖


使用到的表結構

CREATE TABLE `account` (
  `username` varchar(255) COLLATE utf8_bin NOT NULL,
  `balance` int(11) DEFAULT NULL,
  PRIMARY KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
CREATE TABLE `book` (
  `isbn` int(255) NOT NULL AUTO_INCREMENT,
  `book_name` varchar(255) COLLATE utf8_bin DEFAULT NULL,
  `price` double(10,2) DEFAULT NULL,
  PRIMARY KEY (`isbn`)
) ENGINE=InnoDB AUTO_INCREMENT=1003 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
CREATE TABLE `book_stock` (
  `isbn` int(11) NOT NULL,
  `stock` int(11) DEFAULT NULL,
  PRIMARY KEY (`isbn`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

Spring的宣告式事物(註解)

dao

public interface BookShopDao {
	/**根據書號獲取數的單價*/
	public int findBookPriceByIsbn(String isbn);
	/** 更新庫存,是書號對應的庫存*/
	public void updateBookStock(String isbn);
	/**更新使用者的賬戶餘額,使username的balance-price */
	public void updateUserAccount(String username, int price);
}
@Repository("bookShopDao")
public class BookShopDaoImpl implements BookShopDao {
	@Autowired
	private JdbcTemplate jt;
	public int findBookPriceByIsbn(String isbn) {
		String sql = "SELECT price FROM book WHERE isbn=?";
		return jt.queryForObject(sql, Integer.class, isbn);
	}
	public void updateBookStock(String isbn) {
		// 檢查書的庫存是否足夠,若不夠,則丟擲異常
		String sql2 = "SELECT stock FROM book_stock WHERE isbn=?";
		Integer stock = jt.queryForObject(sql2, Integer.class, isbn);
		if (stock == 0) {
			throw new BookStockException("庫存不足!");
		}
		String sql = "UPDATE book_stock SET stock=stock-1 WHERE isbn=?";
		int update = jt.update(sql, isbn);
		System.out.println("更新了-->" + update + "條");
	}
	public void updateUserAccount(String username, int price) {
		// 驗證使用者的餘額是否足夠
		String banalce = "SELECT balance FROM account WHERE username=?";
		int ba = jt.queryForObject(banalce, Integer.class, username);
		if (ba > 0 && ba < price) {
			throw new UserAccountException("賬戶餘額不足!");
		}
		String sql = "UPDATE account SET balance=balance-? WHERE username=?";
		int update = jt.update(sql, price, username);
		System.out.println("更新了-->" + update + "條");
	}
}

service

public interface BookShopService {
	public void purchase(String username, String isbn);
}
@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService {
	@Autowired
	private BookShopDao bsd;
	@Transactional
	public void purchase(String username, String isbn) {
		// 1獲取書的單價
		int price = bsd.findBookPriceByIsbn(isbn);
		// 2更新數的庫存
		bsd.updateBookStock(isbn);
		// 3更新使用者的餘額
		bsd.updateUserAccount(username, price);
	}
}

配置xml

<context:component-scan base-package="com.spring.beans"></context:component-scan>
	<!-- 匯入配置檔案 -->
	<context:property-placeholder location="classpath:db.properties" />
	<!-- 配置資料來源 -->
	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<property name="user" value="${jdbc.user}"></property>
		<property name="password" value="${jdbc.password}"></property>
		<property name="driverClass" value="${jdbc.driverClass}"></property>
		<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
		<property name="initialPoolSize" value="${jdbc.initPoolSize}"></property>
		<property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property>
	</bean>
	<!--配置Spring的JdbcTemplate工具類 -->
	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
	<!-- 配置事物管理器 -->
	<bean id="tx"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
<!-- 啟用事物註解-->	
<tx:annotation-driven transaction-manager="tx" />

測試類:[測試的時候試著去掉service方法上面的@Transactional和不去掉就可以看出事物起到的作用]

public class BookShopDaoImplTest {
	private ApplicationContext ctx = null;
	private BookShopService bookShopService = null;
	{
		ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
		bookShopService = (BookShopService) ctx.getBean("bookShopService");
	}
	@Test
	public void testbookShopService() {
		bookShopService.purchase("AA", "1001");
	}
}

總結:

1、在xml檔案中新增兩個配置

<bean id="tx"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
<tx:annotation-driven transaction-manager="tx" />

2、在對應的service方法上新增@Transactional就可以

事物的其他屬性(隔離級別,回滾,超時,只讀)

@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService {
	@Autowired
	private BookShopDao bsd;
	// 1.使用propagation指定事物的傳播行為
	// 如何使用事物,預設取值為REQUIRED,即使用呼叫方法的事物
	// 2.REQUIRES_NEW:事物自己的事物,呼叫的事物方法的事物掛起
	// 使用isolation指定書屋的隔離級別,最常用的取值是read_commited
	// 3.預設情況下Spring對宣告式事物對所有的執行時異常進行回滾,也可以通過對應的屬性進行設定。通常情況下取預設值即可
	// 4.使用readOnly指定事物是否為只讀,表示這個事物只讀資料但不更新資料,這樣可以幫助資料庫引擎優化事物
	// 若真的是一個只讀資料庫值的方法,應設定為readOnly=true
	// 5.使用timeout指定強制回滾之前事物佔用的時間
	// @Transactional(propagation = Propagation.REQUIRED, isolation =
	// Isolation.READ_COMMITTED, noRollbackFor = { UserAccountException.class })
	@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED, readOnly = false, timeout = 3)
	public void purchase(String username, String isbn) {
		try {
			Thread.sleep(5000);
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		// 1獲取書的單價
		int price = bsd.findBookPriceByIsbn(isbn);
		// 2更新數的庫存
		bsd.updateBookStock(isbn);
		// 3更新使用者的餘額
		bsd.updateUserAccount(username, price);
	}
}

相關文章