Spring 进阶:SpringMVC、AOP 与声明式事务
学习 Spring 时整理的进阶笔记:SpringMVC 的执行流程与请求处理、AOP 的两种配置方式,以及声明式事务的隔离级别与传播行为。
这篇文章的目录⌄
- 一、SpringMVC 概述
- SpringMVC 的开发步骤
- 二、SpringMVC 组件解析
- 1. SpringMVC 的执行流程
- 2. 组件解析
- 3. @RequestMapping 注解
- 三、SpringMVC 的数据响应
- 1. 数据响应方式
- 四、SpringMVC 获得请求数据
- 获得集合类型参数
- 请求数据乱码问题
- 参数绑定注解 @RequestParam
- 获得 Restful 风格的参数
- 自定义类型转换器
- 获得 Servlet 相关 API
- 获得请求头
- 五、SpringMVC 拦截器(interceptor)
- 六、SpringMVC 异常处理
- 七、Spring 的 AOP
- 1. 什么是 AOP 及其作用和优势
- 2. AOP 的底层实现
- 3. AOP 相关概念
- 4. AOP 开发明确的事项
- 5. 基于 XML 的 AOP 开发快速入门
- 6. 切点表达式的写法
- 7. 基于注解的 AOP 开发
- 八、声明式事务控制
- 1. 编程式事务控制相关对象
- 2. 事务隔离级别
- 3. 事务传播行为
- 4. 什么是声明式事务控制
- 5. 基于 XML 的声明式事务控制
- 6. 基于注解的声明式事务控制
承接上一篇 Spring Boot 核心笔记,这篇整理 SpringMVC、AOP 与声明式事务三块进阶内容。
一、SpringMVC 概述
SpringMVC 是一种基于 Java 的实现 MVC 设计模型的请求驱动类型的轻量级 Web 框架,属于 SpringFrameWork 的后续产品,已经融合在 Spring Web Flow 中。
SpringMVC 已经成为目前最主流的 MVC 框架之一,并且随着 Spring 3.0 的发布,全面超越 Struts2,成为最优秀的 MVC 框架。它通过一套注解,让一个简单的 Java 类成为处理请求的控制器,而无须实现任何接口,同时它还支持 RESTful 编程风格的请求。
SpringMVC 的开发步骤
- 导入 SpringMVC 相关坐标(Spring、SpringMVC、Servlet、Jsp 坐标)
- 配置 SpringMVC 核心控制器 DispatcherServlet
- 创建 Controller 类和视图页面
- 使用注解配置 Controller 类中业务方法的映射地址
- 配置 SpringMVC 核心文件 spring-mvc.xml
- 客户端发起请求测试
<!--Spring坐标-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<!--SpringMVC坐标-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<!--Servlet坐标-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<!--Jsp坐标-->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.0</version>
</dependency>
在 web.xml 配置 SpringMVC 的核心控制器 DispatcherServlet:
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
load-on-startup的作用:标记容器是否在启动的时候就加载这个 servlet(实例化并调用其init()方法)。当值为 0 或者大于 0 时,表示容器在应用启动时就加载并初始化这个 servlet;当值小于 0 或者没有指定时,则表示容器在该 servlet 被选择时才会去加载。正数的值越小,优先级越高,应用启动时就越先加载;值相同时容器自己选择顺序。
二、SpringMVC 组件解析
1. SpringMVC 的执行流程
- 用户发送请求至前端控制器 DispatcherServlet。
- DispatcherServlet 收到请求调用 HandlerMapping 处理器映射器。
- 处理器映射器找到具体的处理器(可以根据 xml 配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给 DispatcherServlet。
- DispatcherServlet 调用 HandlerAdapter 处理器适配器。
- HandlerAdapter 经过适配调用具体的处理器(Controller,也叫后端控制器)。
- Controller 执行完成返回 ModelAndView。
- HandlerAdapter 将 controller 执行结果 ModelAndView 返回给 DispatcherServlet。
- DispatcherServlet 将 ModelAndView 传给 ViewResolver 视图解析器。
- ViewResolver 解析后返回具体 View。
- DispatcherServlet 根据 View 进行渲染视图(即将模型数据填充至视图中),响应用户。
2. 组件解析
- 前端控制器 DispatcherServlet:用户请求到达前端控制器,它就相当于 MVC 模式中的 C,是整个流程控制的中心,由它调用其它组件处理用户的请求,降低了组件之间的耦合性。
- 处理器映射器 HandlerMapping:负责根据用户请求找到 Handler 即处理器。SpringMVC 提供了不同的映射器实现不同的映射方式,例如:配置文件方式、实现接口方式、注解方式等。
- 处理器适配器 HandlerAdapter:通过 HandlerAdapter 对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。
- 处理器 Handler:开发中要编写的具体业务控制器。由 DispatcherServlet 把用户请求转发到 Handler,由 Handler 对具体的用户请求进行处理。
- 视图解析器 View Resolver:负责将处理结果生成 View 视图。首先根据逻辑视图名解析成物理视图名(即具体的页面地址),再生成 View 视图对象,最后对 View 进行渲染,将处理结果通过页面展示给用户。
- 视图 View:SpringMVC 框架提供了很多 View 视图类型的支持,包括 jstlView、freemarkerView、pdfView 等,最常用的视图是 jsp。
3. @RequestMapping 注解
作用:用于建立请求 URL 和处理请求方法之间的对应关系。
位置:
- 类上:请求 URL 的第一级访问目录。此处不写的话,就相当于应用的根目录
- 方法上:请求 URL 的第二级访问目录,与类上的
@RequestMapping标注的一级目录一起组成访问虚拟路径
属性:
value:用于指定请求的 URL,它和path属性的作用一样method:用于指定请求的方式params:用于指定限制请求参数的条件,支持简单的表达式,要求请求参数的 key 和 value 必须和配置的一模一样。例如params = {"accountName"}表示请求参数必须有 accountName;params = {"money!100"}表示请求参数中 money 不能是 100
三、SpringMVC 的数据响应
写配置时一定要先加上 mvc 的注解驱动:
<!--mvc的注解驱动-->
<mvc:annotation-driven/>
在方法上添加 @ResponseBody 就可以返回 json 格式的字符串。使用 mvc 的注解驱动代替配置,让 SpringMVC 自动将 User 转换成 json 格式的字符串。
1. 数据响应方式
- 页面跳转:
- 直接返回字符串(返回的字符串与视图解析器的前后缀拼接后跳转)
- 通过 ModelAndView 对象返回(可以通过
addObject()方法向 request 域存储数据,也可以进行页面跳转)
- 回写数据:
- 直接返回字符串
- 返回对象或集合(
@ResponseBody返回 json 格式的字符串)
@RequestMapping(value = "/quick3")
// 如果传有 ModelAndView 作为形参,MVC 框架会帮你注入
public ModelAndView save3(ModelAndView modelAndView){
modelAndView.addObject("username","itheima");
modelAndView.setViewName("success");
return modelAndView;
}
视图解析器的前后缀配置:
<!--配置内部资源视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--prefix = "" 视图名称前缀 suffix = "" 视图名称后缀 -->
<property name="prefix" value="/WEB-INF/views/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
在 SpringMVC 的各个组件中,处理器映射器、处理器适配器、视图解析器称为 SpringMVC 的三大组件。使用 mvc 的注解驱动可以自动加载 RequestMappingHandlerMapping(处理映射器)和 RequestMappingHandlerAdapter(处理器适配器),同时底层默认集成 jackson 进行对象或集合的 json 格式字符串转换。
四、SpringMVC 获得请求数据
MVC 实现数据请求方式:基本类型参数、POJO 类型参数、数组类型参数、集合类型参数。
MVC 获取数据细节:中文乱码问题、@RequestParam 和 @PathVariable、自定义类型转换器、获得 Servlet 相关 API、@RequestHeader 和 @CookieValue、文件上传。
Controller 中的业务方法参数名称与请求参数的 name 一致,参数值会自动映射匹配。
// 基本数据类型的获取
@RequestMapping("/quick9")
@ResponseBody
public void quickMethod9(String username,int age) throws IOException {
System.out.println(username);
System.out.println(age);
}
// POJO 数据类型的获取
@RequestMapping("/quick10")
@ResponseBody
public void quickMethod10(User user) throws IOException {
System.out.println(user);
}
// 数组数据类型的获取
@RequestMapping("/quick11")
@ResponseBody
public void quickMethod11(String[] strs) throws IOException {
System.out.println(Arrays.asList(strs));
}
获得集合类型参数
一般情况:获得集合参数时,要将集合参数包装到一个 POJO 中才可以。
// 用于封装的类
public class VO {
private List<User> userList;
// getter/setter ...
}
前端表单:userList 要与封装类中的属性名对应,[0] 表示第几个对象,username 表示对象中的属性。
<form action="${pageContext.request.contextPath}/quick12" method="post">
<input type="text" name="userList[0].username"><br>
<input type="text" name="userList[0].age"><br>
<input type="text" name="userList[1].username"><br>
<input type="text" name="userList[1].age"><br>
<input type="submit" value="提交"><br>
</form>
特殊情况:当使用 ajax 提交时,可以指定 contentType 为 json 形式,那么在方法参数位置使用 @RequestBody 可以直接接收集合数据而无需使用 POJO 进行包装。
请求数据乱码问题
当 post 请求时数据会出现乱码,可以设置一个过滤器来进行编码的过滤:
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
参数绑定注解 @RequestParam
注解 @RequestParam 还有如下参数可以使用:
value:与请求参数名称required:指定的请求参数是否必须包括,默认是 true,提交时如果没有此参数则报错defaultValue:当没有指定请求参数时,则使用指定的默认值赋值
其实就是设置别名,来获取表单中的数据。
// 后端代码
@RequestMapping("/quick14")
@ResponseBody
public void quickMethod14(@RequestParam("name") String username) throws IOException {
System.out.println(username);
}
@RequestParam("name") 中的 name 就是前端的参数名,其后的 username 就是后端起的别名。
获得 Restful 风格的参数
Restful 是一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。主要用于客户端和服务器交互类的软件,基于这个风格设计的软件可以更简洁、更有层次、更易于实现缓存机制。
Restful 风格的请求使用”url + 请求方式”表示一次请求目的,HTTP 协议里面四个表示操作方式的动词如下:
- GET:用于获取资源
- POST:用于新建资源
- PUT:用于更新资源
- DELETE:用于删除资源
例如:
/user/1GET:得到 id = 1 的 user/user/1DELETE:删除 id = 1 的 user/user/1PUT:更新 id = 1 的 user/userPOST:新增 user
上述 url 地址 /user/1 中的 1 就是要获得的请求参数,在 SpringMVC 中可以使用占位符进行参数绑定:地址 /user/1 可以写成 /user/{id},占位符 {id} 对应的就是 1 的值,在业务方法中可以使用 @PathVariable 注解进行占位符的匹配获取工作。
自定义类型转换器
SpringMVC 默认已经提供了一些常用的类型转换器,例如客户端提交的字符串转换成 int 型进行参数设置。但不是所有的数据类型都提供了转换器,没有提供的就需要自定义转换器,例如日期类型的数据就需要自定义转换器。
自定义类型转换器的开发步骤:
- 定义转换器类实现 Converter 接口
- 在配置文件中声明转换器
- 在
<annotation-driven>中引用转换器
// 第一个参数是接受的参数类型,第二个参数是要转换成的类型
public class DateConverter implements Converter<String, Date> {
public Date convert(String dateStr) {
// 将日期字符串转换成日期对象返回
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
Date date = null;
try {
date = format.parse(dateStr);
} catch (ParseException e) {
e.printStackTrace();
}
return date;
}
}
<bean id="converterService"
class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<list>
<bean class="com.itheima.converter.DateConverter"/>
</list>
</property>
</bean>
<mvc:annotation-driven conversion-service="converterService"/>
获得 Servlet 相关 API
SpringMVC 支持使用原始 ServletAPI 对象作为控制器方法的参数进行注入,常用的对象如下:HttpServletRequest、HttpServletResponse、HttpSession。
@RequestMapping("/quick16")
@ResponseBody
public void quickMethod16(HttpServletRequest request, HttpServletResponse response,
HttpSession session){
System.out.println(request);
System.out.println(response);
System.out.println(session);
}
获得请求头
使用 @RequestHeader 可以获得请求头信息,相当于 web 阶段学习的 request.getHeader(name)。
@RequestMapping("/quick17")
@ResponseBody
public void quickMethod17(@RequestHeader(value = "User-Agent", required = false) String headerValue){
System.out.println(headerValue);
}
使用 @CookieValue 可以获得指定 Cookie 的值。
@RequestMapping("/quick18")
@ResponseBody
public void quickMethod18(@CookieValue(value = "JSESSIONID", required = false) String jsessionid){
System.out.println(jsessionid);
}
五、SpringMVC 拦截器(interceptor)
Spring MVC 的拦截器类似于 Servlet 开发中的过滤器 Filter,用于对处理器进行预处理和后处理。将拦截器按一定的顺序联结成一条链,这条链称为拦截器链(Interceptor Chain)。在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。拦截器也是 AOP 思想的具体实现。
自定义拦截器的开发步骤:
- 创建拦截器类实现 HandlerInterceptor 接口
- 配置拦截器
- 测试拦截器的拦截效果
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class MyInterceptor1 implements HandlerInterceptor {
// 在目标方法执行之前 执行
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException {
// 设置需要的程序
}
// 在目标方法执行之后 视图对象返回之前执行
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
// 设置需要的程序
}
// 在流程都执行完毕后 执行
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
// 设置需要的程序
}
}
配置拦截器:
<!--配置拦截器-->
<mvc:interceptors>
<mvc:interceptor>
<!--对哪些资源执行拦截操作-->
<mvc:mapping path="/**"/>
<bean class="com.itheima.interceptor.MyInterceptor2"/>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.itheima.interceptor.MyInterceptor1"/>
</mvc:interceptor>
</mvc:interceptors>
六、SpringMVC 异常处理
系统中异常包括两类:预期异常和运行时异常 RuntimeException。前者通过捕获异常从而获取异常信息,后者主要通过规范代码开发、测试等手段减少运行时异常的发生。
系统的 Dao、Service、Controller 出现异常都通过 throws Exception 向上抛出,最后由 SpringMVC 前端控制器交由异常处理器进行异常处理。
异常处理两种方式:
- 使用 Spring MVC 提供的简单异常处理器 SimpleMappingExceptionResolver
- 实现 Spring 的异常处理接口 HandlerExceptionResolver,自定义自己的异常处理器
自定义异常处理步骤:
- 创建异常处理器类实现 HandlerExceptionResolver
- 配置异常处理器
- 编写异常页面
- 测试异常跳转
public class MyExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex) {
// 处理异常的代码实现
// 创建 ModelAndView 对象
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("exceptionPage");
return modelAndView;
}
}
<bean id="exceptionResolver" class="com.itheima.exception.MyExceptionResolver"/>
七、Spring 的 AOP
1. 什么是 AOP 及其作用和优势
AOP 为 Aspect Oriented Programming 的缩写,意思为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
AOP 是 OOP(面向对象)的延续,是软件开发中的一个热点,也是 Spring 框架中的一个重要内容,是函数式编程的一种衍生范型。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
- 作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强
- 优势:减少重复代码,提高开发效率,并且便于维护
2. AOP 的底层实现
AOP 的底层是通过 Spring 提供的动态代理技术实现的。在运行期间,Spring 通过动态代理技术动态地生成代理对象,代理对象方法执行时进行增强功能的介入,再去调用目标对象的方法,从而完成功能的增强。
常用的动态代理技术:
- JDK 代理:基于接口的动态代理技术
- cglib 代理:基于父类的动态代理技术
3. AOP 相关概念
- Target(目标对象):代理的目标对象(被增强的对象)
- Proxy(代理):一个类被 AOP 织入增强后,就产生一个结果代理类
- Joinpoint(连接点):被拦截到的点。在 Spring 中这些点指的是方法,因为 Spring 只支持方法类型的连接点(可以被增强的方法)
- Pointcut(切入点):要对哪些 Joinpoint 进行拦截的定义(真正配置去增强的方法,范围比连接点小)
- Advice(通知/增强):拦截到 Joinpoint 之后所要做的事情(封装增强业务逻辑的方法)
- Aspect(切面):切入点和通知(引介)的结合(切点 + 增强)
- Weaving(织入):把增强应用到目标对象来创建新的代理对象的过程。Spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入
4. AOP 开发明确的事项
- 需要编写的内容:
- 编写核心业务代码(目标类的目标方法)
- 编写切面类,切面类中有通知(增强功能方法)
- 在配置文件中配置织入关系,即将哪些通知与哪些连接点进行结合
- AOP 技术实现的内容:
- Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制动态创建目标对象的代理对象,根据通知类别在代理对象的对应位置将通知对应的功能织入,完成完整的代码逻辑运行
- AOP 底层使用哪种代理方式:在 Spring 中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式
5. 基于 XML 的 AOP 开发快速入门
开发步骤:
- 导入 AOP 相关坐标
- 创建目标接口和目标类(内部有切点)
- 创建切面类(内部有增强方法)
- 将目标类和切面类的对象创建权交给 spring
- 在 applicationContext.xml 中配置织入关系
- 测试代码
<!--导入spring的context坐标,context依赖aop-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<!-- aspectj的织入 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
// 接口类
public interface TargetInterface {
public void save();
}
// 实现类(目标类)
public class Target implements TargetInterface {
public void save() {
System.out.println("save running.....");
}
}
// 切面类
public class MyAspect {
// 前置增强方法
public void before() {
System.out.println("前置代码增强.....");
}
}
配置织入关系:
<!--配置目标类-->
<bean id="target" class="com.itheima.aop.Target"></bean>
<!--配置切面类-->
<bean id="myAspect" class="com.itheima.aop.MyAspect"></bean>
<!--配置织入:告诉spring框架 哪些方法(切点)需要进行哪些增强(前置、后置...)-->
<aop:config>
<!--引用myAspect的Bean为切面对象-->
<aop:aspect ref="myAspect">
<!--配置Target的method方法执行时要进行myAspect的before方法前置增强-->
<aop:before method="before" pointcut="execution(public void com.itheima.aop.Target.method())"></aop:before>
</aop:aspect>
</aop:config>
6. 切点表达式的写法
表达式语法:
execution([修饰符] 返回值类型 包名.类名.方法名(参数))
- 访问修饰符可以省略
- 返回值类型、包名、类名、方法名可以使用星号
*代表任意 - 包名与类名之间一个点
.代表当前包下的类,两个点..表示当前包及其子包下的类 - 参数列表可以使用两个点
..表示任意个数、任意类型的参数列表
例如:
execution(public void com.itheima.aop.Target.method()):com 包下的 itheima 包下的 aop 包下的 Target 类下的 method 方法(无参)并且无返回值execution(void com.itheima.aop.Target.*(..)):Target 类下的任意方法并且是任意参数并且无返回值execution(* com.itheima.aop.*.*(..)):aop 包下的任意类的任意方法、任意参数、任意返回值execution(* com.itheima.aop..*.*(..)):aop 包及其子包下的任意类的任意方法execution(* *..*.*(..)):任意包的任意类的任意方法
7. 基于注解的 AOP 开发
开发步骤:
- 创建目标接口和目标类(内部有切点)
- 创建切面类(内部有增强方法)
- 将目标类和切面类的对象创建权交给 spring(使用
@Component标注) - 在切面类中使用注解配置织入关系(使用
@Aspect标注切面类、@通知注解标注通知方法) - 在配置文件中开启组件扫描和 AOP 的自动代理
- 测试
// 目标类
@Component("target")
public class Target implements TargetInterface {
@Override
public void method() {
System.out.println("Target running....");
}
}
// 切面类
@Component("myAspect")
@Aspect
public class MyAspect {
@Before("execution(* com.itheima.aop.*.*(..))")
public void before() {
System.out.println("前置代码增强.....");
}
}
配置文件开启组件扫描和 AOP 自动代理:
<!--组件扫描-->
<context:component-scan base-package="com.itheima.anno"/>
<!--aop自动代理-->
<aop:aspectj-autoproxy/>
切点表达式的抽取:在切面内定义方法,在该方法上使用 @Pointcut 注解定义切点表达式,然后在增强注解中进行引用。
@Component("myAspect")
@Aspect
public class MyAspect {
@Before("MyAspect.myPoint()")
public void before() {
System.out.println("前置代码增强.....");
}
// 只用创建一个空方法,用来写注解的
@Pointcut("execution(* com.itheima.aop.*.*(..))")
public void myPoint() {}
}
八、声明式事务控制
1. 编程式事务控制相关对象
编程式事务控制三大对象:
PlatformTransactionManager:Spring 的事务管理器,提供常用的事务操作方法。它是接口类型,不同的 Dao 层技术有不同的实现类:Dao 层技术是 jdbc 或 mybatis 时,使用org.springframework.jdbc.datasource.DataSourceTransactionManager;Dao 层技术是 hibernate 时,使用org.springframework.orm.hibernate5.HibernateTransactionManagerTransactionDefinition:事务的定义信息对象,包含事务隔离级别、事务传播行为、超时时间(默认 -1,没有超时限制,以秒为单位设置)、是否只读(建议查询时设置为只读)TransactionStatus:事务具体的运行状态
2. 事务隔离级别
设置隔离级别可以解决事务并发产生的问题,如脏读、不可重复读和虚读。
| 隔离级别 | 说明 |
|---|---|
ISOLATION_DEFAULT | 使用后端数据库默认的隔离级别 |
ISOLATION_READ_UNCOMMITTED | 允许读取尚未提交的更改,可能导致脏读、幻影或不可重复读 |
ISOLATION_READ_COMMITTED | 从已经提交的并发事务读取 |
ISOLATION_REPEATABLE_READ | 对相同字段的多次读取的结果是一致的,除非数据被当前事务本身改变 |
ISOLATION_SERIALIZABLE | 完全服从 ACID 的隔离级别,确保不发生脏读、不可重复读和幻读 |
3. 事务传播行为
| 传播行为 | 说明 |
|---|---|
REQUIRED | 如果当前没有事务,就新建一个事务;如果已经存在一个事务,就加入到这个事务中。一般的选择(默认值) |
SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行 |
MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常 |
REQUIRES_NEW | 新建事务,如果当前在事务中,把当前事务挂起 |
NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起 |
NEVER | 以非事务方式运行,如果当前存在事务,抛出异常 |
NESTED | 如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则执行 REQUIRED 类似的操作 |
4. 什么是声明式事务控制
Spring 的声明式事务顾名思义就是采用声明的方式来处理事务。这里所说的声明,就是指在配置文件中声明,用声明式的处理事务来代替代码式的处理事务。
声明式事务处理的作用:
- 事务管理不侵入开发的组件。业务逻辑对象不会意识到正在事务管理之中,因为事务管理属于系统层面的服务,而不是业务逻辑的一部分
- 在不需要事务管理的时候,只要在设定文件上修改一下即可移去事务管理服务,无需改变代码重新编译,维护起来极其方便
注意:Spring 声明式事务控制底层就是 AOP。
5. 基于 XML 的声明式事务控制
实现步骤:
- 导入 AOP 相关坐标
- 配置目标对象(切点)
- 引入 tx 命名空间
- 配置事务增强
- 配置事务 AOP 织入
<!--配置平台事务管理器,根据需要底层运用的技术选择平台-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--transactionManager 底层需要从 dataSource 拿 connection 进行事务控制-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--通知 事务的增强-->
<!--transaction-manager 平台事务管理器 所以需要配置-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--设置事务的属性信息-->
<tx:attributes>
<!--tx:method 哪些方法被增强 name="被增强的方法名称" isolation-事务隔离级别 propagation-事务传播行为 read-only-只读属性-->
<tx:method name="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/>
<tx:method name="save" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/>
<tx:method name="findAll" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="true"/>
<tx:method name="update*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!--配置事务的aop织入-->
<aop:config>
<!--切点表达式的提取-->
<aop:pointcut id="txPointcut" expression="execution(* com.itheima.service.impl.*.*(..))"/>
<!--事务增强用 <aop:advisor/> advice-ref 通知引用 pointcut-ref 切点表达式-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
其中 <tx:method> 代表切点方法的事务参数的配置,例如:
<tx:method name="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED" timeout="-1" read-only="false"/>
name:切点方法名称isolation:事务的隔离级别propagation:事务的传播行为timeout:超时时间read-only:是否只读
6. 基于注解的声明式事务控制
注解声明式事务控制的配置要点:
- 平台事务管理器配置(xml 方式)
- 事务通知的配置(
@Transactional注解配置) - 事务注解驱动的配置:
<tx:annotation-driven transaction-manager="transactionManager"/>
@Service("accountService")
@Transactional(isolation = Isolation.REPEATABLE_READ)
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
public void transfer(String outMan, String inMan, double money) {
accountDao.out(outMan, money);
int i = 1 / 0;
accountDao.in(inMan, money);
}
}
使用要点:
- 使用
@Transactional在需要进行事务控制的类或是方法上修饰,注解可用的属性同 xml 配置方式,例如隔离级别、传播行为等 - 注解使用在类上,那么该类下的所有方法都使用同一套注解参数配置
- 使用在方法上,不同的方法可以采用不同的事务参数配置
- Xml 配置文件中要开启事务的注解驱动:
<tx:annotation-driven transaction-manager="transactionManager"/>