Spring 的事務(wù)管理器
1. 前言
各位同學(xué)大家好,又輪到我的時(shí)間了。
在上一小節(jié)咱們已經(jīng)可以使用 Spring 集成了 Jdbc Template,并且實(shí)現(xiàn)了數(shù)據(jù)的基本操作基本使用,那么本小節(jié)我們?cè)诓僮鲾?shù)據(jù)的基礎(chǔ)之上,講解一下 Spring 中的事務(wù)處理。
其實(shí)我們?cè)谇懊娴?AOP 相關(guān)的小節(jié)中,已經(jīng)實(shí)現(xiàn)了對(duì)事務(wù)的控制。只不過(guò)呢,之前的事務(wù)控制是由我們手動(dòng)創(chuàng)建的類(lèi)來(lái)管理事務(wù)。嘿嘿嘿,代碼略顯簡(jiǎn)單,功能稍許單一。
而今天呢,我們使用 Spring 框架提供的事務(wù)管理器,并通過(guò) AOP 配置切入點(diǎn)來(lái)管理事務(wù)。那么它能否給我們帶來(lái)一些驚喜呢?來(lái)吧,大家。一起進(jìn)入今天的課程…
課程回顧:
首先我們回顧一下事務(wù)控制的實(shí)現(xiàn)要求:
1. 提供一個(gè)類(lèi),作為切面用于處理事務(wù)的開(kāi)啟,提交和回滾。
2. 通過(guò) xml 文件或者注解來(lái)配置 AOP,表述切入點(diǎn)和使用的切面類(lèi)。
本小節(jié)帶著大家分別使用 xml 文件的方式,和注解的方式實(shí)現(xiàn) Spring 框架對(duì)于事務(wù)的控制。
2. 實(shí)例演示
2.1 思路介紹
Spring 的事務(wù)管理器類(lèi)
Spring 框架本身已經(jīng)充分考慮了對(duì)事物的支持,所以我們完全不必像之前一樣自定義類(lèi)來(lái)實(shí)現(xiàn)對(duì)事物的控制。Spring 已經(jīng)抽象了一整套的事務(wù)機(jī)制,而作為開(kāi)發(fā)人員根本不必了解底層的事務(wù) API,
一樣可以通過(guò)代碼管理數(shù)據(jù)庫(kù)的事務(wù)。頂層的事務(wù)管理器抽象就是 PlatformTransactionManager, 它為事務(wù)管理封裝了一組獨(dú)立于技術(shù)的方法。
而本示例就采用 Spring 提供的管理器實(shí)現(xiàn)類(lèi),來(lái)替換掉之前我們自己編寫(xiě)的事務(wù)控制工具類(lèi)。
2.2 工程搭建
1. 創(chuàng)建工程
2. 引入依賴(lài)
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!-- Spring jdbc 使用的依賴(lài)-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
</dependencies>
3. 準(zhǔn)備代碼
實(shí)體類(lèi)代碼
/**
* 賬戶(hù)的實(shí)體類(lèi)
*/
public class Account implements Serializable {
//數(shù)據(jù)id
private Integer id;
//賬號(hào)編碼
private String accountNum;
//賬號(hào)金額
private Float money;
}
持久層接口代碼
/**
* 賬戶(hù)的持久層接口
*/
public interface IAccountDao {
/**
* 根據(jù)Id查詢(xún)賬戶(hù)
* @param accountId
* @return
*/
Account findAccountById(Integer accountId);
/**
* 保存賬戶(hù)
* @param account
*/
void saveAccount(Account account);
/**
* 更新賬戶(hù)
* @param account
*/
void updateAccount(Account account);
}
持久層實(shí)現(xiàn)類(lèi)代碼
/**
* 賬戶(hù)的持久層實(shí)現(xiàn)類(lèi)
*/
@Repository
public class AccountDaoImpl implements IAccountDao {
//jdbc模板類(lèi)屬性
@Autowired
private JdbcTemplate jdbcTemplate;
//根據(jù)id查找
public Account findAccountById(Integer accountId) {
List<Account> accounts = jdbcTemplate.query("select * from account where id = ?",new BeanPropertyRowMapper<Account>(Account.class),accountId);
return accounts.isEmpty()?null:accounts.get(0);
}
public void saveAccount(Account account) {
jdbcTemplate.update("insert into account values(?,?,?)",
account.getId(),account.getAccountNum(),account.getMoney());
}
public void updateAccount(Account account) {
jdbcTemplate.update("update account set accountnum=?,money=? where id=?",account.getAccountNum(),account.getMoney(),account.getId());
}
}
業(yè)務(wù)層接口代碼
/**
* @Auther: wyan
*/
public interface UserService {
/**
* 賬戶(hù)轉(zhuǎn)賬
* @param fromId toId
*/
public void transMoney(Integer fromId, Integer toId, Integer money);
}
業(yè)務(wù)層實(shí)現(xiàn)類(lèi)代碼
/**
* @Auther: wyan
* @Description:
*/
@Service
public class UserServiceImpl implements UserService {
@Autowired
private IAccountDao accountDao;
public void transMoney(Integer fromId, Integer toId, Integer money) {
Account fromAccount = accountDao.findAccountById(fromId);
Account toAccount = accountDao.findAccountById(toId);
//原始賬號(hào)減錢(qián)
fromAccount.setMoney(fromAccount.getMoney()-money);
accountDao.updateAccount(fromAccount);
//拋出異常
int i=1/0;
//轉(zhuǎn)賬賬號(hào)加錢(qián)
toAccount.setMoney(toAccount.getMoney()+money);
accountDao.updateAccount(toAccount);
}
}
Tips: 我們?cè)俳o原始賬號(hào)減掉錢(qián)后,執(zhí)行保存。然后在這里會(huì)出現(xiàn)個(gè)異常,就是為了測(cè)試事務(wù)的特性,所以手動(dòng)加了個(gè)除 0 的代碼。
4. 配置文件
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.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
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--配置JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置數(shù)據(jù)源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql:///transmoney"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!--包路徑掃描-->
<context:component-scan base-package="com.offcn"></context:component-scan>
<!--事務(wù)管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--事務(wù)的通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="del*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="find*" read-only="true"/>
</tx:attributes>
</tx:advice>
<!--切面的配置-->
<aop:config>
<aop:pointcut id="pt" expression="execution(* com.offcn.service.*.*(..))"/>
<aop:advisor pointcut-ref="pt" advice-ref="txAdvice" />
</aop:config>
</beans>
此處需要注意:
? context:component-scan:掃描的節(jié)點(diǎn)路徑為包含 service 和 dao 兩個(gè)子目錄的父級(jí)目錄;
? transactionManager: 此節(jié)點(diǎn)作用就是初始化 Spring 框架提供的事務(wù)管理器的實(shí)現(xiàn)類(lèi);
? tx:advice: 此節(jié)點(diǎn)的作用是配置切面的通知,因?yàn)橹拔覀兊那忻骖?lèi)是自定義的,這里使用的是 Spring 提供的事務(wù)管理器類(lèi)作為切面,那么針對(duì)什么方法需要做增強(qiáng)呢,在此節(jié)點(diǎn)配置,可以看得出來(lái):以 save、del、update 開(kāi)頭的方法都會(huì)支持事務(wù),而 find 開(kāi)頭的方法,指定的是只讀。
? aop:config: 此節(jié)點(diǎn)就是 AOP 的相關(guān)配置節(jié)點(diǎn)了,將切入點(diǎn)和通知整合到一起,同以前的項(xiàng)目差別不大。這里可以看到:切入點(diǎn)規(guī)則是針對(duì) service 下面的所有類(lèi)所有方法任意參數(shù)做增強(qiáng)。通知使用的就是我們上面配置過(guò)的 tx:advice 節(jié)點(diǎn)。
5. 測(cè)試代碼
public class AccountServiceTest {
public static void main(String[] args) {
//1.獲取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
//2.獲取業(yè)務(wù)對(duì)象
UserService userService = ac.getBean(UserService.class);
//3.從id為1的賬號(hào)轉(zhuǎn)成1000到2賬號(hào)
userService.transMoney(1,2,1000);
System.out.println("轉(zhuǎn)賬完成..");
}
}
6. 測(cè)試結(jié)果:
ok, 大家,控制臺(tái)如愿以?xún)敶蛴×水惓5亩褩P畔ⅲ沁@個(gè)不是目的,哈哈哈。目的是在程序執(zhí)行發(fā)生異常的情況下,數(shù)據(jù)的數(shù)據(jù)不會(huì)錯(cuò)亂。我們可以看見(jiàn)數(shù)據(jù)庫(kù)數(shù)據(jù)并沒(méi)有發(fā)生改變。
3. 總結(jié)
Spring 對(duì)于事務(wù)的控制,我們今天就到這里。通過(guò)本小節(jié),我們也能體會(huì)到,使用 Spring 對(duì)事務(wù)控制還是非常簡(jiǎn)單的。無(wú)非以下三個(gè)注意事項(xiàng):
- 配置 Spring 框架提供的事務(wù)管理器;
- 配置控制事務(wù)使用的通知;
- 配置切面將通知與切入點(diǎn)結(jié)合即可。
沒(méi)有比人更高的山… 沒(méi)有比腳更長(zhǎng)的路… 繼續(xù)加油哦!