前言
最近在线上环境出现了一个机器总是宕机的问题。就是资源总是耗尽,但是报错的功能点还不一样,每次报错之后都是先重启解决。但是支撑不了一天又会出现资源耗尽的提示。不过,哪个功能出现问题之后,经过仔细的代码检查的之后,还是能发现之前的代码不够严谨,虽然修改了代码,重新上环境之后,发现问题没有根本解决。最后通过查阅了大量的日志文件之后,发现是有一个同事由于使用的ibatis根据表名动态保存数据的方式,采用的是自己手动管理事务的方式,但是在有一个出现异常的地方没有回滚也没有提交,然后这个功能是每天定时根据项目触发的定时任务。每个任务涉及到35张表。所以在一开始并不会立即报错,而是慢慢耗尽资源,导致定位方向出现了偏差。查阅资料后得到如下文章,作为学习记录使用。
简单的事物管理
添加依赖,sprongboot 会默认开启事务管理
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
简单的事物使用
@Autowired
DataSourceTransactionManager dataSourceTransactionManager;
@Autowired
TransactionDefinition transactionDefinition;
//手动开启事务!
TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
/**
业务代码
*/
// 提交
transactionManager.commit(transactionStatus);
// 最好是放在catch 里面,防止程序异常而事务一直卡在哪里未提交
transactionManager.rollback(transactionStatus);
常用的事物注解
一般的事物配置有XML和注解的方式,但是随着spring5的普及,目前使用的比较普遍的还是注解,而且有着越来越广泛的使用的趋势。
- @Transactional
spring 事务注解 - 简单开启事务管理
// 启注解事务管理,等同于xml配置方式的 <tx:annotation-driven />
@EnableTransactionManagement - 事务注解详解
默认遇到throw new RuntimeException("…");会回滚
需要捕获的throw new Exception("…");不会回滚 - 指定回滚
@Transactional(rollbackFor=Exception.class)
public void methodName() {
// 不会回滚
throw new Exception("...");
}
- 指定不回滚
@Transactional(noRollbackFor=Exception.class)
public ItimDaoImpl getItemDaoImpl() {
// 会回滚
throw new RuntimeException("注释");
}
- 如果有事务,那么加入事务,没有的话新建一个(不写的情况下)
@Transactional(propagation=Propagation.REQUIRED)
- 容器不为这个方法开启事务
@Transactional(propagation=Propagation.NOT_SUPPORTED)
- 不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务
@Transactional(propagation=Propagation.REQUIRES_NEW)
- 必须在一个已有的事务中执行,否则抛出异常
@Transactional(propagation=Propagation.MANDATORY)
- 必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反)
@Transactional(propagation=Propagation.NEVER)
- 如果其他bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务
// other失败了不会影响 本类的修改提交成功,本类update的失败,other也失败
@Transactional(propagation=Propagation.SUPPORTS)
public void methodName(){
// 本类的修改方法 1
update();
// 调用其他类的修改方法
otherBean.update();
// 本类的修改方法 2
update();
}
- readOnly=true只读,不能更新,删除
@Transactional (propagation = Propagation.REQUIRED,readOnly=true)
- 设置超时时间
@Transactional (propagation = Propagation.REQUIRED,timeout=30)
- 设置数据库隔离级别
@Transactional (propagation = Propagation.REQUIRED,isolation=Isolation.DEFAULT)
指定事务管理器
spring Boot 使用事务非常简单,首先使用注解 @EnableTransactionManagement 开启事务支持后,然后在访问数据库的Service方法上添加注解 @Transactional 便可。
关于事务管理器,不管是JPA还是JDBC等都实现自接口 PlatformTransactionManager 如果你添加的是 spring-boot-starter-jdbc 依赖,框架会默认注入 DataSourceTransactionManager 实例。如果你添加的是 spring-boot-starter-data-jpa 依赖,框架会默认注入JpaTransactionManager 实例。
你可以在启动类中添加如下方法,Debug测试,就能知道自动注入的是 PlatformTransactionManager 接口的哪个实现类。
这些SpringBoot为我们自动做了,这些对我们并不透明。
- 判断事务管理器
// 启注解事务管理,等同于xml配置方式的 <tx:annotation-driven />
@EnableTransactionManagement
@SpringBootApplication
public class ProfiledemoApplication {
Logger log = LoggerFactory.getLogger(ProfiledemoApplication.class)
@Bean
public Object testBean(PlatformTransactionManager platformTransactionManager){
log.info(">>>>>>>>>>" + platformTransactionManager.getClass().getName());
return new Object();
}
public static void main(String[] args) {
SpringApplication.run(ProfiledemoApplication.class, args);
}
}
- 指定事务管理器
在Spring容器中,我们手工注解@Bean 将被优先加载,框架不会重新实例化其他的 PlatformTransactionManager 实现类。然后在Service中,被 @Transactional 注解的方法,将支持事务。如果注解在类上,则整个类的所有方法都默认支持事务。
@EnableTransactionManagement
@SpringBootApplication
public class ProfiledemoApplication {
Logger log = LoggerFactory.getLogger(ProfiledemoApplication.class)
// 其中 dataSource 框架会自动为我们注入
@Bean
public PlatformTransactionManager txManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean
public Object testBean(PlatformTransactionManager platformTransactionManager) {
log.info(">>>>>>>>>>" + platformTransactionManager.getClass().getName());
return new Object();
}
public static void main(String[] args) {
SpringApplication.run(ProfiledemoApplication.class, args);
}
}
- 指定的事务管理器
// 开启注解事务管理,等同于xml配置文件中的 <tx:annotation-driven />
@EnableTransactionManagement
@SpringBootApplication
public class ProfiledemoApplication implements TransactionManagementConfigurer {
@Resource(name="txManager2")
private PlatformTransactionManager txManager2;
// 创建事务管理器1
@Bean(name = "txManager1")
public PlatformTransactionManager txManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
// 创建事务管理器2
@Bean(name = "txManager2")
public PlatformTransactionManager txManager2(EntityManagerFactory factory) {
return new JpaTransactionManager(factory);
}
// 实现接口 TransactionManagementConfigurer 方法,其返回值代表在拥有多个事务管理器的情况下默认使用的事务管理器
@Override
public PlatformTransactionManager annotationDrivenTransactionManager() {
return txManager2;
}
public static void main(String[] args) {
SpringApplication.run(ProfiledemoApplication.class, args);
}
}
@Component
public class DevSendMessage implements SendMessage {
Logger log = LoggerFactory.getLogger(DevSendMessage .class)
// 使用value具体指定使用哪个事务管理器
@Transactional(value="txManager1")
@Override
public void send() {
log.info(">>>>>>>>Dev Send()<<<<<<<<");
send2();
}
// 否则默认使用方法 annotationDrivenTransactionManager() 返回的事务管理器
@Transactional
public void send2() {
log.info(">>>>>>>>Dev Send2()<<<<<<<<");
}
}
Spring 手动开启事务
- 注入
@Autowired
private DataSourceTransactionManager dstManager
- 设置事物的相关属性
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// 事物隔离级别,开启新事务,这样会比较安全些。
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
// 获得事务状态
TransactionStatus transaction= dstManager.getTransaction(def);
- 提交事务
dstManager.commit(transaction);
- 回滚事务
dstManager.rollback(transaction);
注意
使用手动提交事物的时候,千万千万记得捕获异常,并且在适当的时候进行回滚和提交。千万不要一直占用着事物资源不处理。