Spring 的聲明式事務(wù)控制
1. 前言
各位同學(xué)大家好,long time no see.
本小節(jié),我給大家?guī)?Spring 對于事務(wù)的另外一種支持方式,也就是聲明式事務(wù)的配置。其實聲明式配置和 xml 文件的配置,孰優(yōu)孰劣并不是重點。
Spring 框架設(shè)計兩種模式的初衷更多是體現(xiàn)技術(shù)的多樣性,畢竟條條大路通羅馬。您說呢?所以本小節(jié)重點就看如果使用注解來對事務(wù)做支持,那么我們應(yīng)該如何做,又有哪些需要注意點地方。
各位看官,隨我來,不要掉隊哦…
課程回顧:
老套路,首先我們回顧一下 xml 對于事務(wù)支持的實現(xiàn):
1. 在 Spring 的 xml 配置文件中,使用 bean 標(biāo)簽初始化配置事務(wù)的管理器類 DataSourceTransactionManager;
2. 在 Spring 的 xml 配置文件中,通過 tx:advice 節(jié)點配置事務(wù)使用的通知方式,已經(jīng)支持的事務(wù)級別;
3. 在 Spring 的 xml 配置文件中,通過 aop:config 節(jié)點指定切入點,說明哪些類的哪些方法需要支持事務(wù),同時將配置的切入點和通知整合到一起。
xml 的方式回顧之后,就看我們使用注解如何替換掉上面的必要配置吧…
2. 實例演示
2.1 工程搭建
1. 創(chuàng)建工程
2. 引入依賴
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!-- Spring jdbc 使用的依賴-->
<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)備代碼
實體類代碼
/**
* 賬戶的實體類
*/
public class Account implements Serializable {
//數(shù)據(jù)id
private Integer id;
//賬號編碼
private String accountNum;
//賬號金額
private Float money;
}
持久層接口代碼
/**
* 賬戶的持久層接口
*/
public interface IAccountDao {
/**
* 根據(jù)Id查詢賬戶
* @param accountId
* @return
*/
Account findAccountById(Integer accountId);
/**
* 保存賬戶
* @param account
*/
void saveAccount(Account account);
/**
* 更新賬戶
* @param account
*/
void updateAccount(Account account);
}
持久層實現(xiàn)類代碼
/**
* 賬戶的持久層實現(xiàn)類
*/
@Repository
public class AccountDaoImpl implements IAccountDao {
//jdbc模板類屬性
@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 {
/**
* 賬戶轉(zhuǎn)賬
* @param fromId toId
*/
public void transMoney(Integer fromId, Integer toId, Integer money);
}
業(yè)務(wù)層實現(xiàn)類代碼
/**
* @Auther: wyan
* @Description:
*/
@Service
@Transactional
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);
//原始賬號減錢
fromAccount.setMoney(fromAccount.getMoney()-money);
accountDao.updateAccount(fromAccount);
//拋出異常
int i=1/0;
//轉(zhuǎn)賬賬號加錢
toAccount.setMoney(toAccount.getMoney()+money);
accountDao.updateAccount(toAccount);
}
}
Tips: 此時需要注意注解
@Transactional
的含義。
Transactional 就是表示事務(wù),那么在此類上面加入注解,說明需要 Spring 框架針對此類的方法做事務(wù)的增強(qiáng)行為,也就是說此注解其實是相當(dāng)于我們在配置文件中配置的節(jié)點 tx:advice。
那么這時候有的細(xì)心的同學(xué)可能會有些疑問:
- 我們在 xml 文件中可以配置事務(wù)的傳播行為與隔離級別,那么這一個注解如何制定事務(wù)的傳播行為與隔離級別呢?
- 一個類中如果定義方法過多,而實際上需要增強(qiáng)控制事務(wù)的方法只有一部分,如何縮小粒度,只控制需要事務(wù)的方法呢?
ok,大家。這里有必要跟大家解釋下此注解的其余使用方式:
問題一答疑:
在注解后面可以通過括號內(nèi)的參數(shù)設(shè)置隔離級別與傳播行為。比如:
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.READ_COMMITTED)
此表達(dá)式的含義是事務(wù)一定需要,并且是讀已提交。問題二答疑:
在方法上使用注解。類上面可以不使用
@Transactional
注解,而是將注解寫在需要用到事務(wù)的方法之上。
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>
<!-- 配置數(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ù)驅(qū)動-->
<tx:annotation-driven/>
</beans>
Tips: 此處需要注意
tx:annotation-driven
節(jié)點
無需配置通知節(jié)點與切面節(jié)點,而是使用 tx:annotation-driven
節(jié)點表示,事務(wù)的支持方式為聲明式事務(wù)。
5. 測試代碼
public class AccountServiceTest {
public static void main(String[] args) {
//1.獲取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
//2.獲取業(yè)務(wù)對象
UserService userService = ac.getBean(UserService.class);
//3.從id為1的賬號轉(zhuǎn)成1000到2賬號
userService.transMoney(1,2,1000);
System.out.println("轉(zhuǎn)賬完成..");
}
}
6. 測試結(jié)果:
ok, 大家,我們繼續(xù)測試之前的轉(zhuǎn)賬代碼,依然得到錯誤的異常信息。同時數(shù)據(jù)庫的金額并沒有發(fā)生改變,因為事務(wù)的控制,保證了數(shù)據(jù)的一致性原子性。那么也證明我們聲明式事務(wù)的案例測試成功。
3. 總結(jié)
Spring 的聲明式事務(wù),我們今天就到這里。通過本小節(jié),我們知道聲明式事務(wù)實現(xiàn)一樣很簡單:
- xml 文件中開啟注解驅(qū)動
tx:annotation-driven
; - 在實現(xiàn)類上使用
@Transactional
注解。
上面兩個步驟即可實現(xiàn)聲明式事務(wù)的控制,配置更為簡潔,代碼可讀性也更強(qiáng)。你學(xué)會了嗎?
如果你問我,什么是達(dá)到成功最有效的方法,我會告訴你 —— 堅持!