注:
大家好我是妈妈的好大儿,
笔者联系方式
QQ:3302254385
微信:yxc3302254385
交个朋友!
创作不易,三连十分感谢!!!
为什么会去写这样的一篇博客?
很多面试官在面试的时候都会去问到!
- 数据库的事务!!事务是什么东西?
- 事务的隔离级别
- ACID原则
- spring事务的实现方式
- Spring事务的传播特性!!
- 等等等!!
对于这些面试题相信大家都已记得滚瓜烂熟了!!!基本上80%都会被问道!!!在工作中也是100%会用到我们的事务!!!
可是你真的理解,事务吗?spring是怎样去实现事务!!去拓展出事务的传播特性呢??
案列演示 正常情况和疑问点
正常情况:
@Service
public class TestService {
@Resource
private ArticleDirectoriesMapper articleDirectoriesMapper;
/**
* 插入一个文章目录
*/
@Transactional
public void methodA(){
ArticleDirectoriesDO articleDirectoriesDO = new ArticleDirectoriesDO().setArticleId(111L);
articleDirectoriesMapper.insertOneLevel(articleDirectoriesDO);
methodB();
System.out.println(1/0);
}
/**
* 插入一个文章目录
*/
public void methodB(){
ArticleDirectoriesDO articleDirectoriesDO = new ArticleDirectoriesDO().setArticleId(222L);
articleDirectoriesMapper.insertOneLevel(articleDirectoriesDO);
}
}
相信大家经常会编写这样的代码,使用默认的spring提供的事务的传播特性(加入当前正要执行的事务不在另外一个事务里,那么就起一个新的事务。默认情况下,参与的事务将加入外部作用域的特征,而忽略本地隔离级别),A方法和B方法将作为一个整体进行事务的提交与回滚!!!!
产生疑问的情况:
@Service
public class TestService {
@Resource
private ArticleDirectoriesMapper articleDirectoriesMapper;
/**
* 插入一个文章目录
*/
@Transactional
public void methodA(){
ArticleDirectoriesDO articleDirectoriesDO = new ArticleDirectoriesDO().setArticleId(111L);
articleDirectoriesMapper.insertOneLevel(articleDirectoriesDO);
methodB();
System.out.println(1/0);
}
/**
* 插入一个文章目录
*/
//此时的事务隔离级别为 (以非事务方式执行,如果存在事务则抛出异常)
@Transactional(propagation = Propagation.NEVER)
public void methodB(){
ArticleDirectoriesDO articleDirectoriesDO = new ArticleDirectoriesDO().setArticleId(222L);
articleDirectoriesMapper.insertOneLevel(articleDirectoriesDO);
}
}
这里我们设置了方法B的事务的隔离传播特性为(以非事务方式执行,如果存在事务则抛出异常)
但是调用完A方法后,B方法插入的数据也回滚了!!! 代表A方法和B方法在同一事务,但是这里我们的B方法的传播特性明明是,有事务就抛出异常!!!为什么会失效呢???难道是spring的问题???
问题分析与解决
这里就涉及到Spring的bean的生命周期!!!!
我们在使用xxService的bean对象,执行业务层操作时,如果xxxxService中有事务方法,其实是需要Spring帮我们进行了动态代理的也就是进行Aop,来完成我们的事务执行!!!
所以要保证我们事务的生效,必须使用我们的代理对象!!!
其实在我们使用Spring执行事务的时候,它会在我们的方法上执行,
@Transactional
public void methodA(){
//开启connection
//setAutoCommit(false)设置是否自动提交
ArticleDirectoriesDO articleDirectoriesDO = new ArticleDirectoriesDO().setArticleId(111L);
articleDirectoriesMapper.insertOneLevel(articleDirectoriesDO);
methodB();
System.out.println(1/0);
//提交事务
}
刚刚我们从methodA中调用methodB方法,相当于this.methodB方法,其实它并没有使用Spring的代理对象!!!也就导致我们设置的事务传播特性失效!!!
@Transactional
public void methodA(){
//开启connection
//setAutoCommit(false)设置是否自动提交
ArticleDirectoriesDO articleDirectoriesDO = new ArticleDirectoriesDO().setArticleId(111L);
articleDirectoriesMapper.insertOneLevel(articleDirectoriesDO);
this.methodB();
System.out.println(1/0);
//提交事务
}
为了解决这个事务传播特性设置失效的问题,我们只需要使用代理对象即可!!解决!!!
@Service
public class TestService {
@Resource
private ArticleDirectoriesMapper articleDirectoriesMapper;
//注入自身的代理对象
@Autowired
TestService testService;
/**
* 插入一个文章目录
*/
@Transactional
public void methodA(){
ArticleDirectoriesDO articleDirectoriesDO = new ArticleDirectoriesDO().setArticleId(111L);
articleDirectoriesMapper.insertOneLevel(articleDirectoriesDO);
testService.methodB();
System.out.println(1/0);
}
/**
* 插入一个文章目录
*/
//此时的事务隔离级别为 (以非事务方式执行,如果存在事务则抛出异常)
@Transactional(propagation = Propagation.NEVER)
public void methodB(){
ArticleDirectoriesDO articleDirectoriesDO = new ArticleDirectoriesDO().setArticleId(222L);
articleDirectoriesMapper.insertOneLevel(articleDirectoriesDO);
}
}
看效果:
为标记为传播’never’的事务找到现有事务 ,事务传播特性设置成功!!!
总结
如果理解了,事务是通过spring进行Aop,进行动态代理从而实现的结果!!!那么和面试官谈起来也就更会有谈资而不是一味的死记一些概念!!!毫无意义
其实事务的实现也需要数据库的支持,如Mysql的InnoDB,OLTP类支持事务-----MyISAM,Archive不支持事务,这些是需要数据引擎去实现完成的!!!而并分Spring去做!!!
关于数据库的基础事务知识我们就不在复习了,这里主要就是讲到怎么去理解Spring这个容器,去帮你做了什么事情,应该怎么用才合理,那么我们来简单的复习一遍SpringBean的生命周期
- Spring扫描class得到BeanDefinition
- 根据得到的BeanDefinition去⽣成bean,推断构造方法
- 默认使用无参构造方法
- 通过@Autowired注解可以指定,Spring调用具体的某个构造方法
- 如果有多个构造却没有无参构造报错,如果只有一个有参构造会使用这个有参构造
- 如果构造方法有参数,spring会先通过类型,再通过属性名来自动注入对象
- 根据推断出来的构造⽅法,反射,得到⼀个对象(暂时叫做原始对象)
- 属性填充(如:@Autowired注入其他bean引用)(依赖注⼊)
- 如果原始对象中的某个⽅法被AOP了,那么则需要根据原始对象⽣成⼀个代理对象
- 生成切入点的代理对象
- bean(代理对象)
- afterPropertiesSet(如:@PostConstruct)
- 把最终⽣成的代理对象放⼊单例池(源码中叫做singletonObjects)中,下次getBean时就直接从单例池拿即可