Spring
2022-09-19 19:51:19 75 举报
AI智能生成
根据 JavaGuide 绘制的脑图
作者其他创作
大纲/内容
Spring 基础
什么是 Spring 框架?<br>
Spring 是一款开源的轻量级 Java 开发框架,旨在提高开发人员的开发效率以及系统的可维护性。
我们一般说 Spring 框架指的都是 Spring Framework,它是很多模块的集合,使用这些模块可以很方便地协助我们进行开发,比如说 Spring <br>支持 IoC(Inverse of Control: 控制反转) 和 AOP(Aspect-Oriented Programming: 面向切面编程)、可以很方便地对数据库进行访问、可以<br>很方便地集成第三方组件(电子邮件,任务,调度,缓存等等)、对单元测试支持比较好、支持 RESTful Java 应用程序的开发。
Spring 最核心的思想就是不重新造轮子,开箱即用,提高开发效率。
Spring 提供的核心功能主要是 IoC 和 AOP。学习 Spring ,一定要把 IoC 和 AOP 的核心思想搞懂!
Spring 包含的模块有哪些?<br>
Spring 4.x 版本 :<br>
Spring 5.x 版本 :<br>
Core Container
Spring 框架的核心模块,也可以说是基础模块,主要提供 IoC 依赖注入功能的支持。Spring <br>其他所有的功能基本都需要依赖于该模块,从下面 Spring 各个模块的依赖关系图就可以看出来。
<ul><li>spring-core :Spring 框架基本的核心工具类。</li><li>spring-beans :提供对 bean 的创建、配置和管理等功能的支持。</li><li>spring-context :提供对国际化、事件传播、资源加载等功能的支持。</li><li>spring-expression :提供对表达式语言(Spring Expression Language) <br>SpEL 的支持,只依赖于 core 模块,不依赖于其他模块,可以单独使用。</li></ul>
AOP
<ul><li>spring-aspects :该模块为与 AspectJ 的集成提供支持。</li><li>spring-aop :提供了面向切面的编程实现。</li><li>spring-instrument :提供了为 JVM 添加代理(agent)的功能。 具体来讲,它为 Tomcat 提供了一个织入代理,<br>能够为 Tomcat 传递类文 件,就像这些文件是被类加载器加载的一样。没有理解也没关系,这个模块的使用场景非常有限。</li></ul>
Data Access/Integration<br>
<ul><li>spring-jdbc :提供了对数据库访问的抽象 JDBC。不同的数据库都有自己独立的 API 用于操作数据库,<br>而 Java 程序只需要和 JDBC API 交互,这样就屏蔽了数据库的影响。</li><li>spring-tx :提供对事务的支持。</li><li>spring-orm : 提供对 Hibernate、JPA 、iBatis 等 ORM 框架的支持。</li><li>spring-oxm :提供一个抽象层支撑 OXM(Object-to-XML-Mapping),例如:JAXB、Castor、XMLBeans、JiBX 和 XStream 等。</li><li>spring-jms : 消息服务。自 Spring Framework 4.1 以后,它还提供了对 spring-messaging 模块的继承。</li></ul>
Spring Web<br>
<ul><li>spring-web :对 Web 功能的实现提供一些最基础的支持。</li><li>spring-webmvc : 提供对 Spring MVC 的实现。</li><li>spring-websocket : 提供了对 WebSocket 的支持,WebSocket 可以让客户端和服务端进行双向通信。</li><li>spring-webflux :提供对 WebFlux 的支持。WebFlux 是 Spring Framework 5.0 中引入的新的响应式框架。<br>与 Spring MVC 不同,它不需要 Servlet API,是完全异步。</li></ul>
Messaging
spring-messaging 是从 Spring4.0 开始新加入的一个模块,主要职责是为 Spring 框架集成一些基础的报文传送应用。<br>
Spring Test<br>
Spring 团队提倡测试驱动开发(TDD)。有了控制反转 (IoC)的帮助,单元测试和集成测试变得更简单。<br>
Spring 的测试模块对 JUnit(单元测试框架)、TestNG(类似 JUnit)、Mockito(主要用来 Mock 对象)、<br>PowerMock(解决 Mockito 的问题比如无法模拟 final, static, private 方法)等等常用的测试框架支持的都比较好。
Spring5.x 版本中 Web 模块的 Portlet 组件已经被废弃掉,同时增加了用于异步响应式处理的 WebFlux 组件。
Spring 各个模块的依赖关系如下:
Spring, Spring MVC, Spring Boot 之间什么关系?<br>
Spring 包含了多个功能模块(上面刚刚提高过),其中最重要的是 Spring-Core(主要提供 IoC 依赖注入<br>功能的支持) 模块, Spring 中的其他模块(比如 Spring MVC)的功能实现基本都需要依赖于该模块。
Spring MVC 是 Spring 中的一个很重要的模块,主要赋予 Spring 快速构建 MVC 架构的 Web 程序的能力。MVC 是<br>模型(Model)、视图(View)、控制器(Controller)的简写,其核心思想是通过将业务逻辑、数据、显示分离来组织代码。
使用 Spring 进行开发各种配置过于麻烦比如开启某些 Spring 特性时,需要用 XML 或 Java 进行显式配置。于是,Spring Boot 诞生了!
Spring 旨在简化 J2EE 企业应用程序开发。Spring Boot 旨在简化 Spring 开发(减少配置文件,开箱即用!)。
Spring Boot 只是简化了配置,如果你需要构建 MVC 架构的 Web 程序,你还是需要使用 Spring MVC <br>作为 MVC 框架,只是说 Spring Boot 帮你简化了 Spring MVC 的很多配置,真正做到开箱即用!
Spring ioC
谈谈自己对于 Spring IoC 的了解
<b><font color="#0000ff">IoC(Inverse of Control: 控制反转)</font></b> 是一种设计思想,而不是一个具体的技术实现。IoC 的思想就是 <b><font color="#0000ff">将原本在<br>程序中手动创建对象的控制权,交由 Spring 框架来管理</font></b>。不过, IoC 并非 Spring 特有,在其他语言中也有应用。
例如:现有类 A 依赖于类 B<br><ul><li><b><font color="#0000ff">传统的开发方式</font></b> :往往是在类 A 中手动通过 new 关键字来 new 一个 B 的对象出来</li><li><b><font color="#0000ff">使用 IoC 思想的开发方式</font></b> :不通过 new 关键字来创建对象,而是通过 IoC 容器(Spring 框架) <br>来帮助我们实例化对象。我们需要哪个对象,直接从 IoC 容器里面过去即可。</li></ul>
从以上两种开发方式的对比来看:我们 “丧失了一个权力” (创建、管理对象的权力),<br>从而也得到了一个好处(不用再考虑对象的创建、管理等一系列的事情)
为什么叫控制反转?<br><ul><li><b><font color="#0000ff">控制</font></b> :指的是对象创建(实例化、管理)的权力</li><li><b><font color="#0000ff">反转</font></b> :控制权交给外部环境(Spring 框架、IoC 容器)</li></ul>
IoC 解决了什么问题
IoC 的思想就是两方之间不互相依赖,由第三方容器来管理相关资源。这样有什么好处呢?<br><ul><li>对象之间的耦合度或者说依赖程度降低;</li><li>资源变的容易管理;比如你用 Spring 容器提供的话很容易就可以实现一个单例。</li></ul>
例如:现有一个针对 User 的操作,利用 Service 和 Dao 两层结构进行开发
在没有使用 IoC 思想的情况下,Service 层想要使用 Dao 层的具体实现的话,需要通过 new 关键字在 <br>UserServiceImpl 中手动 new 出 IUserDao 的具体实现类 UserDaoImpl(不能直接 new 接口类)。
很完美,这种方式也是可以实现的,但是我们想象一下如下场景:<br>开发过程中突然接到一个新的需求,针对对 IUserDao 接口开发出另一个具体实现类。因为 Server 层依赖了 IUserDao 的具体实现,所以<br>我们需要修改 UserServiceImpl 中 new 的对象。如果只有一个类引用了 IUserDao 的具体实现,可能觉得还好,修改起来也不是很费力气,<br>但是如果有许许多多的地方都引用了 IUserDao 的具体实现的话,一旦需要更换 IUserDao 的实现方式,那修改起来将会非常的头疼。<br>
使用 IoC 的思想,我们将对象的控制权(创建、管理)交有 IoC 容器去管理,我们在使用的时候直接向 IoC 容器 “要” 就可以了:
将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放<br>出来。 IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。
在 Spring 中, IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个 Map(key,value),Map 中存放的是各种对象。
Spring 时代我们一般通过 XML 文件来配置 Bean,后来开发人员觉得 XML 文件来配置不太好,于是 SpringBoot 注解配置就慢慢开始流行起来。
什么是 DI
DI,即 依赖注入,是 IoC 的一种具体实现方式。
当调用者需要使用对象实例时,Spring 容器为调用者提供对象实例这个过程叫做依赖注入。<br>
依赖注入的方式有三种:<br><ul><li>构造器注入:提供构造器</li><li>set 注入:提供 setter 方法</li><li>注解注入:看下面;</li></ul>
IoC 和 DI 的区别
IoC: 控制反转,将类对象的创建交给 Spring 容器管理创建。
IoC 强调的是将对象实例的创建控制权由 Spring 容器来统一管理,需要的时候从容器中取出,<br>而不是由调用者自身去创建,从而达到降低代码耦合性与硬代码的目的。
DI: 依赖注入,将类里面的属性在创建类的过程中给属性赋值。<br>
DI 强调的是当调用者需要使用对象实例时,Spring 容器为调用者提供对象实例这个过程。
DI 和 IoC 的关系: DI 不能单独存在,DI 需要在 IoC 的基础上来完成。
什么是 Spring Bean?
简单来说,Bean 代指的就是那些被 IoC 容器所管理的对象。
我们需要告诉 IoC 容器帮助我们管理哪些对象,这个是通过配置元数据来定义的。<br>配置元数据可以是 XML 文件、注解或者 Java 配置类。
下图简单地展示了 IoC 容器如何使用配置元数据来管理对象。
org.springframework.beans 和 org.springframework.context 这两个包是 IoC <br>实现的基础,如果想要研究 IoC 相关的源码的话,可以去看看
将一个类声明为 Bean 的注解有哪些?
<b><font color="#0000ff">@Component </font></b>:通用的注解,可标注任意类为 Spring 组件。一个 Bean 不知道属于哪个层时,可以使用此注解。
<b><font color="#0000ff">@Repository </font></b>: 对应持久层即 Dao 层,主要用于数据库相关操作。
<b><font color="#0000ff">@Service</font></b> : 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。
<b><font color="#0000ff">@Controller</font></b> : 对应 Spring MVC 控制层,主要用户接受用户请求并调用 Service 层返回数据给前端页面。
@Component 和 @Bean 的区别
@Component 注解作用于类,而 @Bean 注解作用于方法。
@Component 通常是通过 <b><font color="#0000ff">类路径扫描 </font></b>来自动侦测以及自动装配到 Spring 容器中(我们可以使用 @ComponentScan <br>注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中)。<br>@Bean 注解通常是我们在标有该注解的方法中定义产生这个 bean, @Bean 告诉了 Spring 这是某个类的实例,当我需要用它的时候还给我。
@Bean 注解比 @Component 注解的自定义性更强,而且很多地方我们只能通过 @Bean 注解来注册 bean。<br>比如当我们引用第三方库中的类需要装配到 Spring 容器时,则只能通过 @Bean 来实现。
@Bean 注解使用示例:<br>
上面的代码相当于下面的 xml 配置:
下面这个例子是无法通过 @Component 实现的:
注入 Bean 的注解有哪些?
Spring 内置的 @Autowired 以及 JDK 内置的 @Resource 和 @Inject 都可以用于注入 Bean。
@Autowired 和 @Resource 使用的比较多一些。<br>
@Autowired 和 @Resource 的区别
<b><font color="#0000ff">@Autowired</font></b> 属于 Spring 内置的注解,<b><font color="#0000ff">默认的注入方式为 byType</font></b>(根据类型进行匹配),<br>也就是说会优先根据接口类型去匹配并注入 Bean (接口的实现类)。
这会有什么问题呢? 当一个 <b><font color="#0000ff">接口存在多个实现类 </font></b>的话,byType 这种方式就无法正确注入对象了,<br>因为这个时候 Spring 会同时找到多个满足条件的选择,默认情况下它自己不知道选择哪一个。
这种情况下,注入方式会 <b><font color="#0000ff">变为 byName</font></b>(根据名称进行匹配),这个名称通常就是类名(首字母小写)。
举个例子,SmsService 接口有两个实现类: SmsServiceImpl1 和 SmsServiceImpl2,且它们都已经被 Spring 容器所管理。
建议通过 <b><font color="#0000ff">@Qualifier</font></b> 注解来 <b><font color="#0000ff">显示指定名称 </font></b>而不是依赖变量的名称。
<b><font color="#0000ff">@Resource </font></b>属于 JDK 提供的注解,<b><font color="#0000ff">默认注入方式为 byName</font></b>。如果无法通过名称匹配到对应的 Bean 的话,注入方式会 <b><font color="#0000ff">变为byType</font></b>。
@Resource 有两个比较重要且日常开发常用的属性:name(名称)、type(类型)。<br>
如果仅指定 name 属性则注入方式为 byName,如果仅指定 type 属性则注入方式为 byType,<br>如果同时指定 name 和 type 属性(不建议这么做)则注入方式为 byType+byName。
简单总结一下:<br><ul><li>@Autowired 是 Spring 提供的注解,@Resource 是 JDK 提供的注解。</li><li><b><font color="#0000ff">@Autowired</font></b> 默认的注入方式为 <b><font color="#0000ff">byType</font></b>(根据类型进行匹配),<b><font color="#0000ff">@Resource</font></b> 默认注入方式为 <b><font color="#0000ff">byName</font></b>(根据名称进行匹配)。</li><li>当一个接口存在多个实现类的情况下,@Autowired 和 @Resource 都需要通过名称才能正确匹配到对应的 Bean。<br><b><font color="#0000ff">@Autowired</font></b> 可以通过 <b><font color="#0000ff">@Qualifier</font></b> 注解来显示指定名称,<b><font color="#0000ff">@Resource </font></b>可以通过<b><font color="#0000ff"> name</font></b> 属性来显示指定名称。</li></ul>
Bean 的作用域有哪些?
Spring 中 Bean 的作用域通常有下面几种:<br><ul><li><b><font color="#0000ff">singleton</font></b> : IoC 容器中只有唯一的 bean 实例。<b><font color="#0000ff">Spring 中的 bean 默认都是单例的</font></b>,是对单例设计模式的应用。</li><li><b><font color="#0000ff">prototype</font></b> : <b><font color="#0000ff">每次获取都会创建一个新的 bean 实例</font></b>。也就是说,连续 getBean() 两次,<b><font color="#0000ff">得到的是不同的 Bean 实例</font></b>。</li><li><b><font color="#0000ff">request</font></b> (仅 Web 应用可用): 每一次 HTTP 请求都会产生一个新的 bean(请求 bean),该 bean 仅在当前 HTTP request 内有效。</li><li><b><font color="#0000ff">session</font></b> (仅 Web 应用可用) : 每一次来自新 session 的 HTTP 请求都会产生一个新的 bean(会话 bean),<br>该 bean 仅在当前 HTTP session 内有效。</li><li><b><font color="#0000ff">application/global-session</font></b> (仅 Web 应用可用): 每个 Web 应用在启动时创建一个 Bean(应用 Bean),<br>该 bean 仅在当前应用启动时间内有效。</li><li><b><font color="#0000ff">websocket</font></b> (仅 Web 应用可用):每一次 WebSocket 会话产生一个新的 bean。</li></ul>
如何配置 bean 的作用域呢?<br>
xml 方式:
注解方式:<br>
单例 Bean 的线程安全问题
大部分时候我们并没有在项目中使用多线程,所以很少有人会关注这个问题。单例 Bean <br>存在线程问题,主要是因为当 <b><font color="#0000ff">多个线程操作同一个对象的时候是存在资源竞争的</font></b>。
常见的有两种解决办法:<br><ul><li>在 Bean 中尽量避免定义可变的成员变量。</li><li>在类中定义一个 <b><font color="#0000ff">ThreadLocal</font></b> 成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的一种方式)。</li></ul>
不过,大部分 Bean 实际都是无状态(没有实例变量)的(比如 Dao、Service),这种情况下, Bean 是线程安全的。
Bean 的生命周期
<ol><li><b style="font-size: inherit;"><font color="#0000ff">Spring 启动</font></b><span style="font-size: inherit;">,</span><b style="font-size: inherit;"><font color="#0000ff">查找并加载 </font></b><span style="font-size: inherit;">需要被 Spring 管理的 </span><b style="font-size: inherit;"><font color="#0000ff">bean</font></b><span style="font-size: inherit;">,进行 </span><b style="font-size: inherit;"><font color="#0000ff">Bean 的实例化</font></b></li><li>Bean 实例化后 <b><font color="#0000ff">将 Bean 的引入和值注入到 Bean 的属性中</font><font color="#000000">(属性填充/DI)</font></b></li><li>如果 Bean 实现了 <b><font color="#0000ff">BeanNameAware</font></b> 接口的话,Spring 将 <b><font color="#0000ff">Bean 的 Id 传递给 setBeanName() 方法</font></b></li><li>如果 Bean 实现了 <b><font color="#0000ff">BeanFactoryAware</font></b> 接口的话,Spring 将调用 <b><font color="#0000ff">setBeanFactory() 方法,将 BeanFactory 容器实例传入</font></b></li><li>如果 Bean 实现了 <b><font color="#0000ff">ApplicationContextAware</font></b> 接口的话,Spring 将调用 <b><font color="#0000ff">Bean 的 setApplicationContext() 方法,<br>将 bean 所在应用上下文引用传入进来</font></b></li><li>如果 Bean 实现了 <b><font color="#0000ff">BeanPostProcessor</font></b> 接口,Spring 就将调用他们的 <b><font color="#0000ff">postProcess</font><font color="#ff00ff">Before</font><font color="#0000ff">Initialization() 方法</font></b>。</li><li>如果 Bean 实现了 <b><font color="#0000ff">InitializingBean</font></b> 接口,Spring 将调用他们的 <b><font color="#0000ff">afterPropertiesSet() 方法</font></b>。<br>类似的,如果 bean 使用 <b><font color="#0000ff">init-method 声明了初始化方法,该方法也会被调用 </font><font color="#000000">(一些自定义的属性注入到容器中)</font></b></li><li>如果 Bean 实现了 <b><font color="#0000ff">BeanPostProcessor</font></b> 接口,Spring 就将调用他们的 <b><font color="#0000ff">postProcess</font><font color="#ff00ff">After</font><font color="#0000ff">Initialization() 方法</font></b>。</li><li>此时,<b><font color="#0000ff">Bean 已经准备就绪</font></b>,可以被应用程序使用了。他们将 <b><font color="#0000ff">一直驻留在应用上下文中,直到应用上下文被销毁</font></b>。</li><li><span style="font-size: inherit;">如果 bean 实现了 </span><b style="font-size: inherit;"><font color="#0000ff">DisposableBean</font></b><span style="font-size: inherit;"> 接口,Spring 将调用它的 </span><b style="font-size: inherit;"><font color="#0000ff">destory() </font></b><span style="font-size: inherit;">接口方法,同样,如果 bean 使用了</span></li></ol><b><font color="#0000ff">destory-method 声明销毁方法,该方法也会被调用</font></b>。<br>
Spring AOP
对 AOP 的理解
AOP (Aspect-Oriented Programming: 面向切面编程) 能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、<br>日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
<b><font color="#0000ff">Spring AOP</font></b> 就是基于 <b><font color="#0000ff">动态代理 </font></b>的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象,而对于没有<br>实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理,如下图所示:
当然也可以使用 <b><font color="#0000ff">AspectJ</font></b> !Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。
AOP 切面编程设计到的一些专业术语:
Spring AOP 和 AspectJ AOP 的区别
<b><font color="#0000ff">Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 <br>Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。</font></b>
Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。<br>AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单。
如果我们的切面比较少,那么两者性能差异不大。但是,当 <b><font color="#0000ff">切面太多 </font></b>的话,最好选择 <b><font color="#0000ff">AspectJ</font></b> ,它比 Spring AOP <b><font color="#0000ff">快很多</font></b>。
AspectJ 定义的通知类型有哪些
<ul><li><b><font color="#0000ff">Before(前置通知)</font></b>:目标对象的方法调用之前触发</li><li><b><font color="#0000ff">After (后置通知)</font></b>:目标对象的方法调用之后触发</li><li><b><font color="#0000ff">AfterReturning(返回通知)</font></b>:目标对象的方法调用完成,在返回结果值之后触发</li><li><b><font color="#0000ff">AfterThrowing(异常通知)</font></b> :目标对象的方法运行中抛出 / 触发异常后触发。AfterReturning 和 AfterThrowing <br>两者互斥。如果方法调用成功无异常,则会有返回值;如果方法抛出了异常,则不会有返回值。</li><li><b><font color="#0000ff">Around(环绕通知)</font></b>: 编程式控制目标对象的方法调用。环绕通知是所有通知类型中可操作范围最大的一种,因为它可以<br>直接拿到目标对象,以及要执行的方法,所以环绕通知可以任意的在目标对象的方法调用前后搞事,甚至不调用目标对象的方法</li></ul>
多个切面的执行顺序如何控制
1、通常使用 <b><font color="#0000ff">@Order</font></b> 注解直接定义切面顺序:
2、实现 Ordered 接口重写 getOrder 方法:
SpringMVC
对 SpringMVC 的理解
MVC 是模型(Model)、视图(View)、控制器(Controller)的简写,其核心思想是通过将业务逻辑、数据、显示分离来组织代码。
想要真正理解 Spring MVC,我们先来看看 Model 1 和 Model 2 这两个没有 Spring MVC 的时代。
Model 1 时代<br>
很多学 Java 后端比较晚的朋友可能并没有接触过 Model 1 时代下的 JavaWeb 应用开发。在 Model1 模式下,<br>整个 Web 应用几乎全部用 JSP 页面组成,只用少量的 JavaBean 来处理数据库连接、访问等操作。
这个模式下 JSP 即是控制层(Controller)又是表现层(View)。显而易见,这种模式存在很多问题。比如控制逻辑<br>和表现逻辑混杂在一起,导致代码重用率极低;再比如前端和后端相互依赖,难以进行测试维护并且开发效率极低。
Model 2 时代<br>
学过 Servlet 并做过相关 Demo 的朋友应该了解“Java Bean(Model)+ JSP(View)+ <br>Servlet(Controller) ”这种开发模式,这就是早期的 JavaWeb MVC 开发模式。
<ul><li>Model:系统涉及的数据,也就是 dao 和 bean。</li><li>View:展示模型中的数据,只是用来展示。</li><li>Controller:处理用户请求都发送给 ,返回数据给 JSP 并展示给用户。</li></ul>
Model2 模式下还存在很多问题,Model2 的抽象和封装程度还远远不够,使用 Model2 <br>进行开发时不可避免地会重复造轮子,这就大大降低了程序的可维护性和复用性。
于是,很多 JavaWeb 开发相关的 MVC 框架应运而生比如 Struts2,但是 Struts2 比较笨重。
Spring MVC 时代<br>
随着 Spring 轻量级开发框架的流行,Spring 生态圈出现了 Spring MVC 框架, Spring MVC 是当前最优秀的 MVC <br>框架。相比于 Struts2 , Spring MVC 使用更加简单和方便,开发效率更高,并且 Spring MVC 运行速度更快。
<b><font color="#0000ff">MVC 是一种设计模式,Spring MVC 是一款很优秀的 MVC 框架。</font></b>
Spring MVC 可以帮助我们进行更简洁的 Web 层的开发,并且它天生与 Spring 框架集成。Spring MVC 下我们一般把后端项目<br>分为 Service 层(处理业务)、Dao 层(数据库操作)、Entity 层(实体类)、Controller 层(控制层,返回数据给前台页面)。
SpringMVC 核心组件有哪些
记住了下面这些组件,也就记住了 SpringMVC 的工作原理:<br><ul><li>DispatcherServlet :<b><font color="#0000ff">核心的中央处理器</font></b>,负责接收请求、分发,并给予客户端响应。</li><li>HandlerMapping :<b><font color="#0000ff">处理器映射器</font></b>,根据 uri 去匹配查找能处理的 Handler ,并会将请求涉及到的拦截器和 Handler 一起封装。</li><li>HandlerAdapter :<b><font color="#0000ff">处理器适配器</font></b>,根据 HandlerMapping 找到的 Handler ,适配执行对应的 Handler;</li><li>Handler :<b><font color="#0000ff">请求处理器</font></b>,处理实际请求的处理器。</li><li>ViewResolver :<b><font color="#0000ff">视图解析器</font></b>,根据 Handler 返回的逻辑视图 / 视图,解析并渲染真正的视图,并传递给 DispatcherServlet 响应客户端</li></ul>
SpringMVC 工作原理
Spring MVC 原理如下图所示:
流程说明(<b><font color="#0000ff">重要</font></b>):<br><ul><li>客户端(浏览器)发送请求, <b><font color="#0000ff">DispatcherServlet 拦截请求</font></b>。</li><li><span style="font-size: inherit;">DispatcherServlet 根据请求信息调用 HandlerMapping 。<b><font color="#0000ff">HandlerMapping</font></b> 根据 <b><font color="#0000ff">uri</font></b> 去 <b><font color="#0000ff">匹配查找 </font></b>能处理<br>的 <b><font color="#0000ff">Handler(也就是我们平常说的 Controller 控制器)</font></b> ,并会将请求涉及到的拦截器和 Handler 一起封装。</span></li><li>DispatcherServlet <b><font color="#0000ff">调用 HandlerAdapter 适配执行 Handler</font></b> 。</li><li><b><font color="#0000ff">Handler 完成 </font></b>对用户请求的 <b><font color="#0000ff">处理后</font></b>,会 <b><font color="#0000ff">返回一个 ModelAndView 对象给 DispatcherServlet</font></b>,ModelAndView <br>顾名思义,包含了数据模型以及相应的视图的信息。Model 是返回的数据对象,<b><font color="#0000ff">View 是个逻辑上的 View</font></b>。</li><li><b><font color="#0000ff">ViewResolver 会根据逻辑 View 查找实际的 View</font></b>。</li><li>DispaterServlet 把 <b><font color="#0000ff">返回的 Model 传给 View(视图渲染)</font></b>。</li><li>把 <b><font color="#0000ff">View 返回给请求者</font></b>(浏览器)</li></ul>
统一异常处理怎么做
推荐使用注解的方式统一异常处理,具体会使用到 <b><font color="#0000ff">@ControllerAdvice + @ExceptionHandler</font></b> 这两个注解 。<br>
这种异常处理方式下,会给所有或者指定的 Controller 织入异常处理的逻辑(AOP),<br>当 Controller 中的方法抛出异常的时候,由被 @ExceptionHandler 注解修饰的方法进行处理。
ExceptionHandlerMethodResolver 中 getMappedMethod 方法决定<br>了异常具体被哪个 @ExceptionHandler 注解修饰的方法处理异常。
从源代码看出: <b><font color="#0000ff">getMappedMethod() 会首先找到可以匹配处理异常的所有方法信息,<br>然后对其进行从小到大的排序,最后取最小的那一个匹配的方法(即匹配度最高的那个)。</font></b><br>
Spring 设计模式
Spring 中运用到的设计模式如下:<br><ul><li><b><font color="#0000ff">工厂设计模式</font></b> : Spring 使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象。</li><li><b><font color="#0000ff">代理设计模式</font></b> : Spring AOP 功能的实现。</li><li><b><font color="#0000ff">单例设计模式</font></b> : Spring 中的 Bean 默认都是单例的。</li><li><b><font color="#0000ff">模板方法模式</font></b> : Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。</li><li><b><font color="#0000ff">包装器设计模式</font></b> : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。<br>这种模式让我们可以根据客户的需求能够动态切换不同的数据源。</li><li><b><font color="#0000ff">观察者模式:</font></b>Spring 事件驱动模型就是观察者模式很经典的一个应用。</li><li><b><font color="#0000ff">适配器模式</font></b> : Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller。</li><li>......</li></ul>
详解待续......
Spring 事务
详细讲解
什么是事务?<br>
<b><font color="#0000ff">事务是逻辑上的一组操作,要么都执行,要么都不执行。</font></b>
实际系统的每个业务方法可能包括了多个原子性的数据库操作,比如下面的 savePerson() 方法中就有<br>两个原子性的数据库操作。这些原子性的数据库操作是有依赖的,它们要么都执行,要不就都不执行。
另外,需要格外注意的是:<b><font color="#0000ff">事务能否生效的关键是数据库引擎是否支持事务</font></b>。<br>比如常用的 MySQL 数据库默认使用支持事务的 InnoDB 引擎。但是,如果把数据库引擎变为 MyISAM,那么程序也就不再支持事务了!
事务最经典也经常被拿出来说例子就是转账了。假如小明要给小红转账 1000 元,这个转账会涉及到两个关键操作就是:<br><ol><li>将小明的余额减少 1000 元。</li><li><span style="font-size: inherit;">将小红的余额增加 1000 元。<br><br></span></li></ol>万一在这两个操作之间突然出现错误比如银行系统崩溃或者网络故障,导致小明余额减少而小红的余额没有增加,<br>这样就不对了。事务就是保证这两个关键操作要么都成功,要么都要失败。<br>
另外,<b><font color="#0000ff">数据库事务的 ACID 四大特性是事务的基础</font></b>,下面简单来了解一下。<br>
事务的特性(ACID)了解么?<br>
<b><font color="#0000ff">原子性(Atomicity): </font></b><br><ul><li>一个事务(transaction)中的所有操作,<b><font color="#0000ff">要么全部完成,要么全部不完成</font></b>,不会结束在中间某个环节。事务在执行过程中<br>发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。<b><font color="#0000ff">即事务不可分割、不可约简</font></b>。</li></ul>
<font color="#0000ff" style="font-weight: bold;">一致性(Consistency): </font><br><ul style=""><li style=""><font color="#000000">在事务开始之前和事务结束以后,</font><b style=""><font color="#0000ff">数据库的完整性没有被破坏</font></b><font color="#000000">。</font>事务按照预期生效,数据的状态是预期的状态。<br>例如数据库系统在运行过程中发生故障,有些事务尚未完成就被迫中断,这些未完成的事务对数据库所作的修改<br>有一部分已写入物理数据库,这是数据库就处于一种不正确的状态,也就是不一致的状态</li></ul>
<b><font color="#0000ff">隔离性(Isolation):</font></b> <br><ul><li>数据库允许 <b><font color="#0000ff">多个并发事务同时对其数据进行读写和修改的能力</font></b>,隔离性可以 <b><font color="#0000ff">防止多个事务并发执行时由于交叉执行而导致数据的不一致</font></b>。事务隔离<br>分为不同级别,包括未提交读(Read uncommitted)、提交读(read committed)、可重复读(repeatable read)和串行化(Serializable)。</li></ul>
<b><font color="#0000ff">持久性(Durability):</font></b> <br><ul><li>事务处理 <b><font color="#0000ff">结束后</font></b>,<b><font color="#0000ff">对数据的修改就是永久的</font></b>,即便系统故障也不会丢失。</li></ul>
详谈 Spring 对事务的支持<br>
再提醒一次:你的程序是否支持事务首先取决于数据库
MySQL 怎么保证原子性的?<br><ul><li>我们知道如果想要保证事务的原子性,就需要在 <b><font color="#0000ff">异常发生时</font></b>,对已经执行的操作进行 <b><font color="#0000ff">回滚</font></b>,在 MySQL 中,恢复机制是通过 <b><font color="#0000ff">回滚日志(undo log)</font></b> 实现的,所有事务进行的修改都会先记录到这个回滚日志中,然后再执行相关的操作。如果执行过程中 <b><font color="#0000ff">遇到异常 </font></b>的话,我们直接利用<b><font color="#0000ff"> 回滚日志</font></b> 中的信息将数据回滚到修改之前的样子即可!并且,<b><font color="#0000ff">回滚日志会先于数据持久化到磁盘上</font></b>。这样就保证了即使遇到数据库突然宕机等情况,当用户再次启动数据库的时候,数据库还能够通过查询回滚日志来回滚将之前未完成的事务。</li></ul>
Spring 支持两种方式的事务管理<br>
编程式事务管理<br>
通过 TransactionTemplate 或者 TransactionManager 手动管理事务,<br>实际应用中很少使用,但是对于你理解 Spring 事务管理原理有帮助。
使用 TransactionTemplate 进行编程式事务管理的示例代码如下:
使用 TransactionManager 进行编程式事务管理的示例代码如下:<br>
声明式事务管理<br>
推荐使用(代码侵入性最小),实际是通过 AOP 实现(基于 @Transactional 的全注解方式使用最多)。<br>
使用 @Transactional 注解进行事务管理的示例代码如下:
Spring 事务管理接口介绍<br>
概述
Spring 框架中,事务管理相关最重要的 3 个接口如下:<br><ul><li>PlatformTransactionManager: (平台)事务管理器,Spring 事务策略的核心。</li><li><span style="font-size: inherit;">TransactionDefinition: 事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)。</span></li><li>TransactionStatus: 事务运行状态。</li></ul>
我们可以把 PlatformTransactionManager 接口看作是事务上层的管理者,<br>而 TransactionDefinition 和 TransactionStatus 这两个接口可以看作是事务的描述。
PlatformTransactionManager 会根据 TransactionDefinition 的定义比如事务超时时间、隔离级别、传播行为等来进行<br>事务管理 ,而 TransactionStatus 接口则提供了一些方法来获取事务相应的状态比如是否新事务、是否可以回滚等等。<br>
PlatformTransactionManager: 事务管理接口<br>
<b><font color="#0000ff">Spring 并不直接管理事务,而是提供了多种事务管理器</font></b>。Spring 事务管理器的接口是: PlatformTransactionManager 。<br>
通过这个接口,Spring 为各个平台如 JDBC(DataSourceTransactionManager)、Hibernate(HibernateTransactionManager)、<br>JPA(JpaTransactionManager)等都提供了对应的事务管理器,但是 <b><font color="#0000ff">具体的实现就是各个平台自己的事情了</font></b>。
PlatformTransactionManager 接口的具体实现如下:<br>
PlatformTransactionManager 接口中定义了三个方法:
为什么要定义或者说抽象出来 PlatformTransactionManager 这个接口呢?<br><ul><li>主要是因为要将事务管理行为抽象出来,然后由不同的平台去实现它,这样我们可以保证提供给外部的行为不变,方便我们扩展。</li></ul>
TransactionDefinition: 事务属性<br>
事务管理器接口 PlatformTransactionManager 通过 getTransaction(TransactionDefinition definition) 方法<br>来得到一个事务,这个方法里面的参数是 TransactionDefinition 类 ,这个类就定义了一些基本的事务属性。
什么是事务属性呢? 事务属性可以理解成事务的一些基本配置,描述了事务策略如何应用到方法上。
事务属性包含了 5 个方面:<br><ul><li>隔离级别</li><li>传播行为</li><li>回滚规则</li><li>是否只读</li><li>事务超时</li></ul>
TransactionDefinition 接口中定义了 5 个方法以及一些表示事务属性的常量比如隔离级别、传播行为等等。
TransactionStatus: 事务状态<br>
TransactionStatus 接口用来记录事务的状态,该接口定义了一组方法,用来获取或判断事务的相应状态信息。
PlatformTransactionManager.getTransaction(…) 方法返回一个 TransactionStatus 对象。<br>
TransactionStatus 接口内容如下:<br>
事务属性详解<br>
实际业务开发中,大家一般都是使用 @Transactional 注解来开启事务,<br>但很多人并不清楚这个注解里面的参数是什么意思,有什么用。
事务传播行为<br>
<font color="#000000">事务传播行为是为了 </font><b style="color: rgb(0, 0, 255);">解决业务层方法之间互相调用的事务问题。</b>
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。<br>例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
举个例子:在 A 类的 aMethod() 方法中调用了 B 类的 bMethod() 方法。这个时候就涉及到业务层方法之间互相调用的事务问题。<br>如果 bMethod() 发生异常需要回滚,如何配置事务传播行为才能让 aMethod() 也跟着回滚呢?这个时候就需要用事务传播行为了。
在 TransactionDefinition 定义中包括了如下几个表示传播行为的常量:
不过,为了方便使用,Spring 相应地定义了一个枚举类 Propagation:
正确的事务传播行为可能的值
1. TransactionDefinition.PROPAGATION_REQUIRED<br>
使用的最多的一个事务传播行为,我们平时经常使用的 <b><font color="#0000ff">@Transactional 注解默认使用 </font></b>就是这个事务传播行为。<br><b><font color="#0000ff">如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务</font></b>。也就是说:<br><ul><li>如果外部方法 <b><font color="#0000ff">没有 </font></b>开启事务的话,Propagation.REQUIRED 修饰的 <b><font color="#0000ff">内部方法会新开启自己的事务</font></b>,且开启的事务 <b><font color="#0000ff">相互独立,互不干扰</font></b>。</li><li>如果外部方法 <b><font color="#0000ff">开启 </font></b>事务并且被 Propagation.REQUIRED 的话,<b><font color="#0000ff">所有 Propagation.REQUIRED 修饰的内部方法和外部方法均属于同一事务 ,<br>只要一个方法回滚,整个事务均回滚</font></b>。</li></ul>
举个例子:如果我们上面的 aMethod() 和 bMethod() 使用的都是 PROPAGATION_REQUIRED 传播行为的话,<br>两者使用的就是同一个事务,<b><font color="#0000ff">只要其中一个方法回滚,整个事务均回滚</font></b>。
2. TransactionDefinition.PROPAGATION_REQUIRES_NEW<br>
创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说 <b><font color="#0000ff">不管外部方法是否开启事务</font></b>,<br>Propagation.REQUIRES_NEW 修饰的 <b><font color="#0000ff">内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰</font></b>。
举个例子:如果上面的 bMethod() 使用 PROPAGATION_REQUIRES_NEW 事务传播行为修饰,aMethod 还是用 PROPAGATION_REQUIRED 修饰的话。如果 aMethod() 发生异常回滚,bMethod() 不会跟着回滚,因为 bMethod() 开启了 <b><font color="#0000ff">独立的事务</font></b>。但是,如果 bMethod() 抛出了 <b><font color="#0000ff">未被捕获的异常 </font></b>并且这个 <b><font color="#0000ff">异常满足事务回滚规则 </font></b>的话,aMethod() <b><font color="#0000ff">同样也会回滚</font></b>,因为这个异常被 aMethod() 的事务管理机制检测到了。
3. TransactionDefinition.PROPAGATION_NESTED<br>
如果当前 <b><font color="#0000ff">存在事务</font></b>,就在 <b><font color="#0000ff">嵌套事务内执行</font></b>;如果当前 <b><font color="#0000ff">没有事务</font></b>,<br>就执行 <b><font color="#0000ff">与 PROPAGATION_REQUIRED 类似 </font></b>的操作。也就是说:<br><ul><li>在外部方法开启事务的情况下,在内部开启一个新的事务,作为嵌套事务存在。</li><li>如果外部方法无事务,则单独开启一个事务,与 PROPAGATION_REQUIRED 类似。</li></ul>
这里还是简单举个例子:如果 bMethod() 回滚的话,aMethod() 也会回滚。
4.TransactionDefinition.PROPAGATION_MANDATORY<br>
如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)<br><br>这个使用的很少,就不举例子来说了。
若是 <b><font color="#0000ff">错误的配置以下 3 种事务传播行为,事务将不会发生回滚</font></b>,这里不对照案例讲解了,使用的很少。<br><br><ul><li>TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。</li><li>TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。</li><li>TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。</li></ul>
事务隔离级别
TransactionDefinition 接口中定义了五个表示隔离级别的常量:<br>
和事务传播行为那块一样,为了方便使用,Spring 也相应地定义了一个枚举类 Isolation:
<li>TransactionDefinition.ISOLATION_DEFAULT : 使用后端 <b><font color="#0000ff">数据库默认的隔离级别</font></b>,MySQL 默认<br>采用的 REPEATABLE_READ 隔离级别,Oracle 默认采用的 READ_COMMITTED 隔离级别。</li><li>TransactionDefinition.ISOLATION_READ_UNCOMMITTED : <b><font color="#0000ff">最低的隔离级别</font></b>,使用这个隔离级别很少,<br>因为它 <b><font color="#0000ff">允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读</font></b></li><li>TransactionDefinition.ISOLATION_READ_COMMITTED : <b><font color="#0000ff">允许读取并发事务已经提交的数据,可以阻止脏读,<br>但是幻读或不可重复读仍有可能发生</font></b></li><li>TransactionDefinition.ISOLATION_REPEATABLE_READ : <b><font color="#0000ff">对同一字段的多次读取结果都是一致的</font></b>,除非数据是<br>被本身事务自己所修改,<b><font color="#0000ff">可以阻止脏读和不可重复读,但幻读仍有可能发生。</font></b></li><li>TransactionDefinition.ISOLATION_SERIALIZABLE : <b><font color="#0000ff">最高的隔离级别</font></b>,完全服从 ACID 的隔离级别。所有的事务<br>依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,<b><font color="#0000ff">该级别可以防止脏读、不可重复读以及幻读</font></b>。<br>但是这将严重影响程序的性能。通常情况下也不会用到该级别。</li>
事务超时属性<br>
所谓 <b><font color="#0000ff">事务超时</font></b>,就是指 <b><font color="#0000ff">一个事务所允许执行的最长时间</font></b>,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition <br>中以 int 的值来表示超时时间,其 <b><font color="#0000ff">单位是秒,默认值为 -1</font></b>,这表示事务的超时时间取决于底层事务系统或者没有超时时间。
事务只读属性<br>
对于只有读取数据查询的事务,可以指定事务类型为 readonly,即只读事务。只读事务不<br>涉及数据的修改,数据库会提供一些优化手段,适合用在有多条数据库查询操作的方法中。
为什么一个数据查询操作还要启用事务支持呢?
拿 MySQL 的 InnoDB 举例子,根据官网描述:<br><ul><li>MySQL 默认对每一个新建立的连接都启用了 autocommit 模式。在该模式下,每一个发送到 MySQL <br>服务器的 sql 语句都会在一个单独的事务中进行处理,执行结束后会自动提交事务并开启一个新的事务。</li></ul>
如果 <b><font color="#0000ff">不加 @Transactional</font></b><font color="#0000ff">,</font><b><font color="#0000ff">每条 sql 会开启一个单独的事务,中间被其它事务改了数据,都会实时读取到最新值。</font></b>
但是,如果你给方法 <b><font color="#0000ff">加上了 @Transactional </font></b>的话,这个方法执行的 <b><font color="#0000ff">所有 sql 会被放在一个事务中</font></b>。<br>如果声明了只读事务的话,数据库就会去优化它的执行,除了这并不会带来其他的什么收益。
<ul><li>如果你一次执行 <b><font color="#0000ff">单条查询语句</font></b>,则 <b><font color="#0000ff">没有必要 </font></b>启用事务支持,数据库 <b><font color="#0000ff">默认支持 SQL 执行期间的读一致性</font></b>;</li><li>如果你一次执行 <b><font color="#0000ff">多条查询语句</font></b>,例如统计查询,报表查询,在这种场景下,<b><font color="#0000ff">多条查询 SQL 必须保证整体的读一致性</font></b>,否则,在前条 SQL <br>查询之后,后条 SQL 查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,应该 <b><font color="#0000ff">启用事务支持。</font></b></li></ul>
事务回滚规则<br>
这些规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常(RuntimeException 的子类)<br>时才会回滚,Error 也会导致事务回滚,但是,在遇到检查型(Checked)异常时不会回滚。
如果你想要回滚你定义的特定的异常类型的话,可以这样:<br><ul><li>@Transactional(rollbackFor= MyException.class)</li></ul>
@Transactional 注解使用详解<br>
@Transactional 的作用范围:<br><ul><li><b><font color="#0000ff">方法</font></b> :推荐将注解使用于方法上,不过需要注意的是:<b><font color="#0000ff">该注解只能应用到 public 方法上,否则不生效</font></b>。</li><li><b><font color="#0000ff">类</font></b> :如果这个注解使用在类上的话,表明该注解对 <b><font color="#0000ff">该类中所有的 public 方法都生效</font></b>。</li><li><b><font color="#0000ff">接口</font></b> :<b><font color="#0000ff">不推荐 </font></b>在接口上使用。</li></ul>
@Transactional 的常用配置参数
@Transactional 注解源码如下,里面包含了基本事务属性的配置。<br>
@Transactional 的常用配置参数总结(只列出了 5 个平时比较常用的):<br>
@Transactional 事务注解原理<br>
<b><font color="#0000ff">@Transactional 的工作机制是基于 AOP 实现的,AOP 又是使用动态代理实现的。如果目标对象实现了接口,<br>默认情况下会采用 JDK 的动态代理,如果目标对象没有实现了接口,会使用 CGLIB 动态代理。</font></b>
createAopProxy() 方法 决定了是使用 JDK 还是 Cglib 来做动态代理,源码如下:
如果一个类或者一个类中的 <b><font color="#0000ff">public 方法 </font></b>上被标注 <b><font color="#0000ff">@Transactional</font></b> 注解的话,<b><font color="#0000ff">Spring 容器 </font></b>就会在启动的时候为其创建一个 <b><font color="#0000ff">代理类</font></b>,<br>在调用被 @Transactional 注解的 public 方法的时候,<b><font color="#0000ff">实际调用的是 TransactionInterceptor 类中的 invoke() 方法</font></b>。这个方法的<br>作用就是 <b><font color="#0000ff">在目标方法之前开启事务,方法执行过程中如果遇到异常的时候回滚事务,方法调用完成之后提交事务</font></b>。
TransactionInterceptor 类中的 invoke() 方法内部实际调用的是 TransactionAspectSupport 类的 invokeWithinTransaction() 方法。<br>由于新版本的 Spring 对这部分重写很大,而且用到了很多响应式编程的知识,这里就不列源码了。
Spring AOP 自调用问题<br>
若同一类中的其他 <b><font color="#0000ff">没有</font></b> @Transactional 注解的方法内部 <b><font color="#0000ff">调用有</font></b> @Transactional <br>注解的方法,<b><font color="#0000ff">有 @Transactional 注解的方法的事务会失效</font></b>。
这是由于 <b><font color="#0000ff">Spring AOP 代理 </font></b>的原因造成的,因为只有当 @Transactional <br>注解的方法在 <b><font color="#0000ff">类以外被调用 </font></b>的时候,<b><font color="#0000ff">Spring 事务管理才生效</font></b>。
例如下面 MyService 类中的 method1() 调用 method2() 就会导致 method2() 的事务失效。
解决办法就是避免同一类中自调用或者使用 AspectJ 取代 Spring AOP 代理。
@Transactional 的使用注意事项总结
底层使用的数据库必须支持事务机制,否则不生效;
@Transactional 注解只有作用到 public 方法上事务才生效,不推荐在接口上使用;
避免同一个类中调用 @Transactional 注解的方法,这样会导致事务失效;
正确的设置 @Transactional 的 rollbackFor 和 propagation 属性,否则事务可能会回滚失败;
被 @Transactional 注解的方法所在的类必须被 Spring 管理,否则不生效;
......<br>
简单总结
Spring 管理事务的方式有几种?
<ul><li><b><font color="#0000ff">编程式事务</font></b> : 在代码中硬编码(不推荐使用) : 通过 TransactionTemplate或者 TransactionManager 手动管理事务,<br>实际应用中很少使用,但是对于理解 Spring 事务管理原理有帮助。</li><li><b><font color="#0000ff">声明式事务</font></b> : 在 XML 配置文件中配置或者直接基于注解(推荐使用) : 实际是通过 AOP 实现(基于@Transactional 的全注解方式使用最多)</li></ul>
Spring 事务中哪几种事务传播行为?<br>
<b><font color="#0000ff">事务传播行为是为了解决业务层方法之间互相调用的事务问题。</font></b>
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:<br>方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
正确的事务传播行为可能的值有哪些
1. TransactionDefinition.PROPAGATION_REQUIRED:<br><ul><li>使用的最多的一个事务传播行为,我们平时经常使用的 @Transactional 注解默认使用就是这个事务传播行为。<br>如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。</li></ul>
2. TransactionDefinition.PROPAGATION_REQUIRES_NEW:<br><ul><li>创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,<br>Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。</li></ul>
3. TransactionDefinition.PROPAGATION_NESTED:<br><ul><li>如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,<br>则该取值等价于 TransactionDefinition.PROPAGATION_REQUIRED。</li></ul>
4. TransactionDefinition.PROPAGATION_MANDATORY:<br><ul><li>如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)这个使用的很少。</li></ul>
若是 <b><font color="#0000ff">错误的配置以下 3 种事务传播行为,事务将不会发生回滚</font></b>:<br><br><ul><li>TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。</li><li>TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。</li><li>TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。</li></ul>
Spring 事务中的隔离级别有哪几种?
和事务传播行为这块一样,为了方便使用,Spring 也相应地定义了一个枚举类:Isolation
<ul><li>TransactionDefinition.ISOLATION_DEFAULT : 使用后端数据库默认的隔离级别,MySQL 默认<br>采用的 REPEATABLE_READ 隔离级别,Oracle 默认采用的 READ_COMMITTED 隔离级别。</li><li>TransactionDefinition.ISOLATION_READ_UNCOMMITTED : <b><font color="#0000ff">最低的隔离级别</font></b>,使用这个隔离级别很少,<br>因为它 <b><font color="#0000ff">允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读</font></b></li><li>TransactionDefinition.ISOLATION_READ_COMMITTED : <b><font color="#0000ff">允许读取并发事务已经提交的数据,可以阻止脏读,<br>但是幻读或不可重复读仍有可能发生</font></b></li><li>TransactionDefinition.ISOLATION_REPEATABLE_READ : <b><font color="#0000ff">对同一字段的多次读取结果都是一致的</font></b>,除非数据是<br>被本身事务自己所修改,<b><font color="#0000ff">可以阻止脏读和不可重复读,但幻读仍有可能发生。</font></b></li><li>TransactionDefinition.ISOLATION_SERIALIZABLE : <b><font color="#0000ff">最高的隔离级别</font></b>,完全服从 ACID 的隔离级别。所有的事务<br>依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,<b><font color="#0000ff">该级别可以防止脏读、不可重复读以及幻读</font></b>。<br>但是这将严重影响程序的性能。通常情况下也不会用到该级别。</li></ul>
@Transactional(rollbackFor = Exception.class)注解了解吗?<br>
Exception 分为运行时异常 RuntimeException 和非运行时异常。事务管理对于<br>企业应用来说是至关重要的,即使出现异常情况,它也可以保证数据的一致性。<br>
当 @Transactional 注解作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该<br>标注来覆盖类级别的定义。如果类或者方法加了这个注解,那么这个类里面的方法抛出异常,就会回滚,数据库里面的数据也会回滚。<br>
在 @Transactional 注解中如果不配置 rollbackFor 属性,那么事务只会在遇到 RuntimeException <br>的时候才会回滚,<b><font color="#0000ff">加上 rollbackFor=Exception.class,可以让事务在遇到非运行时异常时也回滚</font></b>。<br>
0 条评论
下一页