目录
5、BeanFactory和FactorybEean的区别(面试题)
一、Spring启示录
1、OCP开闭原则
OCP是软件七大开发原则当中最基本的一个原则:开闭原则
对什么开? 对扩展开放。
对什么闭? 对修改关闭。
OCP原则是最核心的,最基本的,其他的六个原则都是为这个原则服务的。
OCP开闭原则的核心是什么?
只要你在扩展系统功能的时候,没有修改以前写好的代码,那么你就是符合0CP原则的。反之,如果在扩展系统功能的时候,你修改了之前的代码,那么这个设计是失败的,违背OCP原则。
当进行系统功能扩展的时候,如果动了之前稳定的程序,修改了之前的程序,之前所有程序都需要进行重新测试。这是不想看到的,因为非常麻烦。
2、依赖倒置原则(DIP原则)
面向接口编程,面向抽象编程,不要面向具体编程。
依赖倒置原则的目的?
降低程序的耦合度,提高扩展力。
什么叫做符合依赖倒置?
上 不依赖 下,就是符合。
什么叫做违背依赖倒置?
上 依赖 下,就是违背。
只要"下”一改动,"上"就受到牵连
当前程序的设计,显然既违背OCP,又违背DIP,怎么办?
可以采用“控制反转"这种编程思想来解决这个问题。
3、控制反转(重点)
控制反转IOC(Inversion of Control)
反转的是什么?
- 我不在程序中采用硬编码来new对象了。(new对象不管了,new对象的权利交出去)
- 不在采用硬编码的方式来维护对象的关系了。(对象之间关系的维护期也交出去)
4、Spring框架(重点)
Spring框架实现了控制反转IOC这种思想
- spring框架可以帮你new对象
- spring框架可以帮你维护对象和对象之间的关系
控制反转的实现方式有多种,其中比较重要的叫做:依赖注入(Dependency Injection,简称DI)。控制反转是思想。依赖注入是这种思想的具体实现。依赖注入DI,又包括常见的两种方式:
- set注入(执行set方法给属性赋值)
- 构造方法注入(执行构造方法给属性赋值)
依赖注入 中“依赖"是什么意思?“注入”是什么意思?
- 依赖:A对象和B对象的关系。
- 注入:是一种手段,通过这种手段,可以让A对象和B对象产生关系。
- 依赖注入:对象A和对象B的关系,靠注入的手段来维护
二、Spring概述
Spring的8大模块
Spring特点
1、轻量
从大小与开销两方面而言Spring都是轻量的。完整的spring框架可以在一个大小只有1MB多的JAR文件里发布。并且Spring所需的处理开销也是微不足道的。
Spring是非侵入式的: Spring应用中的对象不依赖于Spring的特定类。不用依赖其他类
2、控制反转IOC
Spring通过一种称作控制反转loC的技术进了松拥合。当应用了loc,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。你可以认为ioc与IND相反一不是对象从容器中查找依赖,而是容器在对象视始化时不等对象请求就主动将依赖传递给它。
3、面向切面AOP
Spring提供了面向切面编的丰富支持,允许通过分离应用的业务逻与系统级务(例如计 auditing) 和务tansacton)管理)进行内聚性的开发。应用对象只实现它们应该做的--完成业务逻辑一仅此而已。它们并不负责(其至是意识)其它的系统级关注点,例如日志或事务支持。
4、容器
Spring包含管理应用对象的配置和生命周期,在这个意义上它是一种容器,你可以配置你的每bean如何被创建一一基于一个可配置原型(prototype),你的a.bean可以创建一个单独的实例或者每次需要时都生成一个新的实例--以及它们是如何相与关联的。然而,Spring不应该被混同于传统的重量级的EJB容器,它们经常是庞大与笨重的,难以使用。
5、框架
Spring可以将简单的组件配置、组合成为复杂的应用。在Spring中,应用对象被声明式地组合,典型地是在一个XML文件里。Spring 也提供了很多基础功能(事务管理、持久化框架集成等等),将应用逻辑的开发留给了你。
三、Spring入门程序
spring的bean是不能重复的
spring默认会通过反射机制,调用类的无参构造方法来实例化对象。
spring的核心配置文件可以配置多个,只要ClassPathXmlApplicationContext扫描到就行,可以传入多个参数。
spring在解析配置文件的时候就已经创建对象了
四、Spring对IOC的实现
1、IOC控制反转
2、依赖注入
(1)set注入
set注入的核心实现原理:通过反射机制调用set方法给属性赋值,让两个对象之间产生关系
(2)构造注入
构造注入是在对象实例化过程中注入的
3、set注入专题
(1)注入外部Bean
外部bean就是bean定义在外面,然后引用进去
(2)注入内部bean
在bean标签内部嵌套bean标签就是内部bean,这种方式很少用
(3)注入简单类型
简单类型:八种基本类型及包装类、enum、字符串、Date、uri、url、Locale、Class
实际开发中不会给Date当简单赋值,一般用ref,因为用value必须用他这个格式,很难记
实际开发中简单类型的经典案例:
(4)注入数组
(5)注入list集合、set集合
(6)注入Map集合、properties
4、P命名空间注入
p命名空间注入底层还是set注入,只不过p命名空间注入可以让spring配置变得更简单(我们发现把set方法注释掉就会报错)简化set注入的方式
5、c命名空间注入
本质上就是简化构造方法注入的,是基于构造方法的,所以构造方法必须有
6、基于XML的自动装配
Spring可以自动化注入,被称为自动装配。可以根据名字进行自动装配,也可以根据类型自动装配
(1)根据名字进行自动装配
(2)根据类型进行自动装配
7、引入外部的属性配置文件
五、bean的作用域
1、bean单例和多例(重点)
Spring默认情况下bean是单例的,在Spring上下文初始化的时候实例化(new ClassPathXmlApplicationContext)每次调用getBean都返回那个单例对象。
这里可以设置是否用单例,默认为单例singleton。如果手动设置为prototype就会让bean变成多例的,spring上下文初始化的时候不会初始化这些prototype的bean,而是每次调用getBean方法的时候实例化bean对象
2、scope的其他选项
当引入web框架之后,scope就会多两个选项。
其实还有很多选项的
六、工厂模式
1、工厂模式的三种形态
- 简单工厂模式 :不属于23种设计模式之一。(简单工厂模式又叫做: 静态工厂方法模式。简单工厂模式是工厂方法模式的一种特殊实现)
- 工厂方法模式 : 是23种设计模式之一。
- 抽象工厂模式 : 是23种设计模式之一。
2、简单工厂模式
简单工厂模式的角色包括三个:
- 抽象产品
- 具体产品
- 工厂类
抽象产品:
具体产品:
工厂类:
消费者消费:
优点:客户端不需要关心对象的创建过程,需要哪个对象直接向工厂索要即可,初步实现职责分离。客户端只负责“消费”,生产端只负责“生产”。
缺点:假设现在需要扩展一个新的产品,工厂类的代码是要修改的,违背了OCP原则。工厂类的责任重大,不能出现任何问题,因为这个工厂类负责所有产品的生产,成为全能类,工厂类一旦出现问题,整个系统必然全部瘫痪。(全部鸡蛋放到一个篮子里)
3、工厂方法模式
一个工厂对应一种产品,这样就解决工厂全能类的问题了,也符合OCP原则
角色:
- 抽象产品角色
- 具体产品角色
- 抽象工厂角色
- 具体工厂角色
抽象工厂类
具体工厂类(一个产品对应一个工厂):
优点:当你要扩展的时候,符合OCP原则,只要添加两个类具体类就行,都是添加类,不用修改之前的代码。(还有简单工厂模式的优点都有)
缺点:每次增加一个类,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加。在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖
七、Bean的实例化方式(重点)
Spring为Bean提供了多种实例化方式,通常包括4种方式 (Spring中为Bean对象的创建准备了多种方案,目的是: 更加灵活)
- 通过构造方法实例化
- 通过简单工厂模式实例化
- 通过factory-bean实例化
- 通过FactoryBean接口实例化
1、通过构造方法实例化
2、简单工厂模式实例化
(1)编写一个实体类
(2)编写工厂类
(3)在spring配置文件中配置
(4)test类中测试
3、通过factory-bean(工厂方法模式)实例化
这次调用的不是简单工厂的静态方法,而是实例方法,需要实例化对象之后才能调用(需要先把工厂实例化出来才能调用方法)
4、通过FactoryBean接口实例化
以上的第三种方式中,factory-bean是我们自定义的,factory-method也是我们自己定义的。
在Spring中,当你编写的类直接实现FactoryBean接口之后factory-bean不需要指定了,factory-method也不需要指定了。
factory-bean会自动指向实现FactoryBean接口的类,factory-method会自动指向getObject0方法。
(所以第四种就是第三种演变而来的,可以简化第三种的配置)
实现他的接口,重写他的方法就可以了,最下面那个是单例的方法,true就为单例
5、BeanFactory和FactorybEean的区别(面试题)
BeanFactory是spring ioc容器的顶级对象,被翻译为bean工厂,bean工厂负责创建bean对象
ClassPathXmlApplicationContext就是它的子子子类
FactoryBean它是一个Bean,是一个辅助Spring实例化其他Bean对象的一个Bean
在Spring中,bean可以分类两类bean:普通bean、工厂bean(辅助spring实例化其他bean)
6、注入自定义Date
用一个工厂bean来辅助date的赋值
八、Bean的生命周期(重点)
1、Bean的生命周期之5步(重点)
Bean的生命周期可以粗略的分为以下5步
- 实例化Bean (调用无参数构造方法)
- 给Bean届性赋值 (调用set方法)
- 初始化Bean (会调用Bean的init方法。注意: 这个init()方法需要自已写,自己配)
- 使用Bean
- 销毁Bean (会调用Bean前destroy方法。注意: 这个destroy()方法需要自己写,自己配)
2、Bean的生命周期之7步(重点)
在5步,第三步是初始化Bean,如果你想在bean初始化前和之后添加代码,可以加入"Bean后处理器",编写一个实现BeanPostProcessor类,并重写before和after方法
- 实例化Bean
- Bean属性赋值
- 执行"Bean后处理器"的before方法。
- 初始化Bean
- 执行"Bean后处理器"的after方法
- 使用Bean
- 销毁Bean
3、Bean的生命周期之10步(重点)
Bean生命周期十步: 比七步添加的那三步在哪里?
- 在"Bean后处理器"before方法之前点位
- 在"Bean后处理器"before方法之后点位
- 使用Bean之后,或者说销毁Bean之前
本质上就是检查是否实现了某个特定的接口,是就调用接口的方法
4、Bean的作用域不同管理方式不同
Spring容器只对单例的bean进行完整的生命周期管理
如果是prototype作用域的Bean,Spring只负责将bean的初始化完毕,等客户端程序一旦获取到bean之后,spring不再管理该对象的生命周期。只管理一部分的生命周期(只负责前8步,使用Bean之后就不管了)
5、自己new的对象如何让Spring管理
九、Bean的循环依赖问题(重点)
1、什么是循环依赖
A对象中有B属性,B对象中有A属性,这就是循环依赖,我依赖你,你依赖我
比如:丈夫类和妻子类,hasband有wife的引用,wife类中有hasband的引用
2、单例下set注入产生的循环依赖
这种单例下的set注入是不会产生循环依赖问题的,当我们丈夫类在实例化的时候会要用到wife类,我们没有,然后顺便实例化wife类然后赋值,然后到我们实例化wife类的时候,因为是单例的已经有了不再实例化,用到的丈夫类也是一样。
单例和set模式下,spring是如何解决循环依赖的?
主要是,在这种模式下Spring对Bean的管理主要分为清晰的两个阶段:
- 在spring容器加载的时候,实例化Bean,只要其中任意一个Bean实例化之后,马上进行"曝光"(不等属性赋值就先曝光)
- Bean曝光之后,再进行赋值(调用set方法)
注意:只有在scope为单例的模式下,才会采用提前曝光的措施
3、多例下set注入产生的循环依赖
这种情况就会出现异常,当两个bean的scope都是prototype才会出现异常,一个单例,一个prototype是不会出现的。
4、构造注入的循环依赖
这种情况下产生的循环依赖也是无法解决的。
5、Spring解决循环依赖的机理(重点)(面试题)
Spring为什么可以解决set + singleton模式下循环依赖? 别的模式都解决不了
根本的原因在于:这种方式可以做到将“实例化Bean”和“给Bean属性赋值”这两个动作分开去完成。实例化Bean的时候:调用无参数构造方法来完成。此时可以先不给属性赋值,可以提前将该Bean对象“曝光”给外界。给Bean属性赋值的时候: 调用setter方法来完成。
两个步骤是完全可以分离开去完成的,并且这两步不要求在同一个时间点上完成
也就是说,Bean都是单例的,我们可以先把所有的单例bean实化出来,放到一集合当中(我们可称之为缓存)所有的单Bean全部实例化完之后,以后我们再调用set给属性赋值。
底层:先创建bean对象,对象的属性是空,提前曝光工厂(往3级缓存里面放工厂对象),获取单例对象,先从一级缓存中取,如果没有就找二级缓存,如果还是没有就去三级缓存找工厂,通过工厂getObject拿到单例bean之后放回二级缓存,然后清空三级缓存。
十、回顾反射机制
十一、Spring IOC注解开发(重点)
注解存在是为了简化xml的配置,spring6倡导全注解开发
1、回顾注解
这个注解只能出现在类上,不能出现在属性值上面
通过反射机制读取注解
当只给一个包,包下有的类有注解,有的没有,要把有注解的都加入map里,怎么做?
2、声明Bean注解
负责声明Bean的注解,常见的包括四个:
- @Component
- @Controller
- @Service
- @Repository
上面的是老大,下面3个其实都是第一个的别名,分这么多是为了增加程序的可读性。
3、Spring注解的使用
- 加入aop的依赖
- 在配置文件中添加context命名空间
- 在配置文件中指定扫描的包
- 在Bean类上使用注解
小细节:如果像下面这样不指定bean的名字,那么默认会类名第一个字母小写
如果有多个包怎么办?
直接扫多个包 或 扫他们的父类包(但这样效率低)
4、选择性实例化Bean
假如某个包下有很多Bean,有的Bean是Component,有的是Controller注解,有的是Service等,现在只允许所有的Controller参与Bean管理,其他都不实例化,要怎么弄?
第一种(全部失效,包含的生效):
第二种(全部生效,排除掉不生效的):
5、负责注入的注解(重点)
用了之前的四个注解可以声明Bean,声明后就会被实例化。接下来,我们就要给这些Bean赋值
给bean赋值用到的注解:
- @Value
- @Autowired
- @Qualifier
- @Resource
(1)@Value注解
@Value用来注入简单类型的
之前用xml来注入的必须依赖属性的set方法来注入,现在@Value不提供set方法也可以注入。
@value注解还可以提供在set方法上
甚至还可以直接放在构造方法上
(2)@Autowired与@Qualifier
@Autowired注解可以用来注入非简单类型,被翻译为:自动连线的,或自动装配。
单独用@Autowired注解,默认根据类型装配(byType)
@Autowired与@Qualifier一起用才能根据名字来装配
如果有多个类同一个类型(而且他们都被spring管理了),那么这样单独使用autowired按类型注入就会报错,要按照名字装配(@Autowired与@Qualifier一起用才能根据名字来装配)
下图,虽然Autowired在特定情况可以省略,但最好别省
(3)@Resource注解
@Resource也可以完成非简单类型注入,和@Autowired区别?
- @Resource注解是JDK扩展包中的,也就是说属于JDK的一部分。所以该注解是标解,更加具有通用性,(SR-250中制定的注超类型、JSR是Java规带提案。)
- @Autowired注解是Spring框架自己的
- @Resource注解默认根据名称装配byName,未指定name时,使用属性名作为name。通过name找不到的话会自动启动通过类型byType装配
- @Autowired注解默认根据类型装配byType,如果想根据名称装配,需要配合@Qualifier注解一起用
- @Resource注解用在属性上、setter方法上
- @Autowired注解用在属性上、setter方法上、构造方法上、构造方法参数上
6、全注解开发
我们每次都要写xml配置文件,太麻烦了,能不能不用xml,做到全注解开发
能不能编写一个类代替spring的配置文件,可以:
十二、代理模式(重点)
代理模式在代码实现上有两种形式:静态代理和动态代理
1、静态代理模式
现在有个需求,要把项目中原本的所有类加上统计执行时间的功能
解决方案一:直接在原来每个方法中添加代码统计时间的代码。这会违背OCP原则改动原有代码,所有类都要重新测试一遍,而且每个类都要添加这个代码,没有做到代码复用
解决方案二:编写业务类的子类,让子类继承业务类,然后重写方法加统计时间的代码。虽然解决了OCP,但是采用继承关系,偶尔度非常高,而且代码还是没有得到复用。
解决方案三:代理模式。
公共接口:
目标对象:
代理对象:
继承了目标类的同一个接口,实现方法,然后在方法增加功能,再调用原来的方法。
这种方式解决上面两种的缺点,但是这个是静态代理,缺点是每次代理都要重新写一个代理类,太多了就会导致类爆炸。
2、动态代理模式
在程序运行阶段,在内存中动态生成代理类,被称为动态代理。
(1)JDK动态代理(重点)
首先还是要写上公共的接口和目标类,但是代理类不用写了,用JDK自动生成:
动态代理需要设置动态代理对象
newProxyInstance 翻译为: 新建代理对象
也就是说,通过调用这个方法可以创建代理对象本质上,这个Proxy.newProxyInstance()方法的执行,做了两件事
- 第一件事:在内存中动态的生成了一个代理类的字节码class
- 第二件事: new对象了。通过内存中生成的代理类这个代码,实例化了代理对象
第三个参数用来写增强代码的:
但是这样写完我们发现有了增强代码,但原本目标对象方法的功能却没有了,这时候我们就要学习invoke方法的三个参数了。
invoke 方法是JDK负责调用的,所以JDK 调用这个方法的时候会自动给我们传过来这三个参数我们可以在invoke 方法的大括号中直接使用。
- 第一个参数: object proxy 代理对象的引用。这个参数使用较少。
- 第二个参数: Method method 目标对象上的目标方法。 (要执行的目标方法就是它。)
- 第三个参数: 0bject[] args 月标方法上的实参。
这时候我们就可以利用invoke方法的参数来调用目标对象的方法,target可以通过构造方法传
最后测试代码
注意:一定要记得把目标对象的目标方法执行结果返回
如果觉得写JDK的newProxyInstance太麻烦了,可以提取工具类封装起来:
(2)CGLB动态代理(了解)
JDK的动态代理只能代理接口,而CGLB的动态代理可以代理接口也可以代理类,CGLB底层采用继承的方式实现,所以被代理的目标类不能使用final修饰。
十三、面向切面编程AOP(重点)
1、AOP介绍
AOP:面向切面编程(AOP是一种编程技术)
一般一个系统都会有系统服务,例如:日志、事务、安全等。这些系统服务称交叉业务
这些交叉业务几乎通用,不管什么项目什么模块,都是需要的。(面试的时候要说交叉业务)
- 交叉业务代码在多个业务流程中反复出现,显然这个交叉业务代码没得到复用,如果修改的话,多处都需要修改
- 程序员无法专注核心业务代码的编写,在编写核心业务还需要处理这些交叉业务
将与核心业务无关的代码独立抽取出来,形成独立的组件,然后横向交叉的方式应用到业务流程当中的过程称为AOP
Spring的AOP使用的动态代理是:JDK动态代理+CGLIB动态代理技术。Spring在这两种动态代理中灵活切换,如果是代理接口就默认用JDK动态代理,如果是代理某个类,这个类没有接口实现,就用CGLIB。当然,也可以配置让Spring只用CGLIB(面试可能会问)
AOP优点:
- 代码复用性增强
- 代码易维护
- 使开发者更关注业务逻辑
2、AOP的七大术语
连接点(Joinpoint):可以织入切面的位置。方法执行前后,异常抛出之后等位置。
切点(pointcut):真正织入切面的方法(一个切点对应对个连接点)
通知(Advice):通知又叫增强,就是增强的代码。
- 放到方法前叫前置通知
- 放在方法后叫后置通知
- 方法前后都放叫环绕通知
- 如果程序有异常,放到catch里面叫异常通知
- 放到finally叫最终通知
切面(Aspect):切点+通知
织入Weaving:把通知应用到对象的过程
代理对象Proxy:一个目标对象被织入后产生的新对象
目标对象Target:被织入通知的对象
3、切点表达式
切点表达式用来定义通知往哪些方法上切入
execution([访问控制权限修饰符] 返回值类型 [全限定类名]方法名(形式参数列表) [异常])
访问控制权限修饰符:可选填,没写就是4个权限都包括,写public表示只包括公开的方法
返回值类型:必须填,* 表示返回值类型任意
全限定类名:可选项,两个点 .. 代表当前包以及包下所有类,省略时表示所有的类
方法名:必填项,*表示所有方法,set*表示所有set方法
形式参数列表:必填
- ()表示没参数的方法
- (..)表示参数类型和个数随意的方法
- (*)表示只有一个参数的方法
- (*,String)第一个参数任意类型,第二个String=
异常:可选,省略表示任意异常类型
4、SpringAOP的使用
Spring的AOP实现包括3种方式:
- Spring框架结合AspectJ框架实现的AOP,基于注解方式(推荐用)(重点)
- Spring框架结合AspectJ框架实现的AOP,基于XML方式
- Spring框架自己实现的AOP,基于XML配置方式(大家都不用这种方式,了解)
什么是AspectJ?
是Eclipse组织的支持AOP的框架,是独立于Spring之外的框架,Spring框架用了AspectJ
(1)准备工作
先引入Spring的context依赖和Spring-aspects的依赖
然后核心配置文件中添加context和aop的明命名空间
(2)注解实现步骤
- 前置通知@Before
- 后置通知@AfterReturning
- 环绕通知@Around
- 异常通知@AfterThrowing
- 最终通知@After
先试试怎么用,用前置来演示
proxy-target-class=“true”表示强制使用CGLIB动态代理,不写默认为false,表示混合用(接口使用JDK动态代理,反之使用CGLIB动态代理)
测试:
我们再来试试各种通知怎么用和循序
得出:环绕的范围是最大的,先前环绕再前置,先后置再后环绕
如果加上最终也是后环绕最大
发送异常的时候,后置和后环绕都不会执行
(3)切面的排序
用@Order(数字) 这个注解来排序
比如在安全校验的模块上面写order(1),通知的切面写order(2),那么会先执行安全的切面代码
(4)通用切点
我们可以像这样把通用的切点提取出来,这样以后只用写一遍修改也好修改。
甚至跨类的时候都可以使用:
(4)全注解开发
我们不想写xml核心配置文件,我们就想全注解开发(现在开发都是这种方式)
我们就要专门写个核心配置类config来配置文件
我们的测试类也要方式改变:
(5)Xml方式的实现(了解)
5、AOP实际案例
(1)事务
项目中事务在所难免,需要执行多条DML语句,为了保证数据安全,要么同时成功要么同时失败,这就需要添加事务控制的代码。如下
控制事务的代码是和业务逻辑没有关系的交叉业务,要让这些交叉业务代码得到复用,而且要易于改动,那么我们就可以采用AOP,可以把事务代码作为环绕通知,切入到目标类的方法中。
(2)安全日志
项目已经上线,运行正常,新需求:凡是系统中增删改操作都要把这个人记录下来
十四、Spring事务的支持(重点)
1、事务概述
什么是事务
在一个业务中,通常需要多条增删改语句共同联合完成,这么多条增删改必须同时成功或失败,这样才能保证数据安全。要么同时成功,要么同时失败,就叫事务Transaction
事务四个处理过程
- 开启事务start
- 执行核心业务代码
- 如果执行过程中没有出现异常,提交事务commit
- 如果执行业务代码过程出现异常,回滚事务rollback
事务四个特性
- 原子性:事务是最小的工作单元,不可再分
- 一致性:事务要求要么同时成功,要么同时失败,事务前和事务后总量不变
- 隔离性:事务和事务之间因为有隔离性,才能保证互不干扰
- 持久性:持久性是事务结束的标志
Spring实现事务两种方式
编程式事务:通过编写代码的方式来实现事务(一般不用这种)
声明式事务:基于注解方式、基于xml配置方式
Spring事务管理API
Spring对事务的管理底层实现方式是基于AOP实现的,采用AOP的方式进行封装,所以Spring专门针对事务开发了一套API
PlatformTransactionManager接口:spring事务管理器的核心接口,在Spring6中它有两个实现:
- DataSourceTransactionManager:支持jdbcTemplate、mybatis等事务管理
- JtaTransactionManmger:支持分布式事务管理
写个实现接口的实现类就能被spring帮助管理事务
声明式事务之注解实现
要在核心配置文件配置事务管理器,开启事务注解驱动器,开启事务注解
写在类上:这个类上的所有的方法都支持事务
2、事务的属性
(1)传播行为 propagation
如果一个方法有事务了,然后调用了另一个方法,另一个方法可能也有事务,那么他们这两个事务要怎么处理呢?这个时候就需要设置事务的传播行为来规定这两个事务怎么处理
规定了一共有7中传播行为:
- REQUIRED:支持当前事务,如果不存在就新建一个(默认)[没有就新建,有就加入](重要)
- SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行[有就加入,没有就不管了]
- MANDATORY:必须运行在一个事务中,如果当前没有事务正在发生,将抛出一个异常[有就加入,没有就抛异常]
- REQUIRES_NEW:开启一个新的事务,如果一个事务已经存在,则将这个存在的事务挂起[不管有没有,直接开启一个新事务,开启的新事务和之前的事务不存在嵌套关系,之前事务被挂起](重要)
- NOT_SUPPORTED:以非事务方式运行,如果有事务存在,挂起当前事务[不支持事务,存在就挂起]
- NEVER:以非事务方式运行,如果有事务存在,抛出异常[不支持事务,存在就抛异常]
- NESTED:如果当前正有一个事务在进行中,则该方法应当运行在一个嵌套式事务中。被嵌套的事务可以独立千外层事务进行提交或回滚。如果外层事务不存在,行为就像REQUIRED-样。[有事务的话,就在这个事务里再嵌套一个完全独立的事务,嵌套的事务可以独立的提交和回滚,没有事务就和REQUIRED一样]
事务的传播行为在Spring框架中被定义为枚举类型
(2)事务隔离级别 isolation
事务隔离级别类似于教室A和教室B之间的那道墙,隔离级别越高表示墙体越厚,隔离效果越好。
隔离事务就是为了防止多事务操作一张表的时候出现的干扰
事务的隔离级别也是枚举类型,越往下隔离效果越好
oracle默认是读提交,mysql默认是可重复读
数据库中读取存在三大问题:
- 脏读:读取到没有提交到数据库的数据,还没有commit还在缓存就能直接读到
- 不可重复读:在同一个事务当中,第一次和第二次读取的数据不一样
- 幻读:读到的数据是假的(只要多个事务并发,肯定是存在幻读的,除非一次一个事务排队)
事务隔离级别包括四个级别:
- 读取提交 READ_UNCOMMITTED:这种隔离级别,存在脏读问题,表示能够读取到其他事务未提交的数据
- 读提交 READ_COMMITTED(oracle默认级别):解决了脏读问题,其他事务提交后才能读到,但存在不可重复读问题
- 可重复读 REPEATABLE_READ(mysql默认级别):解决了不可重复读问题,但是存在幻读问题
- 序列化 SERIALIZABLE:解决了幻读,但是事务必须排队执行,不支持并发
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交 | 有 | 有 | 有 |
读提交 | 无 | 有 | 有 |
可重复读 | 无 | 无 | 有 |
序列化 | 无 | 无 | 无 |
(3)事务超时
@Transactional(timeout=10)
以上代码表示设置事务超时设置为10秒
表示超过10秒如果该事务所有的DML还没执行完毕,最终结果会选择回滚
默认值为-1,表示没有时间限制。
这里有个坑:事务的超时时间是指执行到最后一条DML语句的时间,而不是整个方法的执行时间
所以如果你最后一条DML语句后有很多业务代码,这些代码的执行时间都不计入超时
(4)只读事务
@TranSactional(readOnly = true)
将当前事务设置为只读事务的时候,该事务执行过程中只允许使用select语句执行,增删改不能执行,该属性有什么作用:
启动spring的优化策略,提高select语句的执行效率
如果确实没有增删改操作,建议设置为只读事务
(5)设置哪些异常回滚事务
默认是只要执行过程中发送异常,就回滚事务
我们也可以指定什么异常或该异常的子类异常回滚事务
@Transactional(rollbackFor=RuntimeException.class)
上面代码表示RuntimeException异常或该异常的子类异常才回滚
也可以设置什么异常或他的子类不回滚:
@Transactional(noRollbackFor=RuntimeException.class)
上面代码表示RuntimeException异常或该异常的子类异常的时候不会回滚