导入数据源
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事务才会回滚。
原因:
解法:
@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);
//异常
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();
@Aspect
static 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;
}
}
}
抛出异常或者手动设置事务状态进行回滚
原因:
解决办法:
@Transactional
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);
}
}
@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")
@Bean
public DataSource dataSource() {
return new HikariDataSource();
}
@Bean
public DataSourceInitializer dataSourceInitializer(DataSource dataSource, DatabasePopulator populator) {
DataSourceInitializer dataSourceInitializer = new DataSourceInitializer();
dataSourceInitializer.setDataSource(dataSource);
dataSourceInitializer.setDatabasePopulator(populator);
return dataSourceInitializer;
}
@Bean
public DatabasePopulator databasePopulator() {
return new ResourceDatabasePopulator(new ClassPathResource("account.sql"));
}
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
return factoryBean;
}
@Bean
public 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 {
@Autowired
private 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));
select balance from account where accountNo=#{accountNo} for update
原因:
解法:
select ... for update
替换 select