目录
(3)三级缓存机制:原理:将对象实例化与初始化的两个过程分开
5.BeanFactory 与 FactoryBean 有什么区别
1.某一事务嵌套另外一个事务的时候怎么办?A方法调用B方法,他们都有事务并且传播特性不同。那么A如果有异常,B怎么办?B有异常,A怎么办?
一、Spring基础
1.Spring的优点有哪些?
轻量,基本版本大约只有2MB; |
提供了IOC技术,容易管理依赖的对象,从而不需要自己去创建和管理对象了,更轻松的实现了程序的解耦; |
提供了AOP面型切面编程,更方便去处理某一类问题; |
提供了事务支持,使得操作事务更加方便; |
有更方便的框架集成,可以方便的集成各种优秀的框架。如Hibernate、MyBatis等。 |
2.谈一谈IOC的理解:
IOC是控制反转,同时也是个容器。
(1)控制反转,是Spring的核心,对于Spring框架来说,就是由Spring来负责控制对象的生命周期和对象之间的关系。简单来说,控制指的是当前对象对内部成员的控制权;控制反转指的是这种控制权不由当前对象管理了,由其他(类,第三方容器)来管理。
不同于传统的主动去new操作来获取依赖对象。转而由Spring容器来帮忙创建及注入依赖对象,在这个过程中我们只需要配置好配置文件或者注解。
实现原理的话:IOC 是由依赖注入DI实现的。所谓依赖注入,就是把底层类作为参数传入上层类,实现上层类对下层类的“控制反转”。当我们new对象的时候,它会从最上层开始向下寻找依赖关系,到达最底层后,将底层类作为参数传入上层类,向上递归的new依赖,直到目标对象被创建。
(2)IOC容器是一个存储所有Bean的Map集合。在Spring中有三级map缓存结构,用于解决依赖循环问题;SingletonObjects存放完整的Bean对象。 Bean对象就是:交给Spring容器管理的类称为Bean对象。Bean的创建、销毁均有Spring容器进行控制。
(3)优点:IOC和依赖注入降低了应用程序的代码量;并且实现了松耦合。
自动装配是为了将依赖注入“自动化”的一个简化配置的操作。 当一个对象的属性是另一个对象时,实例化时,需要为这个对象属性进行实例化。这就是装配。自动装配就是开发人员不必知道具体要装配哪个bean的引用,这个识别的工作会由spring来完成。与自动装配配合的还有“自动检测”,这 个动作会自动识别哪些类需要被配置成bean,进而来进行装配。这样我们就明白了,自动装配是为了将依赖注入“自动化”的一个简化配置的操作。
基于xml文件的自动装配:byType(类型),byName(名称), constructor(根据构造函数)
基于注解的自动装配:@Autowired,@Resource,@Value
(1)在Spring框架xml配置中共有5种自动装配:
no:默认的方式是不进行自动装配的,通过手工设置ref属性来进行装配bean。
byName:将属性名与bean名称进行匹配,如果找到则注入依赖bean。如果一个bean的 property 与另一bean的name相同,就进行自动装配。
byType:通过参数的数据类型进行自动装配。
constructor:利用构造函数进行装配,并且构造函数的参数通过byType进行装配。
autodetect:自动探测,如果有构造方法,通过 construct的方式自动装配,否则使用 byType的方式自动装配。
(2)基于注解的自动装配方式:
使用@Autowired、@Resource、@Value注解来自动装配指定的bean。在使用@Autowired注解之前需要在Spring配置文件进行配置。在启动spring IoC时,容器自动装载了一个AutowiredAnnotationBeanPostProcessor后置处理器,当容器扫描到@Autowied、@Resource或@Inject时,就会在IoC容器自动查找需要的bean,并装配给该对象的属性。
@Autowired和@Resource的区别:@Autowired注解是按照byType装备依赖对象的,但是存在多个类型一直的bean,无法通过byType注入时,就会再使用byName来注入,如果还是无法装配注入那个bean则会抛出异常 UnsatisfiedDependencyException。
@Resource会首先按照byName进行装配,如果找不到bean,会自动byType再找一次。
(4)IOC容器的创建过程(底层实现)/或者说Spring的生命周期:
① 底层实现:Map数据结构+工厂模式+依赖注入(DI)+反射
阶段 | 执行 | 延申点 |
Step1 | 通过createBeanFactory()创建BeanFactory工厂类容器 | 用于存放BeanDefinition、Bean等对象 |
Step2 | 向BeanFactory中添加一些参数信息,提供扩展点 | 例如:BeanPostProcessor、Awawre接口等子类属性 |
Step3 | 将xml或者注解解析成BeanDefinition对象(封装类的相关信息) | BeanDefinition存储的有:@Scope、@Lazy、@DependsOn、属性等信息 |
Step4 | BeanFactoryPostProcessor的后置处理,处理后才算得到完整的BeanDefinition对象 | 工厂类的扩展点,例如对占位符处理${jdbc.name}等 |
Step5 | 注册扩展接口BeanPostProcessor,方便Bean初始化的扩展 | AOP |
Step6 | Bean的创建-使用-销毁过程 | Bean的生命周期 |
②Bean的创建过程 Bean的生命周期
阶段 | 执行(背诵) | 延伸点 |
Step1 | 调用createBeanInstance(),基于反射将BeanDefinition实例化Bean对象 | 反射 |
Step2 | 调用populateBean(),完成自定义属性的依赖注入 | 循环依赖(三级缓存) |
Step3 | 调用invokeAwareMethod(),完成容器属性的依赖注入,例如BeanName、BeanFactory、BeanClassLoader | Aware是BeanFactory初始化时设置的 |
Step4 | 调用BeanPostProcessor()前置处理方法,完成对象初始化前的相关处理工作,例如ApplicationContextPostPrecessor(),用于设置ApplicationContext、Enviroment、ResourceLoader等对象 | BeanPostProcessor()是BeanFactory初始化时设置的,扩展点 |
Step5 | 检查是否实现initializingBean接口。如果有,则调用afterPropertiesSet()完成属性设置后的工作 | 扩展点 |
Step6 | 调用invokerInitmethod()完成对象初始化相关工作 | 初始化 |
Step7 | 调用BeanPostProcessor()后置处理方法,完成对象初始化后的扩展工作 | AOP是在此阶段实现的 |
Stpe8 | 注册必要的Destruction回调接口,给Bean对象提供销毁方法 | 销毁接口 |
Stpe9 | 调用getBean()从IOC容器中获取对象 | 使用Bean |
Step10 | 如果实现了DisPosableBean接口,则调用其实现的destroy()方法销毁。否则调用自定义销毁方法。 | 销毁操作 |
以上就是IOC 的整体理解,包含了一些详细的处理。
Bean的生命周期流程:
3.AOP
面向切面编程,就是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。简单来说就是统一处理某一“切面”类问题的编程思想,比如统一处理日志、异常等。可以大大减少系统的重复代码并且降低模板的耦合。
代理对象组成主要是: 切面(织入的额外逻辑)+切入点(被增强的方法)+通知(增强的位置);
原理:(1 )基于反射+公共接口+处理类;
(2)Cglib动态代理:基于ASM字节码生成框架+反射+子类继承拦截父类方法调用
3.1 AOP的实现方式有哪些?
实现AOP主要分为两大类:
(1)静态代理-指使用AOP框架提供的命令进行编译,从而在编译阶段就可生成AOP代理类,因此也称为编译时增强: 编译时编制(特殊编译器实现)、 类加载时编织(特殊的类加载器实现)
(2)动态代理-在运行时在内存中“临时”生成AOP动态代理类,因此也被称为运行时增强。
JDK动态代理:通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvcationHandler接口和Proxy类。
CGLIB动态代理:如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态生成某个子类的子类,注意,CGLIB是通过继承的方式做动态代理的,引入如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
JDK动态代理和CGLIB动态代理的区别:
Spring AOP中的动态代理主要有两种方式:JDK动态代理和CGLIB动态代理。
两者的区别:(1)jdk动态代理使用jdk中的类Proxy来创建代理对象,它使用反射技术来实现,不需要导入其他依赖。cglib需要引入相关依赖:asm.jar,它使用字节码增强技术来实现。 (2)当目标类实现了接口的时候Spring Aop默认使用jdk动态代理方式来增强方法,没有实现接口的时候使用cglib动态代理方式增强方法。
<1>如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP;
<2>如果目标对象实现了接口,可以强制使用CGLIB实现AOP;
<3>如果目标对象没有实现了接口,必须采用CGLIB库。
3.1 .1动态代理失效的几种情况
代理模式 | 描述 |
JDK动态代理失效情况 | 只能代理public修饰的,static和final无法代理 |
Cglib动态代理失效的情况 | 只能代理public和protected修饰的,static和final无法代理 |
应用场景:处理日志、Spring事务(等等)、权限等
代理过程:
步骤 | 描述 |
Step1 | 代理对象的创建前,明确(通知+切面+接入点) |
Step2 | 通过jdk(默认,有接口时使用)或者Cglib动态代理(代理对象没有公共接口时用)的方式实现AOP |
Step3 | 在执行代理时,会调用InvaocationHandle; |
Step4 | 从拦截器中依次获取每一个通知开始执行切入; |
(1)AOP应用举例:Spring声明式事务的实现(原理就是AOP)步骤
阶段 | 执行(背诵) |
Step1 | 配置数据源DataSource。设置事务管理器为数据库事务管理器 |
Step2 | 配置“切面”+“通知advice”+“切点表达式”,以及事务传播机制和隔离级别 |
Step3 | AOP事务采用的实际是环绕通知,通过注解@Transcational实现的,具体步骤如下: |
Step4 | 前置通知@Before:获取数据库连接,并且关闭事务自动提交功能 |
Step5 | 后置通知@After:执行过程中无异常,则提交事务,并关闭数据库连接。提交的具体逻辑是通过doCommit()实现的。 |
Step6 | 异常通知@AfterThrowing,操作失败则事务回滚,回滚的具体逻辑是通过doRollBack()方法实现。 |
Step7 | 当事务执行完毕,清除相关的事务信息。至此,事务结束 |
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
try {
//1、前置通知@Before:获取数据库连接,并关闭事务自动提交功能
conn = JDBCUtil.getConnection();
conn.setAutoCommit(false);
//2、预编译SQL并执行:此处表示接入点(被代理的方法)
ps = conn.prepareStatement("update bank set money = money - 100 where id = 1");
ps.executeUpdate();
//3、后置通知@After:无异常,手动提交事务
conn.commit();
} catch (Exception e) {
e.printStackTrace();
try {
//5、异常通知@AfterThrowing,事务回滚
conn.rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
} finally {
try {
//6、后置通知@After:关闭数据库连接
JDBCUtil.close(ps, conn);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
(2)Spring对AOP的支持
Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由容器负责管理。因此,AOP代理可以直接使用容器中的其它bean实例作为目标,这种关系可由IOC容器的依赖注入提供。Spring创建代理的规则为:
<1>默认使用jdk动态代理来创建AOP代理,这样就可以为任何接口实例创建代理了;
<2>当需要代理的类不是代理接口的时候,Spring会切换为使用Cglib代理,也可以强制使用Cglib。
AOP代理编程 | 描述 |
<1> | 定义普通业务组件 |
<2> | 定义切入点,一个切入点可以横切多个业务组件 |
<3> | 定义增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作 |
所以进行AOP编程的关键:就是定义切入点和定义增强处理,一旦定义了合适的切入点和增强处理,AOP框架将自动生成AOP代理,即:代理对象的方法 = 增强处理 + 被代理对象的方法。
4.Spring是如何解决依赖循环的?高频
(1)循环依赖问问题:
A类属性有B类,B类属性有A类,相互依赖,导致死循环。
(2)循环依赖类型
类型 | 解决方式 |
构造器的循环依赖(无set方法) | 无法解决,会直接抛出异常 |
非单例模式下的循环依赖 | 无法解决 |
单例模式下的setter循环依赖 | 三级缓存机制 |
(3)三级缓存机制:原理:将对象实例化与初始化的两个过程分开
分开的方式就是设定3个Map集合,查找顺序是一二三
缓存 | map | 存储对象 | (key,value) |
一级缓存 | SingletonObjects | 实例化与初始化后,成品Bean对象。用于存放我们可以直接getBean() 获取的对象 | (beanName,成品bean) |
二级缓存 | EarlySingletonObjects | 循环依赖,用于存放从第三级缓存中获取的半成品bean。若存在代理,则从三级缓存中最终获取的是aop代理对象 | (beanName,半成品bean) |
三级缓存 | SingletonFactories | 存放的是lamba表达式(getEarlyBeanReference()方法),函数式接口。寻找依赖时,当一级缓存和二级缓存中都为null,此时若三级缓存singletonFactory!=null,那么会调用singletonFactory.getObject()方法,此方法的作用是:返回目前所需要的依赖的bean半成品对象。并判断,若存在AOP代理,则返回代理对象。否则,返回原对象。 | (beanName,lambda 表达式) |
注意:二级缓存和三级缓存不能同时存在相同的Bean对象。
//一级缓存
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
// 三级缓存
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
(4)案例:以A与B为例,循环依赖创建过程如下:
<1>创建A
阶段 | 执行(背诵) |
---|---|
Step1 | 首先按顺序访问一二三级缓存,尝试从容器中获取A对象。获取不到,将A放到正在创建的 beanName 列表中 |
Step2 | 实例化A对象(通过反射),A a = new A(); 此时 a 对象未初始化,属于半成品 |
Step3 | 将"创建a对象的回调函数式接口lambda表达式 "放入第三级缓存中。等待发生A被循环依赖被调用时,三级缓存才会调用singletonFactory.getObject()生成半成品a对象 ;也就是当被调用的那一刻,才创建了A对象。 |
Step4 | 尝试初始化a对象populateBean() 。此时发现设置属性需要B对象,则按顺序访问一二三级缓存 ,尝试从容器中获取B对象。获取不到,将B放到正在创建的beanName列表中,开始创建b对象 |
<2>创建B
阶段 | 执行(背诵) |
---|---|
Step5 | 实例化B对象(通过反射 ),B b = new B(); 此时 b 对象未初始化,属于半成品 |
Step6 | 将"创建b对象的回调函数式接口lambda表达式(作用等同于createBean())"放入第三级缓存中 。等待发生B发生循环依赖被调用时,三级缓存才会调用singletonFactory.getObject()生成半成品b对象 ;也就是当被调用的那一刻,才创建了B对象 |
Step7 | 尝试初始化b对象populateBean()。此时发现设置属性需要A对象。则顺序访问一二三级缓存,最后在第三级缓存找到了用于创建半成品A对象的lambda表达式。那么调用 singletonFactory.getObject() ,生成半成品a对象(注意:此时若a被代理,则此方法返回的是a的代理对象),接着将a传入二级缓存,并从第三级缓存中移除a的lambda表达式 |
Step8 | b拿到了半成品a对象,初始化完成。接着b对象(成品bean)传入一级缓存,并从三级缓存中移除b的lambda表达式。至此,B的创建过程结束。 |
<3>继续创建A
阶段 | 执行(背诵) |
---|---|
Step9 | 上接 Step4。b被创建完毕,此时a可以从一级缓存中getBean() 获得b对象。则a对象完成b的属性填充,初始化工作完成; |
Step10 | 将成品a传入一级缓存中,并从二级缓存中移除。至此,a和b均创建完成。 |
A没有被代理
A被代理
(5)延申问题:
<1>如果只有一个Map结构,能解决循环依赖吗?
· 如果不存在AOP代理,那么一个Map理论上能解决循环依赖问题
解决循环依赖问题的本质就是将依赖对象的实例化与初始化过程分开,具体到对象上,就是将半成品bean和成品bean区分开,通常用两个Map结构分别储存。但实际上,我们可以通过设置标识符号,存到一个Map中进行存储,但是没必要,操作比较麻烦。因此,用两个Map分别存储即可。
<2>如果只有两个Map结构,能解决循环依赖吗?
· 如果不存在AOP代理,那么两个Map理论上能解决循环依赖问题。这里重点说明为什么存在AOP代理时,两个Map解决不了的问题。
· 在对象创建的过程中,原始的对象可能需要生成代理对象,代理对象的创建是在初始化过程的扩展阶段(Bean的生命周期:执行init-method对象初始化之后)。因此我们在属性赋值时判断是否需要生成代理对象。因此引入了三级缓存,三级缓存中存放的是一个lambda表达式。
· 那为什么要使用lambda表达式(BbjectFactory<?>)呢?因为半成品Bean不能对外随便暴露,而对象在什么时候暴露(即何时被引用)是无法提前确定好的,因此只有在调用的那一刻才能进行原始对象还是代理对象的判断。因此引入了一个lambda表达式,它类似于一个回调机制,不暴露的时候就不需要调用执行,当出现循环依赖的时候,才会真正的执行lambda表达式。起作用是:判断应该返回的是原始对象还是代理对象。
若没有三级缓存,只用一二级缓存时 |
此时若根据类名直接由二级缓存中获取对象的话,获取的是原始对象。二我们想要的是获取代理对象,此时就出现了最终Bean对象不一致问题。所以Spring在类加载过程中,我们将lambda表达式放入三级缓存中,从三级缓存中获取类对象的时候,判断类是否被代理,若被代理就返回代理对象。从而解决AOP+循环依赖下Bean不一致的问题; |
若没有二级缓存,只用一三级缓存时 |
由于三级缓存的ObjectFactory<?>的lambda表达式,singletonFactory.getObject()能够解决AOP动态代理下的循环依赖问题,但是如果没有二级缓存,那么无法保证bean的单例模式了,这是因为singletonFactory.getObject()每次都会生成一个新的半成品对象,如果不放在二级缓存中;那么在多线程下,A线程和B线程都需要对象C的半成品对象,那如果没有二级缓存,若此时C还没有进入一级缓存,那么A线程和B线程都会调用三级缓存的singletonFactory.getObject(),从而生成两个半成品C对象,从而破坏了单例模式;因此,二级缓存必须存在! |
<3>三级缓存解决的是什么问题?
· 三级缓存解决的是存在AOP代理+循环依赖下
最终获取的bean不一致的问题
。
代理对象实例化的时候,实例化对象是原始对象,若没有三级缓存,只有二级缓存时,此时若根据类名直接由二级缓存中获取对象的话,获取的是原始对象。而我们想要的是获取代理对象,此时就出现了最终Bean对象不一致问题。所以Spring在类加载过程中,我们将lambda表达式放入三级缓存中,从三级缓存中获取类对象的时候,判断类是否被代理,若被代理则返回代理对象。至此,解决了AOP代理+循环依赖下Bean不一致的问题。
<4>三级缓存核心代码
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//1、根据BeanName从一级缓存(singletonObjects)中查找bean对象,此一级缓存中都是单例对象。即我们熟知的单例bean工厂
Object singletonObject = this.singletonObjects.get(beanName);
//2、一级缓存(singletonObjects)中没有,判断当前beanName是不是正在创建中。
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
//3、尝试从二级缓存中获取
singletonObject = this.earlySingletonObjects.get(beanName);
//4、二级缓存中没有并且允许暴露
if (singletonObject == null && allowEarlyReference) {
//5、从三级缓存中获取lambda表达式(函数式接口)
ObjectFactory<?> singletonFactory=this.singletonFactories.get(beanName);
if (singletonFactory != null) {
//6、调用getObject()方法,真正的执行lambda表达式中的方法Object earlyBean = getEarlyBeanReference()
//此方法会判断是否存在AOP代理,并返回代理半成品对象或者原始半成品对象
singletonObject = singletonFactory.getObject();
//7、将半成品bean放入二级缓存中
this.earlySingletonObjects.put(beanName, singletonObject);
//8、将半成品bean从三级缓存中删除
this.singletonFactories.remove(beanName);
}
}
}
}
//返回bean对象
return singletonObject;
}
<5>从二级缓存中获取到了,就删除二级缓存,传入一级缓存
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
//一级缓存中没有
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
//从二级缓存中删掉,并放入一级缓存
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
<5>三级缓存调用lambda表达式生成半成品Bean对象
当三级缓存调用,singletonObject = singletonFactory.getObject()
时执行
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
//1、将原始半成品bean暴露出来
Object exposedObject = bean;
//2、判断是否实现了Aware接口以及一些扩展点
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
//3、检查是否存在AOP代理
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
//4、将暴露的原始半成品bean替换成经过代理的半成品bean
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
//返回给三级缓存,即本文的上一个方法
return exposedObject;
}
每一级缓存的方式时间和删除时间
缓存 | 放置 | 删除 |
三级缓存 | 实例化createBeanInstance之后 | 第一次从三级缓存调用getObject(),触发函数式接口时 |
二级缓存 | 三级缓存删除时 | Bean对象实例化和初始化完成后 |
一级缓存 | 二级缓存删除时 | 直到Bean被销毁,否则不删除 |
5.BeanFactory 与 FactoryBean 有什么区别
类型 | 作用 | 区别 |
BeanFactory | 创建Bean对象 | 使用BeanFactory创建对象时,必须要遵循严格的生命周期流程; |
FactoryBean | 创建Bean对象 | 简单的自定义某个对象的创建,同时创建完成的对象想交给spring来管理,那么就需要实现FactoryBean接口 FactoryBean中有三个方法: (1)isSingleton(): 是否是单例对象; (2)getObjectType():获取返回对象的类型; (3)getObject():自定义创建对象的过程(new, 反射,动态代理) |
二、Spring事务相关
1.某一事务嵌套另外一个事务的时候怎么办?A方法调用B方法,他们都有事务并且传播特性不同。那么A如果有异常,B怎么办?B有异常,A怎么办?
答:Spring事务有7种传播特性。主要分为三类,支持当前事务、不支持当前事务、嵌套事务
事务类型 | 传播级别 |
---|---|
支持当前事务 | Mandatory、Required、Support |
不支持当前事务 | Never、Required_New、Not_Support |
嵌套事务 | Nested |
(1)具体处理逻辑是这样的:首先判断内外两次的事务是不是一个。<1>是的话,那么异常统一在外层处理;<2>不是的话,内层也许会影响到外层。但是外层不会影响到内层。内外两层是相互独立的。(外层可以选择是否catch内层)
(2)NESTED和REQUIRED_NEW的区别:
类型 | 区别 |
REQUIRED_NEW | REQUIRED_NEW是新建一个事务并且新开始的这个事务与原本事务无关。原事务回滚,新开启的事务不受影响,不会归滚。 |
NESTED | NESTED则是当前存在事务时会开启一个嵌套事务。父事务回滚时,子事务也会回滚。 |
(3)NESTED和REQUIRED的区别:
类型 | 区别 |
REQUIRED | REQUIRED情况下,调用方存在事务时,则被调用方和调用方使用同一个事务,那么被调用方出现异常,由于共用一个事务,所以无论是否catch异常,事务都会回滚 |
NESTED | 被调用方发生异常时,调用方可以catch其异常,这样只有子事务回滚,父事务不会回滚 |
支持当前事务的三种:Mandatory、Required、Support
解释:A和B,A调用B,①A有事务的话就用A的,没有就报错;②A没有事务,那么B创建一个;③使用A的事务,没有的话就不用事务了
不支持当前事务的三种:Never、Required_New、Not_Support
解释:A和B,A调用B,①A有事务的话就报错;②A有事务,那么B挂起它;③A有事务,那么B挂起它,并且新建一个事务;
2.Spring隔离级别有哪些?
隔离级别 | 问题 | 概述 | 原理 |
---|---|---|---|
读未提交 | 脏读、幻读、不可重复读、第二类更新丢失 | 可以读到其它事务没有提交的执行结果 | |
读已提交 | 幻读、不可重复读、第二类更新丢失 | 只能读到其它事务提交后的执行结果 | MVCC机制: 每次快照读都会创建一个新的Read View |
可重复读 | 幻读、第二类更新丢失 | 支持读已提交,可以重复读取相同的数据(Mysql默认) | MVCC机制: Read View只会在第一次快照读的时候创建,后续的快照读都是读的第一个Read View |
可串行化 | 无 | 支持可重复读,最高隔离级别(InnoDB分布式事务下默认) | 事务排序 + 共享锁,解决幻读 |
在进行配置的时候,如果数据库和spring代码中的隔离级别不同,那么以spring的配置为主
3.Spring事务失效的几种情况?
情况 | 描述 |
---|---|
情况1 | Bean对象没有被spring容器管理 |
情况2 | 方法的访问修饰符不是public |
情况3 | 异常被捕获,@Transantional感知不到异常就不会回滚,而是提交 |
三、注解
1.什么是注解?
注解Annontation是JDK5引入的一种新特性。它提供了一种安全的类似注释的机制,用来将任何的信息或元数据与程序元素(类、方法、成员变量)进行关联。为程序的元素加上更直观明了的说明,这些说明是与程序的业务逻辑无关,并且供指定的工具或框架使用。
2.注解的作用?
java注解的主要作用之一,就是跟踪代码依赖性,实现替代配置文件功能。比较常见的是Spring等框架中的基于注解配置。现在的框架很多都使用了这种方式来减少配置文件的数量。基本上秉持着这么一个原则,与具体场景相关的配置应该使用注解的方式与数据关联,与具体场景无关的配置放于配置文件中。