导入数据源
drop table if exists account;
create table account
(accountNo int primary key auto_increment,balance int not null
);insert into account (accountNo, balance)
values (1, 1000),(2, 1000);
并不是任何异常情况下,spring都会回滚事务,默认情况下,RuntimeException和Error的情况下,spring事务才会回滚。
原因:
解法:
@Transactionalpublic void transfer(int from, int to, int amount) throws FileNotFoundException {int fromBalance = accountMapper.findBalanceBy(from);if (fromBalance - amount >= 0) {accountMapper.update(from, -1 * amount);//异常new FileInputStream("aaa");accountMapper.update(to, amount);}}

可见事务并未回滚

@Transactional(rollbackFor = Exception.class)public void transfer(int from, int to, int amount) throws FileNotFoundException {int fromBalance = accountMapper.findBalanceBy(from);if (fromBalance - amount >= 0) {accountMapper.update(from, -1 * amount);//异常new FileInputStream("aaa");accountMapper.update(to, amount);}}


可见事务是进行了回滚的,数据库的数据也并未改变
原因:
解法1:异常原样抛出
解法2:手动设置 TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
@Transactional(rollbackFor = Exception.class)
public void transfer(int from, int to, int amount) {try {int fromBalance = accountMapper.findBalanceBy(from);if (fromBalance - amount >= 0) {accountMapper.update(from, -1 * amount);new FileInputStream("aaa");accountMapper.update(to, amount);}} catch (FileNotFoundException e) {e.printStackTrace();}}

可见事务还是进行了提交,并未回滚
@Transactional(rollbackFor = Exception.class)
public void transfer(int from, int to, int amount) {try {int fromBalance = accountMapper.findBalanceBy(from);if (fromBalance - amount >= 0) {accountMapper.update(from, -1 * amount);new FileInputStream("aaa");accountMapper.update(to, amount);}} catch (FileNotFoundException e) {e.printStackTrace();
// TransactionInterceptor.currentTransactionStatus().setRollbackOnly();throw new RuntimeException(e);}}
抛出异常或者手动设置事务状态进行回滚

原因:事务切面优先级最低,但如果自定义的切面优先级和他一样,则还是自定义切面在内层,这时若自定义切面没有正确抛出异常,事务就无法回滚
解法1:异常原样抛出
解法2:手动设置 TransactionInterceptor.currentTransactionStatus().setRollbackOnly();

@Aspectstatic class MyAspect {@Around("execution(* transfer(..))")public Object around(ProceedingJoinPoint pjp) throws Throwable {LoggerUtils.get().debug("log:{}", pjp.getTarget());try {return pjp.proceed();} catch (Throwable e) {e.printStackTrace();return null;}}}
抛出异常或者手动设置事务状态进行回滚

原因:
解决办法:
@Transactionalvoid transfer(int from, int to, int amount) throws FileNotFoundException {int fromBalance = accountMapper.findBalanceBy(from);if (fromBalance - amount >= 0) {accountMapper.update(from, -1 * amount);accountMapper.update(to, amount);}}

无事务的开启和提交
@Transactional
public void transfer(int from, int to, int amount) throws FileNotFoundException {int fromBalance = accountMapper.findBalanceBy(from);if (fromBalance - amount >= 0) {accountMapper.update(from, -1 * amount);accountMapper.update(to, amount);}}

原因:
解法:
SpringBoot项目就只有一个容器,不会发生该情况)以下是两个配置类:
@ComponentScan("day04.tx.app")
public class WebConfig {
}
@Configuration
@PropertySource("classpath:jdbc.properties")
@EnableTransactionManagement
@EnableAspectJAutoProxy(exposeProxy = true)
@ComponentScan("day04.tx.app.service")
@MapperScan("day04.tx.app.mapper")
public class AppConfig {@ConfigurationProperties("jdbc")@Beanpublic DataSource dataSource() {return new HikariDataSource();}@Beanpublic DataSourceInitializer dataSourceInitializer(DataSource dataSource, DatabasePopulator populator) {DataSourceInitializer dataSourceInitializer = new DataSourceInitializer();dataSourceInitializer.setDataSource(dataSource);dataSourceInitializer.setDatabasePopulator(populator);return dataSourceInitializer;}@Beanpublic DatabasePopulator databasePopulator() {return new ResourceDatabasePopulator(new ClassPathResource("account.sql"));}@Beanpublic SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) {SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();factoryBean.setDataSource(dataSource);return factoryBean;}@Beanpublic PlatformTransactionManager transactionManager(DataSource dataSource) {return new DataSourceTransactionManager(dataSource);}}
然后我们加载的时候将两个配置都加载
public static void main(String[] args) throws FileNotFoundException {GenericApplicationContext parent = new GenericApplicationContext();AnnotationConfigUtils.registerAnnotationConfigProcessors(parent.getDefaultListableBeanFactory());ConfigurationPropertiesBindingPostProcessor.register(parent.getDefaultListableBeanFactory());parent.registerBean(AppConfig.class);parent.refresh();GenericApplicationContext child = new GenericApplicationContext();AnnotationConfigUtils.registerAnnotationConfigProcessors(child.getDefaultListableBeanFactory());child.setParent(parent);child.registerBean(WebConfig.class);child.refresh();AccountController bean = child.getBean(AccountController.class);bean.transfer(1, 2, 500);}

放在一个容器里面或者各扫各的
原因:
解法:
@Service
public class Service6 {@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)public void foo() throws FileNotFoundException {LoggerUtils.get().debug("foo");System.out.println(this.getClass());bar();}@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)public void bar() throws FileNotFoundException {LoggerUtils.get().debug("bar");}
}

由运行结果结果可知:
事务只开启了一次,并且打印的this的类型不为代理类
总的来说就是要获取代理类来调用被事务增强的方法才可以保证事务不失效
@Service
public class Service6 {@Autowiredprivate Service6 proxy;@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)public void foo() throws FileNotFoundException {LoggerUtils.get().debug("foo");System.out.println(proxy.getClass());
// System.out.println(this.getClass());proxy.bar();}@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)public void bar() throws FileNotFoundException {LoggerUtils.get().debug("bar");}
}

由运行结果可知,事务开启了两次,并且打印的也是代理对象
另一种方法同理,只不过要在配置时加上@EnableAspectJAutoProxy(exposeProxy = true),暴露对象才可以获取
((Service6) AopContext.currentProxy()).bar();
原因:
[insert、update、delete、select] ... for update 语句,select方法并不阻塞CountDownLatch latch = new CountDownLatch(2);
new MyThread(() -> {bean.transfer(1, 2, 1000);latch.countDown();}, "t1", "boldMagenta").start();new MyThread(() -> {bean.transfer(1, 2, 1000);latch.countDown();}, "t2", "boldBlue").start();latch.await();System.out.println(bean.findBalance(1));

可知数据库的数据已经出现了问题
synchronized 范围应扩大至代理方法调用

使用 select ... for update 替换 select
select balance from account where accountNo=#{accountNo} for update
原因:
解法:
select ... for update 替换 select