十六、Spring对事务的支持
0.注意的点
- 1)一个事务中如果出现了 异常,即使进行了 try catch 捕获 处理异常,也依然会回滚!。不会提交事务。 —>它不知道你报错的部分,或者后面代码是否还有dml 代码,就直接回滚。
16.1 事务概述
什么是事务
- 在一个业务流程当中,通常需要多条DML(insert delete update)语句共同联合才能完成,这多条DML语句必须同时成功,或者同时失败,这样才能保证数据的安全。
- 多条DML要么同时成功,要么同时失败,这叫做事务。
- 事务:Transaction(tx)
事务的四个处理过程:
- 第一步:开启事务 (start transaction)
- 第二步:执行核心业务代码
- 第三步:提交事务(如果核心业务处理过程中没有出现异常)(commit transaction)
- 第四步:回滚事务(如果核心业务处理过程中出现异常)(rollback transaction)
事务的四个特性:
-
16.2 引入事务场景
以银行账户转账为例学习事务。两个账户act-001和act-002。act-001账户向act-002账户转账10000,必须同时成功,或者同时失败。(一个减成功,一个加成功, 这两条update语句必须同时成功,或同时失败。)
连接数据库的技术采用Spring框架的JdbcTemplate。
采用三层架构搭建:
模块名:spring6-013-tx-bank(依赖如下)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion>
<groupId>com.powernode</groupId> <artifactId>spring6-013-tx-bank</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging>
<repositories> <repository> <id>repository.spring.milestone</id> <name>Spring Milestone Repository</name> <url>https://repo.spring.io/milestone</url> </repository> </repositories>
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>6.0.0-M2</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>6.0.0-M2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.30</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.13</version> </dependency> <dependency> <groupId>jakarta.annotation</groupId> <artifactId>jakarta.annotation-api</artifactId> <version>2.1.1</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency> </dependencies>
<properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> </properties>
</project>
|
第一步:准备数据库表
表结构:
表数据:
第二步:创建包结构
com.powernode.bank.pojo
com.powernode.bank.service
com.powernode.bank.service.impl
com.powernode.bank.dao
com.powernode.bank.dao.impl
第三步:准备POJO类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| package com.powernode.bank.pojo;
public class Account { private String actno; private Double balance;
@Override public String toString() { return "Account{" + "actno='" + actno + '\'' + ", balance=" + balance + '}'; }
public Account() { }
public Account(String actno, Double balance) { this.actno = actno; this.balance = balance; }
public String getActno() { return actno; }
public void setActno(String actno) { this.actno = actno; }
public Double getBalance() { return balance; }
public void setBalance(Double balance) { this.balance = balance; } }
|
第四步:编写持久层
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| package com.powernode.bank.dao;
import com.powernode.bank.pojo.Account;
public interface AccountDao {
Account selectByActno(String actno);
int update(Account act);
} package com.powernode.bank.dao.impl;
import com.powernode.bank.dao.AccountDao; import com.powernode.bank.pojo.Account; import jakarta.annotation.Resource; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Component;
@Repository("accountDao") public class AccountDaoImpl implements AccountDao {
@Resource(name = "jdbcTemplate") private JdbcTemplate jdbcTemplate;
@Override public Account selectByActno(String actno) { String sql = "select actno, balance from t_act where actno = ?"; Account account = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Account.class), actno); return account; }
@Override public int update(Account act) { String sql = "update t_act set balance = ? where actno = ?"; int count = jdbcTemplate.update(sql, act.getBalance(), act.getActno()); return count; } }
|
第五步:编写业务层
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| package com.powernode.bank.service;
public interface AccountService {
void transfer(String fromActno, String toActno, double money); } package com.powernode.bank.service.impl;
import com.powernode.bank.dao.AccountDao; import com.powernode.bank.pojo.Account; import com.powernode.bank.service.AccountService; import jakarta.annotation.Resource; import org.springframework.stereotype.Service;
@Service("accountService") public class AccountServiceImpl implements AccountService {
@Resource(name = "accountDao") private AccountDao accountDao;
@Override public void transfer(String fromActno, String toActno, double money) { Account fromAct = accountDao.selectByActno(fromActno); if (fromAct.getBalance() < money) { throw new RuntimeException("账户余额不足"); } Account toAct = accountDao.selectByActno(toActno); fromAct.setBalance(fromAct.getBalance() - money); toAct.setBalance(toAct.getBalance() + money); int count = accountDao.update(fromAct); count += accountDao.update(toAct); if (count != 2) { throw new RuntimeException("转账失败,请联系银行"); } } }
|
第六步:编写Spring配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <?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: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/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.powernode.bank"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/spring6"/> <property name="username" value="root"/> <property name="password" value="root"/> </bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"/> </bean>
</beans>
|
第七步:编写表示层(测试程序)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| package com.powernode.spring6.test;
import com.powernode.bank.service.AccountService; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;
public class BankTest { @Test public void testTransfer(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml"); AccountService accountService = applicationContext.getBean("accountService", AccountService.class); try { accountService.transfer("act-001", "act-002", 10000); System.out.println("转账成功"); } catch (Exception e) { e.printStackTrace(); }
} }
|
执行结果:
数据变化:
模拟异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| package com.powernode.bank.service.impl;
import com.powernode.bank.dao.AccountDao; import com.powernode.bank.pojo.Account; import com.powernode.bank.service.AccountService; import jakarta.annotation.Resource; import org.springframework.stereotype.Service;
@Service("accountService") public class AccountServiceImpl implements AccountService {
@Resource(name = "accountDao") private AccountDao accountDao;
@Override public void transfer(String fromActno, String toActno, double money) { Account fromAct = accountDao.selectByActno(fromActno); if (fromAct.getBalance() < money) { throw new RuntimeException("账户余额不足"); } Account toAct = accountDao.selectByActno(toActno); fromAct.setBalance(fromAct.getBalance() - money); toAct.setBalance(toAct.getBalance() + money); int count = accountDao.update(fromAct); String s = null; s.toString();
count += accountDao.update(toAct); if (count != 2) { throw new RuntimeException("转账失败,请联系银行"); } } }
|
执行结果:
数据库表中数据:
丢了1万。
16.3 Spring对事务的支持
Spring实现事务的两种方式
Spring事务管理API
Spring对事务的管理底层实现方式是基于AOP实现的。采用AOP的方式进行了封装。所以Spring专门针对事务开发了一套API,API的核心接口如下:
PlatformTransactionManager接口:spring事务管理器的核心接口。在Spring6中它有两个实现:
- DataSourceTransactionManager:支持JdbcTemplate、MyBatis、Hibernate等事务管理。
- JtaTransactionManager:支持分布式事务管理。
如果要在Spring6中使用JdbcTemplate,就要使用DataSourceTransactionManager来管理事务。(Spring内置写好了,可以直接用。)
声明式事务之注解实现方式
1 2 3
| <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean>
|
- 第二步:在spring配置文件中引入tx命名空间。
1 2 3 4 5 6 7 8
| <?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:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
|
- 第三步:在spring配置文件中配置“事务注解驱动器”,开始注解的方式控制事务。
1
| <tx:annotation-driven transaction-manager="transactionManager"/>
|
- 第四步:在service类上或方法上添加@Transactional注解
在类上添加该注解,该类中所有的方法都有事务。在某个方法上添加该注解,表示只有这个方法使用事务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| package com.powernode.bank.service.impl;
import com.powernode.bank.dao.AccountDao; import com.powernode.bank.pojo.Account; import com.powernode.bank.service.AccountService; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional;
@Service("accountService") @Transactional public class AccountServiceImpl implements AccountService {
@Resource(name = "accountDao") private AccountDao accountDao;
@Override public void transfer(String fromActno, String toActno, double money) { Account fromAct = accountDao.selectByActno(fromActno); if (fromAct.getBalance() < money) { throw new RuntimeException("账户余额不足"); } Account toAct = accountDao.selectByActno(toActno); fromAct.setBalance(fromAct.getBalance() - money); toAct.setBalance(toAct.getBalance() + money); int count = accountDao.update(fromAct);
String s = null; s.toString();
count += accountDao.update(toAct); if (count != 2) { throw new RuntimeException("转账失败,请联系银行"); } } }
|
当前数据库表中的数据:
执行测试程序:
虽然出现异常了,再次查看数据库表中数据:
通过测试,发现数据没有变化,事务起作用了。
事务属性
事务属性包括哪些
事务中的重点属性:
- 事务传播行为
- 事务隔离级别
- 事务超时
- 只读事务
- 设置出现哪些异常回滚事务
- 设置出现哪些异常不回滚事务
事务传播行为
什么是事务的传播行为?
在service类中有a()方法和b()方法,a()方法上有事务,b()方法上也有事务,当a()方法执行过程中调用了b()方法,事务是如何传递的?合并到一个事务里?还是开启一个新的事务?这就是事务传播行为。
事务传播行为在spring框架中被定义为枚举类型:
一共有七种传播行为:
- REQUIRED:支持当前事务,如果不存在就新建一个(默认)【没有就新建,有就加入】
- SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行【有就加入,没有就不管了】
- MANDATORY(强制):必须运行在一个事务中,如果当前没有事务正在发生,将抛出一个异常【强制你要有就加入,没有就我就闹,我就抛异常】
- REQUIRES_NEW:开启一个新的事务,如果一个事务已经存在,则将这个存在的事务挂起【不管有没有,直接开启一个新事务,开启的新事务和之前的事务不存在嵌套关系,之前事务被挂起】 ———> ==并列==
- NOT_SUPPORTED:以非事务方式运行,如果有事务存在,挂起当前事务【不支持事务,存在就挂起】
- NEVER:以非事务方式运行,如果有事务存在,抛出异常【 不支持事务,不想要有事务,存在就抛异常】
- NESTED:如果当前正有一个事务在进行中,则该方法应当运行在一个嵌套式事务中。被嵌套的事务可以独立于外层事务进行提交或回滚。如果外层事务不存在,行为就像REQUIRED一样。【有事务的话,就在这个事务里再嵌套一个完全独立的事务,嵌套的事务可以独立的提交和回滚。没有事务就和REQUIRED一样。】
在代码中设置事务的传播行为:
1
| @Transactional(propagation = Propagation.REQUIRED)
|
可以编写程序测试一下传播行为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| @Transactional(propagation = Propagation.REQUIRED) public void save(Account act) {
accountDao.insert(act);
Account act2 = new Account("act-004", 1000.0); try { accountService.save(act2); } catch (Exception e) {
} } @Override
@Transactional(propagation = Propagation.REQUIRES_NEW) public void save(Account act) { accountDao.insert(act); String s = null; s.toString();
}
|
一定要集成Log4j2日志框架,在日志信息中可以看到更加详细的信息。
事务隔离级别
事务隔离级别类似于教室A和教室B之间的那道墙,隔离级别越高表示墙体越厚。隔音效果越好。
数据库中读取数据存在的三大问题:(三大读问题)
- 脏读:读取到没有提交到数据库的数据,叫做脏读。
- 不可重复读:在同一个事务当中,第一次和第二次读取的数据不一样。
- 幻读:读到的数据是假的。
事务隔离级别包括四个级别:
读未提交:READ_UNCOMMITTED
- 这种隔离级别,存在脏读问题,所谓的脏读(dirty read)表示能够读取到其它事务未提交的数据。
读提交:READ_COMMITTED
- 解决了脏读问题,其它事务提交之后才能读到,但存在不可重复读问题。
可重复读:REPEATABLE_READ
- 解决了不可重复读,可以达到可重复读效果,只要当前事务不结束,读取到的数据一直都是一样的。但存在幻读问题。
序列化:SERIALIZABLE
-
大家可以通过一个表格来记忆:
隔离级别 |
脏读 |
不可重复读 |
幻读 |
读未提交 |
有 |
有 |
有 |
读提交 |
无 |
有 |
有 |
可重复读 |
无 |
无 |
有 |
序列化 |
无 |
无 |
无 |
在Spring代码中如何设置隔离级别?
隔离级别在spring中以枚举类型存在:
1
| @Transactional(isolation = Isolation.READ_COMMITTED)
|
测试事务隔离级别:READ_UNCOMMITTED 和 READ_COMMITTED
怎么测试:一个service负责插入,一个service负责查询。负责插入的service要模拟延迟。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| package com.powernode.bank.service.impl;
import com.powernode.bank.dao.AccountDao; import com.powernode.bank.pojo.Account; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Transactional;
@Service("i1") public class IsolationService1 {
@Resource(name = "accountDao") private AccountDao accountDao;
@Transactional(isolation = Isolation.READ_COMMITTED) public void getByActno(String actno) { Account account = accountDao.selectByActno(actno); System.out.println("查询到的账户信息:" + account); }
} package com.powernode.bank.service.impl;
import com.powernode.bank.dao.AccountDao; import com.powernode.bank.pojo.Account; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional;
@Service("i2") public class IsolationService2 {
@Resource(name = "accountDao") private AccountDao accountDao;
@Transactional public void save(Account act) { accountDao.insert(act); try { Thread.sleep(1000 * 20); } catch (InterruptedException e) { e.printStackTrace(); } }
}
|
测试程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Test public void testIsolation1(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml"); IsolationService1 i1 = applicationContext.getBean("i1", IsolationService1.class); i1.getByActno("act-004"); }
@Test public void testIsolation2(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml"); IsolationService2 i2 = applicationContext.getBean("i2", IsolationService2.class); Account act = new Account("act-004", 1000.0); i2.save(act); }
|
通过执行结果可以清晰的看出隔离级别不同,执行效果不同。
事务超时
代码如下:
1
| @Transactional(timeout = 10)
|
以上代码表示设置事务的超时时间为10秒。
表示超过10秒如果该事务中所有的DML语句还没有执行完毕的话,最终结果会选择回滚。
默认值-1,表示没有时间限制。
这里有个坑,事务的超时时间指的是哪段时间?
在当前事务当中,最后一条DML语句执行之前的时间。如果最后一条DML语句后面很有很多业务逻辑,这些业务代码执行的时间不被计入超时时间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @Transactional(timeout = 10) public void save(Account act) { accountDao.insert(act); try { Thread.sleep(1000 * 15); } catch (InterruptedException e) { e.printStackTrace(); } } @Transactional(timeout = 10) public void save(Account act) { try { Thread.sleep(1000 * 15); } catch (InterruptedException e) { e.printStackTrace(); } accountDao.insert(act); }
|
当然,如果想让整个方法的所有代码都计入超时时间的话,可以在方法最后一行添加一行无关紧要的DML语句。
只读事务
代码如下:
1
| @Transactional(readOnly = true)
|
将当前事务设置为只读事务,在该事务执行过程中只允许select语句执行,delete insert update均不可执行。
该特性的作用是:启动spring的优化策略。提高select语句执行效率。
如果该事务中确实没有增删改操作,建议设置为只读事务。
设置哪些异常回滚事务
代码如下:
1
| @Transactional(rollbackFor = RuntimeException.class)
|
表示只有发生RuntimeException异常或该异常的子类异常才回滚。
设置哪些异常不回滚事务
代码如下:
1
| @Transactional(noRollbackFor = NullPointerException.class)
|
表示发生NullPointerException或该异常的子类异常不回滚,其他异常则回滚。
事务的全注解式开发
编写一个类来代替配置文件,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| package com.powernode.bank;
import com.alibaba.druid.pool.DruidDataSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
@Configuration @ComponentScan("com.powernode.bank") @EnableTransactionManagement public class Spring6Config {
@Bean public DataSource getDataSource(){ DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/spring6"); dataSource.setUsername("root"); dataSource.setPassword("root"); return dataSource; }
@Bean(name = "jdbcTemplate") public JdbcTemplate getJdbcTemplate(DataSource dataSource){ JdbcTemplate jdbcTemplate = new JdbcTemplate(); jdbcTemplate.setDataSource(dataSource); return jdbcTemplate; }
@Bean public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){ DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(); dataSourceTransactionManager.setDataSource(dataSource); return dataSourceTransactionManager; }
}
|
测试程序如下:
1 2 3 4 5 6 7 8 9 10 11
| @Test public void testNoXml(){ ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Spring6Config.class); AccountService accountService = applicationContext.getBean("accountService", AccountService.class); try { accountService.transfer("act-001", "act-002", 10000); System.out.println("转账成功"); } catch (Exception e) { e.printStackTrace(); } }
|
执行结果:
数据库表中数据:
声明式事务之XML实现方式
配置步骤:
- 第一步:配置事务管理器
- 第二步:配置通知
- 第三步:配置切面
记得添加aspectj的依赖:
1 2 3 4 5 6
| <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>6.0.0-M2</version> </dependency>
|
Spring配置文件如下:
记得添加aop的命名空间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| <?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:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.powernode.bank"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/spring6"/> <property name="username" value="root"/> <property name="password" value="root"/> </bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"/> </bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean>
<tx:advice id="txAdvice" transaction-manager="txManager"> <!- 配置通知的相关属性 --> <tx:attributes> <tx:method name="save*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/> <tx:method name="del*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/> <tx:method name="update*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/> <tx:method name="transfer*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/> <tx:method name="query" read-only="true"/> <tx:method name="find*" read-only="true"/> <tx:method name="get***" read-only="true"/> </tx:attributes> </tx:advice>
<aop:config> <aop:pointcut id="txPointcut" expression="execution(* com.powernode.bank.service..*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/> </aop:config>
</beans>
|
将AccountServiceImpl类上的@Transactional注解删除。
编写测试程序:
1 2 3 4 5 6 7 8 9 10 11
| @Test public void testTransferXml(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring2.xml"); AccountService accountService = applicationContext.getBean("accountService", AccountService.class); try { accountService.transfer("act-001", "act-002", 10000); System.out.println("转账成功"); } catch (Exception e) { e.printStackTrace(); } }
|
执行结果:
数据库表中记录:
通过测试可以看到配置XML已经起作用了。