JavaEE——SpringMVC
目录
- 1.SpringMVC 架构
-
- 1.1 介绍
- 1.2 Spring-MVC 组件
- 2. SpringMVC 入门
-
- 2.1.创建maven项目
- 2.2:在 pom 中导入依赖的 jar 包
- 2.3 第三步:前端控制器配置
- 2.4 第四步:创建 springmvc 配置文件
- 2.5 第五步:处理器开发
- 2.6 第六步:注册组件扫描
- 2.7 第七步:注册注解驱动
- 2.8 第八步:配置视图解析器
- 2.9 第九步:创建视图
- 2.10 :部署在 tomcat 测试
- 3. 请求流程
-
- 第 1 站:DispatcherServlet
- 请求流程第 3 站:handlerMapping
-
- handlerMapping 的配置
- 请求流程第 4 站:handlerAdapter
-
- handlerAdapter 的配置
- 请求流程第 5 站:Handler
-
- @RequestMapping :
- 设置请求前缀
- 请求方式限定
- @GetMapping
- @PostMapping
- 请求转发
- 重定向
- controller 方法返回值
-
- 返回 ModelAndView
- 返回 void
- 返回字符串
- Controller 的参数绑定
-
- 默认支持的参数类型
- 参数绑定映射
- 简单类型
- pojo
- VO
- 自定义参数绑定
- 集合类参数绑定
- Post 提交中文乱码
- 数据验证 validator
-
- 分组校验
- 校验注解
- 数据回显
-
- 需求
- 简单数据类型回显
- pojo 类型自动回显
- Model,ModelMap,HashMap 回显
- @ModelAttribute
- 异常处理
-
- 异常处理的思路
- 自定义异常类
- 自定义异常处理器
- 异常处理器配置
- 错误页面
- 异常处理测试
- 上传文件
- AJAX
-
- @RequestBody
- @ResponseBody
- 请 key/value,响应 json 实现
- RESTful 支持
- 静态资源访问
- 请求流程第 6 站:视图解析
- 请求流程第 2 站拦截器
-
- 定义拦截器
- 配置拦截器链
- 测试拦截器(正常流程)
- 测试拦截器(中断流程)
- 权限拦截
- 分页查询
-
- PageInfo 类的属性
- 事务控制
-
- 编程式事务
- 声明式事务环境搭建
-
- 第一步:导入 jar 包
- 第二步:配置事务管理器
- 声明式事务使用
-
- 使用方式 1:使用注解@Transactional 管理事务
- 使用方式 2:基于 xml 配置管理事务
- 事务的提交行为
- 不需要事务管理的方法
- 事务的传播行为
- 事务的隔离性
1.SpringMVC 架构
1.1 介绍
在 MVC 设计模式下的 Web 编程,无论使用什么技术,什么框架,无非是要解决以下 5
个问题,如下图所示:
1:视图向控制器发出请求并提交数据
2:控制器获取数据、对数据进行相应的类型转换、对数据进行验证、调用模型
3:模型进行业务处理,并将业务处理后的数据返回给控制器
4:控制器再将数据响应给视图
5:视图对响应的数据进行渲染显示
之前用的是 JSP 调用 Servlet,Servlet 调用 JavaBean 是 MVC 设计模式的实现。JSP 是视图、Servlet 是控制器、JavaBean 是模型。
Spring Web MVC 也是一种基于 MVC 设计模式的、请求驱动类型的轻量级 Web 框架。
是 Spring 框架的一个模块(如下图所示)。既然是框架,那么大多数开发人员需要的功能框架都已经实现了,开发人员只需在框架上的基础上,完成个性化的需求。
Spring Web MVC 也是服务到工作者模式的实现,但进行可优化。前端控制器是DispatcherServlet;应用控制器被拆为处理器映射器(Handler Mapping)和视图解析器(View Resolver);处理器为Controller 接口(仅包含 ModelAndView handleRequest(request, response) 方法)的实现(也可以是任何的 POJO 类);支持本地化(Locale)解析、主题(Theme)解析及文件上传等;提供了非常灵活的数据验证、格式化和数据绑定机制;提供了强大的约定大于配置(惯例优先原则)的契约式编程支持。
这些单词在 SpringMVC 中将会反复出现。
架构图如下:
具体执行步骤如下:
- 用户发送请求至前端控制器DispatcherServlet。
- 前端控制器DispatcherServlet接收请求后,调用处理器映射器HandlerMapping。
- 处理器映射器HandlerMapping根据请求的url找到处理该请求的处理器Handler(即Controller),将处理器Handler返回给前端控制器DispatcherServlet。
- 前端控制器DispatcherServlet通过处理器适配器HandlerAdapter调用处理器Handler。
- 执行处理器Handler(即Controller,也叫后端控制器或应用控制器)。
- 处理器Handler执行完成后,返回ModelAndView(ModelAndView:实体数据和视图)给处理器适配器HandlerAdapter。
- 处理器适配器HandlerAdapter将处理器Handler执行的结果ModelAndView返回给前端控制器DispatcherServlet。
- 前端控制器DispatcherServlet将ModelAndView传给视图解析器ViewReslover。
- 视图解析器ViewReslover解析后返回具体视图View。
- 前端控制器DispatcherServlet对视图View进行渲染(即将模型数据填充至视图中)。
- 前端控制器DispatcherServlet响应用户。
Spring MVC提供的组件包括
- DispatcherServlet
- HandlerMapping
- HandlerAdapter
- ViewReslover
1.2 Spring-MVC 组件
DispatcherServlet:Spring 中提供了org.springframework.web.servlet.DispatcherServlet 类,它从 HttpServlet 继承而来,它就是Spring MVC 中的前端控制器(Front controller) ;
HandlerMapping: DispatcherServlet 自己并不处理请求,而是将请求交给页面控制器。那么在 DispatcherServlet 中如何选择正确的页面控制器呢?这件事情就交给HandlerMapping 来做了,经过了 HandlerMapping 处理之后,DispatcherServlet 就知道要将请求交给哪个页面控制器来处理了。
HandlerAdapter:经过了 HandlerMapping 处理之后,DispatcherServlet 就获取到了处理器,但是处理器有多种,为了方便调用,DispatcherServlet 将这些处理器包装成处理器适配器 HandlerAdapter,HandlerAdapter 调用真正的处理器的功能处理方法,完成功能处理;并返回一个 ModelAndView 对象(包含模型数据、逻辑视图名);
ModelAndView:DispatcherServlet 取得了 ModelAndView 之后,需要将把逻辑视图名解析为具体的 View,比如 jsp 视图,pdf 视图等,这个解析过程由 ViewResolver 来完成;
ViewResolver:ViewResolver 将把逻辑视图名解析为具体的 View,通过这种策略模式,很容易更换其他视图技术
View:DispatcherServlet 通过 ViewResolver 取得了具体的 view 之后,就需要将 model 中的数据渲染到视图上,最终 DispatcherServlet 将渲染的结果响应到客户端。
2. SpringMVC 入门
2.1.创建maven项目
1.输入GAV
G:com.ltw
A:eshop
V:0.0.1-SNAPSHOT
2.2:在 pom 中导入依赖的 jar 包
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.westos</groupId> <artifactId>demo02</artifactId> <version>1.0-SNAPSHOT</version> <name>demo02</name> <packaging>war</packaging> <properties> <maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.source>1.8</maven.compiler.source> <springmvc-version>5.3.13</springmvc-version> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${springmvc-version}</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.2</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.22</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.71</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.3.5.Final</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>3.3.0</version> </plugin> </plugins> </build></project>
2.3 第三步:前端控制器配置
在 WEB-INF\web.xml 中配置前端控制器
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"><servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
解析:
(1) load-on-startup:表示servlet随服务启动;
(2) *.action
a) url中以.action结尾的请求交给DispatcherServlet处理,请求进入springmvc框
架。
b) url中不以.action结尾的请求不交给DispatcherServlet处理,不进入springmvc
框架。
(3) contextConfigLocation:指定springmvc配置文件的加载位置,如果不指定则默认加载WEB-INF/[DispatcherServlet 的Servlet 名字]-servlet.xml。
2.4 第四步:创建 springmvc 配置文件
使用向导在src/main/resources包下创建springmvc.xml。
开发环境中的src/main/resources包将映射到运行环境下的classpath目录。
Springmvc配置文件的声明如下:
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:mvc="http://www.springframework.org/schema/mvc"xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsdhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd"> <context:component-scan base-package="com.ltw"/> <mvc:annotation-driven/> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/> <property name="prefix" value="/WEB-INF/views/"/> <property name="suffix" value=".jsp"/> </bean></beans>
2.5 第五步:处理器开发
@Controllerpublic class HelloController { @RequestMapping("/hello") @ResponseBody public String hello(){ return "Hello World!! 我是springmvc的控制器"; }
2.6 第六步:注册组件扫描
组件扫描用于实现 IoC,即 spring 识别类上标注的@Controller,@Service,@Repository,@Component 注解,将标注了这些注解的类实例化,将实例化的对象由 spring 容器管理。
<context:component-scan base-package="org.westos"/>
2.7 第七步:注册注解驱动
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:mvc="http://www.springframework.org/schema/mvc"xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsdhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd"> <context:component-scan base-package="org.westos"/> <mvc:annotation-driven/> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/> <property name="prefix" value="/WEB-INF/views/"/> <property name="suffix" value=".jsp"/> </bean></beans>
解析:mvc:annotation-driver主要做了以下两件事
- 注册了 RequestMappingHandlerMapping 和 RequestMappingHandlerAdapter 的bean 对象。
- 存储了请求 url 到 Controller 方法的映射关系
2.8 第八步:配置视图解析器
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/> <property name="prefix" value="/WEB-INF/views/"/> <property name="suffix" value=".jsp"/> </bean>
解析:
(1) InternalResourceViewResolver:支持JSP视图解析
(2) viewClass:JstlView表示JSP模板页面需要使用JSTL标签库,所以classpath中必须包含JSTL的相关jar包;
(3) prefix 和suffix:查找视图页面的前缀和后缀,最终视图的址为:前缀+逻辑视图名+后缀,逻辑视图名需要在controller中返回ModelAndView指定,比如逻辑视图名为hello,则最终返回的jsp视图地址 “WEB-INF/views/hello.jsp”
完整的 springmvc.xml 配置文件如下
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:mvc="http://www.springframework.org/schema/mvc"xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsdhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd"> <context:component-scan base-package="org.westos"/> <mvc:annotation-driven/> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/> <property name="prefix" value="/WEB-INF/views/"/> <property name="suffix" value=".jsp"/> </bean></beans>
2.9 第九步:创建视图
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> ${msg } </body> </html>
2.10 :部署在 tomcat 测试
(1) 启动 tomcat
(2) 在浏览器中输入:http://localhost:8080/hello
3. 请求流程
第 1 站:DispatcherServlet
Spring MVC 架构如下图所示
DispatcherServlet 是前端控制器,用于接受客户端的请求,并向客户端响应结果。 DispatcherServlet 从 HttpServlet 继承而 来 ,所以它 是 一个 标 准 的 Servlet , 因 此DispatcherServlet 能够进行请求和响应。
在 web.xml 中启动 DispatcherServlet 时配置哪些请求进入 DispatcherServlet。
<servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
配置进入 DispatcherServlet 的请求
1、 拦截固定后缀的url,
比如设置为*.action时,只拦截以action结尾的请求。
例如:/user/add.action ,
此方法不会导致静态资源(jpg,js,css)被拦截。
2、 拦截所有
设置为/时,拦截所有的请求。
例如:/user/add或/user/add.action
此方法会导致静态文件(jpg,js,css)被拦截后不能正常显示。需要配置静态资源访问的特殊处理后,静态资源才不被拦截。
此方法可以实现REST风格的url。
3、 拦截所有,
设置为/*,此设置方法错误,因为请求到controller,当
controller转到jsp时再次被拦截,提示不能根据jsp路径mapping成功。
如何加载 springmvc 的配置文件
在启动 tomcat 的时候加载 Springmvc 的配置文件,springmvc 的配文件必须放在 classpath下,因此在 maven 项目中,springmvc 配置文件需要放在 src/main/resources 目录下。那么springmvc 配置文件的文件名是什么?文件名有两种情况。
第一种:约定的配置文件名
解析:
(1) 配置文件的名是/WEB-INF/【servlet-name】_servlet.xml。
(2) 本例中为/WEB-INF/springmvc_servlet.xml
第二种:自定义的配置文件名
<servlet> <servlet-name>springmvc</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
解析:
(1) init-param:可自定义配置文件的文件名。
(2) load-on-startup 表示前端控制器是否随服务一起启动。
请求流程第 3 站:handlerMapping
handlerMapping 的作用 :handlerMapping 的作用是将请求的 url 映射到控制器的方法。
handlerMapping 的配置
配置解析:
annotation-driven 配置向 springmvc 注册了注解解析器,注册的注解解析器能够解析
@RequestMapping,@GetMapping,@PostMapping。
请求流程第 4 站:handlerAdapter
handlerAdapter 的作用 :handlerMapping 将 url 映射到控制器的方法后,由 handlerAdapter 调用控制器的方法。
handlerAdapter 的配置
配置解析;
annotation-driven 配置注册了注解解析器,配置的注解解析器能够调用 Controller 的方法,完成对用户请求的处理。
请求流程第 5 站:Handler
Handler 是 MVC 中的 Controller
url 映射到 controller 的方法
@RequestMapping :
通过 RequestMapping 注解可以定义不同的处理器映射规则。
URL 路径映射
@RequestMapping(value="/goods")或@RequestMapping("/goods)
解析:
(1) 当只有一个属性值时,属性是赋值给 value 键的,此时 value 键名可以略。
(2) value 的值是数组,可以将多个 url 映射到同一个方法。例如:
访问方式 1:http://localhost:8080/index
访问方式 2:http://localhost:8080/home
设置请求前缀
在 class 上添加@RequestMapping(url)指定通用请求前缀, 限制此类下的所有方法请求url 必须以请求前缀开头,通过此方法对 url 进行分类管理
@Controller @RequestMapping("/goods") public class HomeController { @RequestMapping("/list") public String home(Model model) { model.addAttribute("msg","goods list"); return "home"; } @RequestMapping("/insert") public String home(Model model) { model.addAttribute("msg","goods insert"); return "home"; } }
访问地址分别为:
/goods/list
/goods/insert
表示 list 和 insert 都是对 goods 表的操作,实现了分类管理
请求方式限定
请求方式有 get 和 post,可以限定请求的方式。比如只允许 get 请求。
限定 GET 方法
@RequestMapping(value="/editGoods",method=RequestMethod.GET)
如果通过 Post 访问则报错:
HTTP Status 405 - Request method ‘POST’ not supported
限定 POST 方法
@RequestMapping(value="/editGoods",method=RequestMethod.POST)
如果通过 get 访问则报错:
HTTP Status 405 - Request method ‘GET’ not supported
GET 和 POST 都可以
@RequestMapping(value="/editGoods",method={RequestMethod.GET,RequestMethod.POST})
@GetMapping
@GetMapping 仅限于 get 请求,例如:
@GetMapping(value = "/home") public String home(Model model) { System.out.println("home"); model.addAttribute("msg","hello springmvc"); return "home"; }
如果使用 post 请求,报如下错误:
@PostMapping
@PostMapping 仅限于 post 请求,例如:
@PostMapping(value = "/home") public String home(Model model) { System.out.println("home"); model.addAttribute("msg","hello springmvc"); return "home"; }
如果使用 get 请求,报出如下错误
请求转发
在 Spring MVC 框架中,控制器类中处理方法的 return 语句默认就是转发实现,只不过实现的是转发到视图。示例代码如下:
@RequestMapping("/register") public String register() {return "register"; //转发到 register.jsp }
@Controller @RequestMapping("/index") public class IndexController{@RequestMapping("/login") public String login() { //转发到一个请求方法(同一个控制器类可以省略/index/) return "forward:/index/isLogin"; }@RequestMapping("/isLogin") public String isLogin() { //重定向到一个请求方法 return "redirect:/index/isRegister"; }@RequestMapping("/isRegister") public String isRegister() { //转发到一个视图 return "register"; } }
重定向
@RequestMapping("/isLogin") public String isLogin() { //重定向到一个请求方法 return "redirect:/index/isRegister"; }
controller 方法返回值
在 MVC 架构中,controller 返回值需要包含视图的 URL 和视图中显示的数据。
视图的 URL 可以是 JSP 文件,显示的数据通常来自于数据库。
返回 ModelAndView
controller 方法中定义 ModelAndView 对象并返回,其中 model 是视图中要显示的数据,view是视图的名称(如 jsp 文件名)。
示例:返回 ModelAndView
控制器
@Controller @RequestMapping("/goods") public class HomeController { @RequestMapping("/list") public ModelAndView home() { List<GoodsModel> models = new ArrayList<GoodsModel>(); models.add(new GoodsModel(1, "平谷大桃", 20d, "新鲜", "images/pg.jpg", new Date())); models.add(new GoodsModel(2, "平谷大桃", 22d, "新鲜", "images/pg.jpg", new Date())); ModelAndView modelAndView =new ModelAndView(); modelAndView.addObject("models",models); modelAndView.setViewName("home"); return modelAndView; } }
modelAndView 中保存的数据会被 springMVC 放到 request 作用域中,界面上可通过${}显示 modelAndView 中的数据。
视图
列表<br> <c:forEach items="${models }" var="item"> ${item.goodsname },${item.price }<br /> </c:forEach>
返回 void
在 controller 方法形参上可以定义 request 和 response,在 controller 中就可有使用 request和 response 了。
1、使用 request 转向页面,如下:
request.getRequestDispatcher(“页面路径”).forward(request, response);
@Controller @RequestMapping("/goods") public class HomeController { @RequestMapping("/list") public void home(HttpServletRequest request,HttpServletResponse response) { try {request.getRequestDispatcher("/index.jsp").forward(request, response); } catch (Exception e) {e.printStackTrace(); } } }
2、也可以通过 response 页面重定向:
response.sendRedirect(“url”)
@Controller @RequestMapping("/goods") public class HomeController { @RequestMapping("/list") public void home(HttpServletRequest request,HttpServletResponse response) { try {response.sendRedirect(request.getContextPath()+"/index.jsp"); } catch (IOException e) {e.printStackTrace(); } } }
3、也可以通过 response 指定响应结果,例如响应 json 数据如下:
@Controller @RequestMapping("/goods") public class HomeController { @RequestMapping("/list") public void home(HttpServletRequest request,HttpServletResponse response) { try {response.setCharacterEncoding("utf-8");response.setContentType("application/json;charset=utf-8");response.getWriter().write("json串"); } catch (IOException e) {e.printStackTrace(); } } }
返回字符串
逻辑视图名
controller 方法返回字符串可以指定逻辑视图名,通过视图解析器解析为物理视图地址。
@Controller @RequestMapping("/goods") public class HomeController { @RequestMapping("/list") public String home() { return "home"; } }
视图解析器为
生成的物理地址为
/WEB-INF/views/home.jsp
Redirect 重定向
Contrller 方法返回结果重定向到一个 url 地址。
@Controller @RequestMapping("/goods") public class HomeController { @RequestMapping("/list") public String home() { return "redirect:/index.jsp"; } }
redirect 方式相当于“response.sendRedirect()”,转发后浏览器的地址栏变为转发后的地址,因为转发即执行了一个新的 request 和 response。由于新发起一个 request 原来的参数在转发时就不能传递到下一个 url,如果要传参数可以/index.jsp 后边加参数,如下: /index.jsp?id=1&……
forward 转发
controller 方法执行后继续执行另一个 controller 方法,例如商品修改提交后转向到商品修改页面,修改商品的 id 参数可以带到商品修改方法中。
@Controller @RequestMapping("/goods") public class HomeController { @RequestMapping("/list") public String home() { return "forward:/goods/update.action"; } @RequestMapping("/update") public String update() { return "home"; } }
forward 方式相当于“request.getRequestDispatcher().forward(request,response)”,转发后浏览器地址栏还是原来的地址。转发并没有执行新的 request 和 response,而是和转发前的请求共用一个 request 和 response。所以转发前请求的参数在转发后仍然可以读取到。
Controller 的参数绑定
处理器适配器在执行 controller 之前需要把 http 请求的 key/value 数据绑定到 controller方法形参上,相当于 request.getParameter(“key”)。
默认支持的参数类型
控制器方法形参中添加如下类型的参数,处理器适配器会默认识别并进行注入。
HttpServletRequest :通过 request 对象获取请求信息
HttpServletResponse :通过 response 处理响应信息
HttpSession :通过 session 对象得到 session 中存放的对象
Model/ModelMap/HashMap :ModelMap 是 Model 接口的实现类,通过 Model 或 ModelMap 向页面传递数据,例如:
//调用service查询商品信息 GoodsModel goods = goodsService.findGoodsById(id); model.addAttribute("goods", goods);
页面通过$ {goods.XXXX}获取 goods 对象的属性值。
使用 Model 和 ModelMap 的效果一样,如果直接使用 Model,springmvc 会实例化 ModelMap。保存到 Model 或 ModelMap 中的数据会被 springmvc 保存到 request 作用域中,因此界面可以通过${}获取。
默认支持的参数类型举例
@GetMapping(value="/home") public String home(HttpServletRequest request, HttpServletResponse response,HttpSession session, Model model,String user,UserModel userModel,int age) { response.setContentType("text/html;charset=utf-8"); String id = request.getParameter("id"); session.setAttribute("id",id); model.addAttribute("msg","hello springmvc"); return "home"; }
参数绑定映射
默认情况下,当请求的参数名称和处理器形参名称一致时会将请求参数与形参进行绑定
简单类型
整型
public String home(Model model,Integer id) {}
字符串
public String home(Model model,String goodsName){ }
单精度/双精度
public String home(Model model,Double price){ }
布尔型
处理器方法:
public String home(Model model,Integer id,Boolean status) {
}
请求 url:
http://localhost:8080/springmvc/home.action?id=2&status=false
对于布尔类型的参数,请求的参数值为 true 或 false。
@RequestParam
当请求的参数名称和处理器形参名称不一致时,可通过注解适配器@RequestParam 实现请求的参数名称与处理器形参的名称进行映射。
定义如下:
@GetMapping(value="/home") public String home(@RequestParam(value="goodsid",required=true,defaultValue = "1") String id) { return "home"; }
参数传递方式 :
Xxxx/home?goodsid=6
实现了 URL 中 goodsid 的值传递给 controller 方法参数 id 上。
关于@RequestParam 注解的参数解释如下
value:参数名字,即入参的请求参数名字,如value=“goodsid”表示请求的参数中的名字为goodsid的参数的值将传入;
required:是否必须,默认是true,表示请求中一定要有相应的参数,
如果没有参数,否则将报如下异常:
TTP Status 400 - Required Integer parameter ‘XXXX’ is not present
defaultValue:默认值,表示如果请求中没有同名参数时的默认值
这里通过 required=true 限定 goodsid 参数为必需传递,如果不传递则报 400 错误,可以使用defaultValue 设置默认值,即使 required=true 也可以不传 goodsid 参数值 .
参数绑定综合示例
@Controller @RequestMapping("/goods") public class HomeController { @RequestMapping("/list") public String home(@RequestParam(value = "goodsid",required = true,defaultValue = "1") Integer id, String goodsname,double price,boolean status) { System.out.println("id="+id); System.out.println("goodsname="+goodsname); System.out.println("price="+price); System.out.println("status="+status); return "home"; } }
运行结果
id=2
goodsname=tao
price=22.3
status=true
pojo
简单 pojo
将 pojo 对象中的属性名与传递进来的属性名对应,如果传进来的参数名称和对象中的属性名称一致则将参数值设置在 pojo 对象中 。
Pojo 定义如下:
public class GoodsModel implements Serializable { private Integer id; private String goodsname; private Double price; private String memo; private String pic; private Date createtime; }
页面定义如下:
<form method="post" action="${pageContext.request.contextPath }/goods/save.action"> <input type="text" name="goodsname"/> <input type="text" name="price"/> <input type="submit" value="save"> </form>
Contrller 方法定义如下:
@Controller @RequestMapping("/goods") public class HomeController { @RequestMapping("/save") public String home(GoodsModel model) { System.out.println("goodsname="+model.getGoodsname()); System.out.println("price="+model.getPrice()); return "home"; } }
VO
如果视图中采用“对象.属性”的方式命名,需要将 pojo 对象作为一个包装对象的属性,controller 中以该包装对象作为形参。
包装 POJO 定义如下:
public class QueryVO implements Serializable{ private GoodsModel goodsModel; ……
Pojo 定义如下:
public class GoodsModel implements Serializable { private Integer id; private String goodsname; private Double price; private String memo; private String pic; private Date createtime; ……
页面定义:
<form method="post" action="${pageContext.request.contextPath }/goods/save.action"> <input type="text" name="goodsModel.goodsname"/> <input type="text" name="goodsModel.price"/> <input type="submit" value="save"> </form>
Controller 方法定义如下:
@Controller @RequestMapping("/goods") public class HomeController { @RequestMapping("/save") public String home(QueryVO queryVO) { System.out.println("goodsname="+queryVO.getGoodsModel().getGoodsname()); System.out.println("price="+queryVO.getGoodsModel().getPrice()); return "home"; } }
自定义参数绑定
需求
根据业务需求自定义日期格式进行参数绑定。实现界面提交的 String 类型的日期转换为 Date类型的日期。例如: 将下图表单中的日期绑定到 QueryVO 对象的 goodsModel 属性中的 createtime 属性上。
Converter
表单提交的都是 String 类型的数据,
将 String 类型的数据绑定到控制器方法参数对象时,如果方法参数是基本数据类型,那么springMVC 会自动将 String 类型转换为基本数据类型。
如果方法参数不是基本类型,比如是 Date 类型,那么就需要自定义类型转换器,将 String 类型转换为 Date 类型。
自定义类型转换器
自定义类型转换器需要实现 Converter 接口,重写 convert 方法,并向 springmvc 进行注册。
public class CustomDateTimeConverter implements Converter<String, Date>{ @Override public Date convert(String source) { try {SimpleDateFormat simpleDateFormat=null;if(source!=null && source.length()==10) { simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");}else { simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");}return simpleDateFormat.parse(source); } catch (Exception e) {e.printStackTrace(); } return null; } }
注册自定义 Converter
<mvc:annotation-driven conversion-service="customDateConverter" /> <bean id="customDateConverter" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <property name="converters"> <list><bean class="com.ltw.converter.CustomDateTimeConverter"></bean> </list> </property> </bean>
自定义类型转换器的示例:
界面
<form method="post" action="${pageContext.request.contextPath }/goods/save.action"> <input type="text" name="goodsModel.createtime"/> <input type="submit" value="save"> </form>
控制器
@Controller @RequestMapping("/goods") public class HomeController { @RequestMapping("/save") public String home(QueryVO queryVO) { System.out.println("createtime="+queryVO.getGoodsModel().getCreatetime()); return "home"; } }
集合类参数绑定
字符串数组
页面定义如下:
页面选中多个 checkbox 向 controller 方法传递
批量删除<br /> <form method="post" action="${pageContext.request.contextPath }/goods/delete.action"> 1<input type="checkbox" name="goodsid" value="1"/><br /> 2<input type="checkbox" name="goodsid" value="2"/><br /> 3<input type="checkbox" name="goodsid" value="3"/><br /> <input type="submit" value="delete"> </form>
Controller 方法中可以用 String[]接收,定义如下:
@Controller @RequestMapping("/goods") public class HomeController { @RequestMapping("/delete") public String home(@RequestParam("goodsid") String ids[]) { if(ids!=null && ids.length>0) {for (int i = 0; i < ids.length; i++) { System.out.println(ids[i]);} } return "home"; } }
List
List 中存放对象,并将定义的 List 放在包装类中,controller 使用包装对象接收。
示例:修改数据
提示,修改前先查询原始数据。
(1)包装类中定义 List 对象,并添加 get/set 方法,如下:
public class QueryVO implements Serializable{ private List<GoodsModel> goodsModels; … …
(2)在控制器中查询原始数据
@Controller @RequestMapping("/goods") public class HomeController { @RequestMapping("/edit") public String getGoods(Model model) { List<GoodsModel> goodsModels = new ArrayList<GoodsModel>(); GoodsModel goodsModel1 =new GoodsModel(1, "平谷大桃1", 20.2d, "好吃", "images/a.jpg", new Date()); GoodsModel goodsModel2 =new GoodsModel(2, "平谷大桃2", 20.2d, "好吃", "images/a.jpg", new Date()); goodsModels.add(goodsModel1); goodsModels.add(goodsModel2); model.addAttribute("goodsModels", goodsModels); return "edit"; }
(3)页面定义如下:
<form method="post" action="${pageContext.request.contextPath }/goods/update.action"> <c:forEach items="${goodsModels}" var="goodsModel" varStatus="s"> 名称:<input type="text" name="goodsModels[${s.index }].goodsname" value="${goodsModel.goodsname }"/><br /> 价格:<input type="text" name="goodsModels[${s.index }].price" value="${goodsModel.price }"/><br /> --------------------------------------------------------------------<br /> </c:forEach> <input type="submit" value="update"> </form>
(4)在控制器中更新数据:
@RequestMapping("/update") public String home(QueryVO queryVO) { if(queryVO!=null && queryVO.getGoodsModels()!=null) { for (int i = 0; i < queryVO.getGoodsModels().size(); i++) {System.out.print(queryVO.getGoodsModels().get(i).getGoodsname());System.out.print("\t");System.out.println(queryVO.getGoodsModels().get(i).getPrice()); } } return "home"; }
Post 提交中文乱码
在 web.xml 中加入 spring 提供了处理 post 提交乱码的过滤器。
以上可以解决 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>
对于 get 请求中文参数出现乱码解决方法有两种:
第一种:
修改 tomcat 的 server.xml 配置文件,添加编码如下:
<Connector URIEncoding="utf-8" connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/>
第二种:
String userName = new String(request.getParamter("userName").getBytes("ISO8859-1"),"utf-8")
ISO8859-1 是 tomcat 默认编码,需要将 tomcat 编码后的内容转换为 utf-8 编码
数据验证 validator
这里的数据验证是指界面提交的数据在绑定到 Controller 方法参数前,对界面提交的数据进行验证,若有非法数据,终止业务执行,回显到界面。
Spring 支持 JSR-303 验证框架,JSR 是 Java Specification Requests 的缩写,意思是 Java 规范提案,是 JAVA EE 6 中的一项子规范,303 是版本。官方参考实现是 Hibernate Validator(Hibernate Validator 与 Hibernate 没有关系),JSR 303 用于对 Java Bean 中的字段的值进行验证。
需求
添加商品,要求
(1) 验证商品名称必须填写
(2) 验证商品名称长度
(3) 验证商品价格必须填写
(4) 验证价格>0
在 pom.xml 中添加 jsr303 的依赖包
<dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.3.5.Final</version> </dependency>
配置数据校验 validator
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"> <property name="providerClass" value="org.hibernate.validator.HibernateValidator" /> <property name="validationMessageSource" ref="messageSource" /> </bean> <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource"> <property name="basenames"> <list> <value>classpath:CustomValidationMessages</value> </list> </property> <property name="fileEncodings" value="utf-8" /> <property name="defaultEncoding" value="UTF-8"/> <property name="cacheSeconds" value="120" /> </bean>
将 validator 注册到处理器适配器
<mvc:annotation-driven validator="validator"/>
添加验证规则
public class GoodsModel implements Serializable { private Integer id; @Size(min = 2,max = 20,message = "{goodsname.size.error}") @NotEmpty(message = "{goodsname.notempty}") private String goodsname; @DecimalMin(value = "0",message = "{price.min.value.error}") @NotNull(message = "{price.notnull}") private Double price; ……
校验资源文件 CustomValidationMessages.properties
goodsname.notempty=商品名称必须填写 goodsname.size.error=商品名称大于两个字符 price.notnull=价格必须填写 price.min.value.error=价格大于零
捕获错误
Controller
@Controller @RequestMapping("/goods") public class HomeController { //准备添加表单方法 @GetMapping("/preadd") public String preAdd() { return "add"; } //表单提交的方法 @RequestMapping("/save") public String save(Model model, @ModelAttribute("goodsModel") @Validated GoodsModel goodsModel, BindingResult br) { System.out.println("goodsname="+goodsModel.getGoodsname()); System.out.println("price="+goodsModel.getPrice()); if(br.hasErrors()==true) {List<FieldError> errors = br.getFieldErrors();for (FieldError error : errors) { model.addAttribute(error.getField(), error.getDefaultMessage());}return "add"; } return "home"; } }
解析:
(1) 添加@Validated 表示在对 goods 参数绑定时进行校验。
(2) 校验信息写入 BindingResult 中,在要校验的 pojo 后边添加BingdingResult。
(3) 一个 BindingResult 对应一个 pojo,且 BingdingResult 放在 pojo 的后边。
(4) error.getField()获取验证未通过的实体对象的属性名,如 username。
(5) error.getDefaultMessage()获取验证未通过的描述信息。
界面显示错误信息
<form method="post" action="${pageContext.request.contextPath }/goods/save.action"> goodsname:<input type="text" name="goodsname"/>${username} <br /> price:<input type="text" name="price"/> ${userpass} <br /> <input type="submit" value="save"><br /> </form>
解析:
是错误描述的 key。是在控制器方法中 model.addAttribute(error.getField(), error.getDefaultMessage())中的 error.getField()的值。
分组校验
如果两处校验使用同一个 GoodsModel 类则可以设定校验分组,通过分组校验可以对每处的校验个性化设置。
(1) 需求:修改商品
验证要求:只验证商品名称长度
提示:添加商品对商品名称的验证与修改商品对商品名称的验证是不相同的。
(2)定义分组:
分组就是一个标识,使用空接口定有分组标识。
public interface ValidGroup1 { } public interface ValidGroup2 { }
(3)指定分组校验:
public class GoodsModel implements Serializable { private Integer id; @Size(min = 2,max = 20,message = "{goodsname.size.error}",groups = {ValidGroup1.class,ValidGroup2.class}) @NotEmpty(message = "{goodsname.notempty}",groups = {ValidGroup1.class}) private String goodsname; @DecimalMin(value = "0",message = "{price.min.value.error}",groups = {ValidGroup1.class}) @NotNull(message = "{price.notnull}",groups = {ValidGroup1.class}) private Double price;
(4)控制器
@Controller @RequestMapping("/goods") public class HomeController { @GetMapping("/preadd") public String preAdd() { return "add"; } @RequestMapping("/save") public String save(Model model, @ModelAttribute("goodsModel") @Validated(value = {ValidGroup1.class}) GoodsModel goodsModel, BindingResult br) { …… } @RequestMapping("/update") public String update(Model model, @ModelAttribute("goodsModel") @Validated(value = {ValidGroup2.class}) GoodsModel goodsModel, BindingResult br) { System.out.println("goodsname="+goodsModel.getGoodsname()); System.out.println("price="+goodsModel.getPrice()); …… } }
在@Validated 中添加 value={ValidGroup1.class}表示 POJO 类的属性中添加了 ValidGroup1.class标识的验证规则都适用于此验证。
@Validated 可以添加多个验证标识,如下:
@Validated(value={ValidGroup1.class,ValidGroup2.class })
校验注解
数据回显
需求
表单提交失败需要再回到表单页面重新填写,原来提交的数据需要重新在页面上显示。
简单数据类型回显
对于简单数据类型,如:Integer、String、Float 等使用 Model 将传入的参数再放到 request域实现回显。
方法:model.addAttribute(key, value)将 value 保存到 request 范围中。
例如:
在 controller 中将 goodsname 保存到 request 范围中
@RequestMapping("/save") public String save(Model model,String goodsname) { //将简单类型数据保存到request范围中 model.addAttribute("goodsname", goodsname); return "add"; }
界面上回显 goodsname
goodsname:<input type="text" name="goodsname" value="${goodsname}"/>
pojo 类型自动回显
springmvc 默认支持 pojo 数据回显,springmvc 自动将形参中的 pojo 重新放回 request 范围中,request 的 key 为 pojo 的类名(首字母小写),相当于调用下边的代码: model.addAttribute(“goodsVO”, goodsVO);
还相当于
request.setAttribute(“goodsVO”, goodsVO);
例如:
controller 方法:
@RequestMapping("/update") public String save(GoodsModel goodsModel) { return "add"; }
界面:
goodsname:<input type="text" name="goodsname" value="${goodsModel.goodsname}"/>
Model,ModelMap,HashMap 回显
将回显的数据存储到 Model,或者 ModelMap,或者 Map 集合中都可以实现回显。Model、ModelMap、Map 都是将数据放入到 request 请求域中存储,然后页面转发到视图显示。
@RequestMapping("/testmap") public String testmap(Map map) { map.put("testmap","testmap"); return "saveresult"; } @RequestMapping("/testmodel") public String testmodel(Model model) { model.addAttribute("testmodel","testmodel"); return "saveresult"; } @RequestMapping("/testmodelmap") public String testmodelmap(ModelMap modelMap) { modelMap.addAttribute("testmodelmap", "testmodelmap"); return "saveresult"; }
视图回显
<body> ${testmap }<br /> ${testmodel }<br /> ${testmodelmap }<br /> </body>
@ModelAttribute
如果 key 不是 pojo 的类名(首字母小写),可以使用@ModelAttribute 完成数据回显。
@ModelAttribute 作用如下:
设置 springmvc 保存到 request 范围中的 pojo 的 key
此方法可实现数据回显效果。
@RequestMapping("/update") public String save(@ModelAttribute("goods") GoodsModel goodsModel) { return "add"; }
界面:
goodsname<input type="text" name="goodsname" value="${goods.goodsname}"/>
如果不用@ModelAttribute 也可以使用 model.addAttribute(“goods”, goodsModel)
完成数据回显。
关于@ModelAttribute 用法详解如下
在 SpringMVC 的 Controller 中使用@ModelAttribute 时,其位置包括下面三种:
应用在方法上
被@ModelAttribute 注解的方法会在 Controller 每个方法执行之前都执行,因此对于一个 Controller 中包含多个 URL 的时候,要谨慎使用。
使用@ModelAttribute 注解无返回值的方法
@Controller @RequestMapping(value = "/modelattribute") public class ModelAttributeController { @ModelAttribute public void myModel(String abc, Model model) { model.addAttribute("attributeName", abc); } @RequestMapping(value = "/method") public String method() { return "method"; } }
这个例子,在请求/modelattribute/method?abc=aaa 后,会先执行 myModel 方法,然后接着执行 method 方法,参数 abc 的值被放到 Model 中后,接着被带到 method 方法中。
当返回视图/modelattribute/method 时,Model 会被带到页面上
如果把 myModel 和 method 合二为一,代码如下,这也是我们最常用的方法:
@RequestMapping(value = "/method") public String method(String abc, Model model) { model.addAttribute("attributeName", abc); return "method"; }
使用@ModelAttribute 注解带有返回值的方法
@ModelAttribute public String myModel(String abc) { return abc; } @ModelAttribute public Student myModel(String abc) { Student student = new Student(abc); return student; } @ModelAttribute public int myModel(int number) { return number; }
对于这种情况,返回值对象会被默认放到隐含的 Model 中,在 Model 中的 key 为返回值类型首字母小写,value 为返回的值。
上面 3 种情况等同于
model.addAttribute("string", abc); model.addAttribute("int", number); model.addAttribute("student", student);
提示
在 jsp 页面使用${int}表达式时会报错:javax.el.ELException: Failed to parse the expression [${int}]。 解决办法: 在 tomcat 的配置文件 conf/catalina.properties 添加配置org.apache.el.parser.SKIP_IDENTIFIER_CHECK=true
如果只能这样,未免太局限了,我们很难接受 key 为 string、int、float 等等这
样的。想自定义其实很简单,只需要给@ModelAttribute 添加 value 属性即可,如
下:
@ModelAttribute(value = "num") public int myModel(@RequestParam(required = false) int number) { return number; }
@ModelAttribute 标注在有返回值的方法上时,还常用于以下需求
@ModelAttribute("goodstypes") public Map<String, String> getGoodsTypes(){ Map<String, String> goodsTypes = new HashMap<String,String>(); goodsTypes.put("1", "水果"); goodsTypes.put("2", "青菜"); return goodsTypes; }
解析:
(1) 当请求达到 Controller 类时,先调用 Controller 中标注了@ModelAttribute 的方法,并将标注了@ModelAttribute 方法的返回值保存到 request 范围中。
(2) 然后才调用 Controller 的方法。
页面:
商品类型: <select name="goodstype"> <c:forEach items="${goodstypes }" var="goodstype"> <option value="${goodstype.key }">${goodstype.value }</option> </c:forEach> </select>
应用在方法的参数上
@Controller @RequestMapping(value = "/modelattribute") public class ModelAttributeParamController { @ModelAttribute(value = "attributeName") public String myModel(@RequestParam(required = false) String abc) { return abc; } @ModelAttribute public void myModel3(Model model) { model.addAttribute("name", "zong"); model.addAttribute("age", 20); } @RequestMapping(value = "/param") public String param(@ModelAttribute("attributeName") String str, @ModelAttribute("name") String str2, @ModelAttribute("age") int str3) { System.out.println("str="+ str); System.out.println("str2 = "+ str2); System.out.println("str3 = "+ str3); return "param"; } }
url 中输入/param?abc=abc 后输出结果如下
str=abc
str2 = zong
str3 = 20
从代码中可以看出,使用@ModelAttribute 注解的参数,意思是从前面的 Model 中提取对应名称的属性并为参数赋值。
应用在方法上,并且方法也使用了@RequestMapping
@Controller @RequestMapping(value = "/modelattribute") public class ModelAttributeController { @RequestMapping(value = "/test") @ModelAttribute("name") public String test(@RequestParam(required = false) String name) { return name; } }
这种情况下,返回值 String(或者其他对象)就不再是视图了,而是放入到 Model 中的值,此 时 对 应 的 页 面 就 是 @RequestMapping 的 值 test 。 本 例 中 视 图 路 径 为modelattribute/test.jsp。
异常处理
异常处理的思路
系统中异常包括两类:编译时异常和运行时异常,前者通过捕获异常从而获取异常信息,后者主要通过规范代码开发、测试通过手段减少运行时异常的发生。
系统的 dao、service、controller 出现都通过 throws Exception 向上抛出,最后由 springmvc前端控制器交由异常处理器进行异常处理,如下图:
自定义异常类
自定义一个异常类,如果 controller、service、dao 抛出编译时异常,就将这些编译时异常转换为自定义异常,再将自定义异常在 springmvc 中统一处理。
public class CustomException extends Exception{ // 异常信息 private String message; public CustomException(String message) { super(message); this.message = message; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } }
自定义异常处理器
自定义异常处理器是实现 HandlerExceptionResolver 接口的类。
public class CustomExceptionResolver implements HandlerExceptionResolver{ @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,Exception ex) { CustomException customException = null; if(ex!=null && ex instanceof CustomException) {customException = (CustomException) ex; }else {customException = new CustomException("未知错误"); } ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("message", customException.getMessage()); modelAndView.setViewName("error"); return modelAndView; } }
异常处理器配置
<bean id="handlerExceptionResolver" class="com.ltw.exception.CustomExceptionResolver" />
错误页面
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>错误页面</title> </head> <body> ${message } </body> </html>
异常处理测试
Controller: @GetMapping("/preadd") public String preAdd() throws Exception{ throw new CustomException("准备添加商品异常"); }
上传文件
需求: 为商品添加图片
在 pom.xml 文件中添加上传图片依赖的 jar 包
<dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.1</version> </dependency>
配置上传图片解析器
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize"> <value>5120000</value> </property> </bean>
图片上传
Controller:
@RequestMapping("/update") public String save(GoodsModel goodsModel, MultipartFile goodsPic,HttpServletRequest request) throws IllegalStateException, IOException { if(goodsPic!=null) { //上传文件的原始文件名 String originalFilename = goodsPic.getOriginalFilename(); //上传文件的新文件名 String newFileName = UUID.randomUUID().toString()+originalFilename.substring(originalFilename.lastIndexOf(".")); //保存文件的目录 String folder = request.getSession().getServletContext().getRealPath("/")+"upload/"; //新文件的全路径 File fullPath = new File(folder,newFileName); if(!fullPath.exists()) {fullPath.mkdirs(); } //将上传文件写入磁盘 goodsPic.transferTo(fullPath); } return "home"; }
解析:
(1) MultipartFile 表示上传的文件
(2) MultipartFile 的形参名称必须与表单上传组件的 name 一致。
界面
<form method="post" action="${pageContext.request.contextPath }/goods/update.action" enctype="multipart/form-data" > <c:if test="${goodspic !=null}"> <img src="/upload/${goodspic}" width=100 height=100 /><br /> </c:if> <input type="file" name="goodsPic" /> <input type="submit" value="update"> </form>
解析:
(1) 界面的表单必须是 enctype=“multipart/form-data”
(2) Type=“file”是上传组件,其名称必须与 Controller 方法形参名称一致。
AJAX
异步请求与响应中,浏览器向服务器请求时,传递的数据格式多数为 key/value,服务器向客户端浏览器响应的是 json 格式。如下图:
Springmvc 支持 ajax 请求与响应,springmvc 是使用@RequestBody 和@ResponseBody 实现异步请求与响应的。
@RequestBody
作用:
@RequestBody 注 解 用 于 读 取 http 请 求 的 内 容 ( 字 符 串 ) , 通 过springmvc 提 供 的HttpMessageConverter 接口将读到的内容转换为 json、xml 等格式的数据并绑定到 controller方法的参数上。
@ResponseBody
作用:
该注解用于将 Controller 的方法返回的对象,通过 HttpMessageConverter 接口转换为指定格式的数据如:json,xml 等,通过 Response 响应给客户端。
请 key/value,响应 json 实现
表单默认请求 application/x-www-form-urlencoded 格式的数据即 key/value,通常有 post 和get 两种方法,响应 json 数据是为了方便客户端解析。
第一步:添加 ajax 的 jar 包
Spring 的 ajax 支持使用的是 jackson 包。
在 pom.xml 中添加 jackson 包。
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.9.0.pr3</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.0.pr3</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.9.0.pr3</version> </dependency>
第二步:配置 json 转换器
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"> <property name="messageConverters"> <list> <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"></bean> </list> </property> </bean>
如果使用 则不用定义 json 转换器。
第三步:编写 Controller
@RequestMapping("/edit") public @ResponseBody GoodsModel edit(Integer id) { System.out.println("id="+id); GoodsModel goodsModel =new GoodsModel(1, "平谷大桃", 20.0d, "好吃", "pgda.jpg", new Date()); return goodsModel; }
第四步:编写界面
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Insert title here</title> <script type="text/javascript" language="javascript" src="${pageContext.request.contextPath }/js/jquery-3.2.0.js"></script> <script type="text/javascript" language="javascript"> function edit(id){ var user = "id="+id; $.ajax( {type:'post', //ContentType没指定将默认为:application/x-www-form-urlencoded url:'${pageContext.request.contextPath}/goods/edit.action',data:user,success:function(data){ $("#id").val(data.id); $("#goodsname").val(data.goodsname); $("#price").val(data.price);} } ); } </script> </head> <body> <table width="800" border="1" align="center" cellpadding="0" cellspacing="0"> <tr> <td align="center">序号</td> <td align="center">名称</td> <td align="center">价格</td> <td align="center">日期</td> <td align="center">编辑</td> </tr> <tr> <td align="center">1</td> <td align="center">平谷大桃</td> <td align="center">20</td> <td align="center">2019-10-10</td> <td align="center"><a href="javascript:void(0)" onclick="edit(1)">编辑</a></td> </tr> <tr> <td align="center">2</td> <td align="center">油桃</td> <td align="center">18</td> <td align="center">2019-12-12</td> <td align="center"><a href="javascript:void(0)" onclick="edit(2)">编辑</a></td> </tr> </table> <form method="post" action="${pageContext.request.contextPath }/goods/update.action"> <input type="hidden" name="id" id="id" /> goodsname:<input type="text" id="goodsname" name="goodsname"/><br /> price:<input type="text" id="price" name="price"/><br /> <input type="submit" value="update"> </form> </body> </html>
RESTful 支持
RESTFul 是一种将参数分布到 url 路径中的编程风格。
例如编辑 id=1 的用户信息
传统写法:http://…/edit.action?id=1
RESTFul 写法:http://…/edit/1
使用 restful 风格
第一步:添加 DispatcherServlet 的 rest 支持
使用 Rest 风格要求进入到 springmvc 框架的请求为/,因此需要修改 DispatcherServlet 对rest 风格的支持。
<servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
第二步:添加模板映射
@RequestMapping(value="/goods/{id}")中{xxx}是占位符,请求的URL可以是
“/goods/1”或“/goods/2”,通过在方法中使用@PathVariable获取{xxx}中的xxx变
量。@PathVariable用于将请求URL中的模板变量映射到功能处理方法的参数上。
@RequestMapping("/edit/{id}") public @ResponseBody GoodsModel edit(@PathVariable("id") Integer id) { System.out.println("id="+id); GoodsModel goodsModel =new GoodsModel(1, "平谷大桃", 20.0d, "好吃", "pgda.jpg", new Date()); return goodsModel; }
如果RequestMapping中value的值"/viewGoodss/{id}",中的参数id和形参名称一致,@PathVariable不用指定名称。例如:
@RequestMapping("/edit/{id}") public @ResponseBody GoodsModel edit(@PathVariable Integer id) {
第三步:测试
http://localhost:8080/eshop3/goods/edit/3
静态资源访问
当启动 springmvc 时使用/,其中/表示所有请求都进入 springmvc框架,包括静态资源,如 jpg 文件,css 文件,js 文件等,但是对于这些文件的请求进入到springmvc 框架后是找不到映射 Controller 的,因为我们就没有为这些请求配置映射。因此对于这些静态资源访问在/配置为/时,要允许直接访问静态资源。 为此 springmvc 对静态资源配置如下:
方法 1: <mvc:resources mapping="/css/" location="/css/" /> <mvc:resources mapping="/js/" location="/js/" /> <mvc:resources mapping="/images/" location="/images/" /> 方法 2: <!-- 将springmvc不能处理的请求交给tomcat --> <mvc:default-servlet-handler/>
请求流程第 6 站:视图解析
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" /> <property name="prefix" value="/WEB-INF/jsp/" /> <property name="suffix" value=".jsp" /> </bean>
请求流程第 2 站拦截器
Spring MVC中的拦截器(Interceptor)类似于Servlet中的过滤器(Filter),它主要用于拦截用户请求并作相应的处理。例如通过拦截器可以进行权限验证、记录请求信息的日志、判断用户是否登录等。
定义拦截器
拦截器是实现 HandlerInterceptor 接口的类。
public class HandlerInterceptor1 implements HandlerInterceptor{ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception { return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception { } }
preHandle() 方法:该方法会在控制器方法前执行,其返回值表示是否中断后续操作。当其返回值为 true 时,表示继续向下执行;当其返回值为 false 时,会中断后续的所有操作(包括调用下一个拦截器和控制器类中的方法执行等)。
postHandle()方法:该方法会在控制器方法调用之后,且解析视图之前执行。可以通过此方法对请求域中的模型和视图做出进一步的修改。
afterCompletion()方法:该方法会在整个请求完成,即视图渲染结束之后执行。可以通过此方法实现一些资源清理、记录日志信息等工作。
定义第一个拦截器
public class HandlerInterceptor1 implements HandlerInterceptor{ / * controller执行前调用此方法 * 返回true表示继续执行,返回false中止执行 * 这里可以加入登录校验、权限拦截等 */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception { System.out.println("HandlerInterceptor1 - preHandle"); return true; } / * controller执行后但未返回视图前调用此方法 * 这里可在返回用户前对模型数据进行加工处理,比如这里加入公用信息以便页面显示 */ @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,ModelAndView modelAndView) throws Exception { System.out.println("HandlerInterceptor1 - postHandle"); } / * controller执行后且视图返回后调用此方法 * 这里可得到执行controller时的异常信息 * 这里可记录操作日志,资源清理等 */ @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception { System.out.println("HandlerInterceptor1 - afterCompletion"); } }
定义第二个拦截器
public class HandlerInterceptor2 implements HandlerInterceptor{ / * controller执行前调用此方法 * * 返回true表示继续执行,返回false中止执行 * 这里可以加入登录校验、权限拦截等 */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception { System.out.println("HandlerInterceptor2 - preHandle"); return true; } / * controller执行后但未返回视图前调用此方法 * 这里可在返回用户前对模型数据进行加工处理,比如这里加入公用信息以便页面显示 */ @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,ModelAndView modelAndView) throws Exception { System.out.println("HandlerInterceptor2 - postHandle"); } / * controller执行后且视图返回后调用此方法 * 这里可得到执行controller时的异常信息 * 这里可记录操作日志,资源清理等 */ @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception { System.out.println("HandlerInterceptor2 - afterCompletion"); } }
配置拦截器链
<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/"/><bean class="com.ltw.interceptor.HandlerInterceptor1"></bean> </mvc:interceptor> <mvc:interceptor> <mvc:mapping path="/"/> <mvc:exclude-mapping path="/user_login"/> <mvc:exclude-mapping path="/user_logout"/> <bean class="com.ltw.interceptor.HandlerInterceptor2"></bean> </mvc:interceptor> </mvc:interceptors>
path 配置拦截的请求
多个拦截器,顺序执行
如果不配置或/,将拦截所有的 Controller
匹配任意地址时注意是 2 个“”号不是 1 个“”号
exclude-mapping 配置不拦截的请求
要先配置 再配置 有先后顺序的
测试拦截器(正常流程)
代码:
定义两个拦截器分别为:HandlerInterceptor1 和 HandlerInteptor2,每个拦截器的 preHandler方法都返回 true。
运行流程 :
HandlerInterceptor1 - preHandle
HandlerInterceptor2 - preHandle
HandlerInterceptor2 - postHandle
HandlerInterceptor1 - postHandle
HandlerInterceptor2 - afterCompletion
HandlerInterceptor1 - afterCompletion
测试拦截器(中断流程)
代码: 定义两个拦截器分别为:HandlerInterceptor1 和 HandlerInteptor2。
运行流程 :
HandlerInterceptor1 的 preHandler 方法返回 false,HandlerInterceptor2 返回 true,运行流程如下:
HandlerInterceptor1 - preHandle
从日志看出第一个拦截器的 preHandler 方法返回 false 后,第一个拦截器只执行了
preHandler 方法,其它两个方法没有执行,第二个拦截器的所有方法不执行,且
controller 也不执行了。
HandlerInterceptor1 的 preHandler 方法返回 true,HandlerInterceptor2 返回 false,运行流程如下:
HandlerInterceptor1 - preHandle
HandlerInterceptor2 - preHandle
HandlerInterceptor1 – afterCompletion
从日志看出第二个拦截器的 preHandler 方法返回 false 后第一个拦截器的 postHandler没有执行,第二个拦截器的 postHandler 和 afterCompletion 没有执行,且 controller也不执行了。
总结:
preHandle 按拦截器定义顺序调用
postHandler 按拦截器定义逆序调用
afterCompletion 按拦截器定义逆序调用
postHandler 在拦截器链内所有拦截器返成功调用
afterCompletion 只有 preHandle 返回 true 才调用
权限拦截
第一步:定义权限拦截器
public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception { // 如果是登录页面则放行 if (request.getRequestURI().indexOf("login") >= 0) {return true; } HttpSession session = request.getSession(); // 如果用户已登录也放行 if (session.getAttribute("user") != null) {return true; } // 用户没有登录挑战到登录页面 request.getRequestDispatcher("/WEB-INF/views/login.jsp").forward(request, response); return false; } }
第二步:配置权限拦截器
<mvc:interceptor> <mvc:mapping path="/"/> <bean class="com.ltw.interceptor.LoginInterceptor"></bean> </mvc:interceptor>
第三步:定义 UserController 类
@Controller public class UserController { //打开登录界面 @RequestMapping("/login") public String login() { return "login"; } //用户登录 @RequestMapping("/dologin") public String dologin(HttpSession session,String name,String pass) { System.out.println("name="+name); session.setAttribute("user", name); return "home"; } //用户退出 @RequestMapping("/logout") public String logout(HttpSession session) { session.invalidate(); return "redirect:login"; } }
第四步:定义登录界面
<form action="${pageContext.request.contextPath }/dologin" method="post"> name:<input name="name"><br /> pass:<input name="pass"><br /> <button>登录</button> </form>
第五步:测试
分页查询
使用分页查询组件 pagehelper
重要提示:
(1) PageHelper 组件中存储了当前页要显示的数据集合
(2) PageHelper 组件中存储了页码信息
(3) PageHelper 组件中提供了一个名称为 PageInfo 的实体类,该类中包含了当前页要显示的数据集合 和 页码信息
1.导入依赖的 jar 包
<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.1.11</version> </dependency>
(2) 在 mybatis 配置文件中注册 pagehelper
<configuration> <typeAliases> <package name="cn.itlaobing.models"/> </typeAliases> <plugins><plugin interceptor="com.github.pagehelper.PageInterceptor"> <!-- 分页参数合理化,pageNumpages(超过总 数时),会查询最后一页 --> <property name="reasonable" value="true"/></plugin> </plugins> </configuration>
(3) 在控制器 EmployeeController 中分页查询
使用 pageHelper 分页时,控制器中编写
(1)设置第几页,页大小
PageHelper.startPage(pageNum, 4);
(2)将页面的数据和分页信息封装到 PageInfo 中
PageInfo pageInfo =new PageInfo(employees, 5);
(3)返回 PageInfo
完整的控制器代码如下:
@Controller @RequestMapping("/employee") public class EmployeeController { @Autowired private EmployeeService employeeService; @RequestMapping("/selectpage/{pageNum}") @ResponseBody public PageInfo selectByExampleWithDepartmentByPage(HttpServletRequest request, @PathVariable("pageNum") Integer pageNum) throws InterruptedException { //第1步:设置查询的页码和一页几条记录 PageHelper.startPage(pageNum, 4); //第2步:紧跟着的第一个select方法会被分页 List<Employee> employees = employeeService.selectByExampleWithDepartment(); //第3步:页面显示的本页数据封装到PageInfo中,将分页导航按钮的数量封装到PageInfo中 PageInfo pageInfo =new PageInfo(employees, 5); //第4步:返回pageInfo对象 return pageInfo; } }
(4) 界面适合于 Bootstrap 分页导航
界面引入 bootstrap.css
使用 bootstrap 分页组件
分页代码
<nav aria-label="Page navigation"> <ul class="pagination"> <li><a href="${pageContext.request.contextPath }/goods/selectAll/1" aria-label="Previous"> <span aria-hidden="true">«</span></a> </li> <c:forEach items="${pageInfo.navigatepageNums }" var="n"> <li><a href="${pageContext.request.contextPath }/goods/selectAll/${n}">${n}</a></li> </c:forEach> <li><a href="${pageContext.request.contextPath }/goods/selectAll/${pageInfo.pages}" aria-label="Next"> <span aria-hidden="true">»</span></a> </li> </ul> </nav>
测试运行
PageInfo 类的属性
//当前页 private int pageNum; //每页的数量 private int pageSize; //当前页的数量 private int size; //由于 startRow 和 endRow 不常用,这里说个具体的用法 //可以在页面中"显示 startRow 到 endRow 共 size 条数据" //当前页面第一个元素在数据库中的行号 private int startRow; //当前页面最后一个元素在数据库中的行号 private int endRow; //总记录数 private long total; //总页数 private int pages; //结果集 private List<T> list; //前一页 private int prePage; //下一页 private int nextPage; //是否为第一页 private boolean isFirstPage; //是否为最后一页 private boolean isLastPage; //是否有前一页 private boolean hasPreviousPage; //是否有下一页 private boolean hasNextPage; //导航页码数 private int navigatePages; //所有导航页号 private int[] navigatepageNums; //导航条上的第一页 private int navigateFirstPage; //导航条上的最后一页 private int navigateLastPage; public PageInfo() { this.isFirstPage = false; this.isLastPage = false; this.hasPreviousPage = false; this.hasNextPage = false; }
事务控制
编程式事务
事务控制的办法是:有异常就回滚,没异常就提交
public void addOrder() { try { //开始事务 connection.setAutoCommit(false); pstmt = connection.prepareStatement("insert ..."); pstmt.executeUpdate(); conn.setSavepoint("savepoint");//设置事务保存点 pstmt = connection.prepareStatement("update ..."); pstmt.executeUpdate(); //提交事务 connection.commit(); } catch (Exception e) { //回滚事务到保存点,保存点之前的sql被执行,保存点之后的sql被回滚 connection.rollback("savepoint"); connection.commit();//提交事务保存点之前的SQL语句 }finally { connection.close(); } }
声明式事务环境搭建
第一步:导入 jar 包
导入 Spring-jdbc 的 jar 包
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.3.13</version> </dependency>
导入 Spring-AOP 的 jar 包
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.3.13</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.8.10</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.10</version> </dependency>
第二步:配置事务管理器
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean>
声明式事务使用
声明式事务有两种使用方法
1:使用注解@Transactional 管理事务
2:使用 xml 配置文件管理事务
这两种方法可以各自单独使用,也可以两者结合一起使用
使用方式 1:使用注解@Transactional 管理事务
第一步:启动事务注解扫描
<tx:annotation-driven transaction-manager="transactionManager"/> <aop:aspectj-autoproxy proxy-target-class="true"/>
第二步:使用注解@Transactional 管理事务
将@Transactional 注解标注在业务方法上,表示告诉 Spring 容器,该方法调用前要打开事务,方法调用结束后提交或回滚事务。
将@Transactional 注解标注在业务类上,表示告诉 Spring 容器,该类中的所有方法调用前要打开事务,方法调用结束后提交或回滚事务。
如果方法内抛出运行时异常(RuntimeException),事务将回滚。
方法内抛出编译时异常(Exception),事务将提交。
@Transactional public int save(UserInfoModel model) { int id = userInfoDao.save(model); model.getDetailModel().setId(id); userDetailDao.save(model.getDetailModel()); return id; }
@Transactional 注解标注的 save()方法中的 userInfoDao.save(model); 和 userDetailDao.save(model.getDetailModel()); 在一个事务中执行,一起提交或一起回滚。
使用方式 2:基于 xml 配置管理事务
第一步:定义事务切入点
<aop:config><aop:pointcut id="serviceMethod" expression="execution(* com.kaifamiao.service..*.*(..))"/><aop:advisor pointcut-ref="serviceMethod" advice-ref="txAdvice"/> </aop:config>
Aspect 表达式定义了调用 com.kaifamiao.service()方法开启事务
第二步:定义事务的 Advice
<tx:advice id="txAdvice" transaction-manager="transactionManager"><tx:attributes> <tx:method name="save*" propagation="REQUIRED" isolation="READ_COMMITTED"/> <tx:method name="insert*" propagation="REQUIRED" isolation="READ_COMMITTED"/> <tx:method name="add*" propagation="REQUIRED" isolation="READ_COMMITTED"/> <tx:method name="create*" propagation="REQUIRED" isolation="READ_COMMITTED"/> <tx:method name="update*" propagation="REQUIRED" isolation="READ_COMMITTED"/> <tx:method name="delete*" propagation="REQUIRED" isolation="READ_COMMITTED"/> <tx:method name="find*" propagation="SUPPORTS" read-only="true" /> <tx:method name="select*" propagation="SUPPORTS" read-only="true" /> <tx:method name="get*" propagation="SUPPORTS" read-only="true" /> <tx:method name="*" propagation="REQUIRED" isolation="READ_COMMITTED" read-only="true"/></tx:attributes> </tx:advice>
事务配置了所有查询方法都不开启事务,所有增删改方法都开启事务。
事务的提交行为
如果方法内抛出运行时异常(RuntimeException),事务将回滚。
方法内抛出编译时异常(Exception),事务将提交。
异常的体系结构如下:
不需要事务管理的方法
getXxx()方法或 findXxx()只是获取数据,并没有修改数据,因此这些用于获取数据的方法不应该放在事务中。如果获取数据的方法也开启了事务,那么对性能是有影响的。 可以设置事务的传播行为来要求获取数据方法调用时不使用事务。事务的传播行为使用Propagation.NOT_SUPPORTED 进行设置。
@Transactional(propagation=Propagation.NOT_SUPPORTED) public UserInfoModel findById(int id) { }
事务的传播行为
Propagation 用于设置事务的传播行为,事务的传播行为是指从一个开启事务的方法调用另外一个方法时,是否将事务传播给被调用方法。事务的传播行为有以下几个设置:
- REQUIRED:(必须)
业务方法需要在一个事务中运行。如果方法运行时,已经处在一个事务中,那么就加入该事务,否则为自己创建一个新的事务。该项为默认值。 - NOT_SUPPORTED:(不支持)
声明方法不需要事务。如果方法没有关联到一个事务,spring 容器不会为它开启事务。如果方法在一个事务中被调用,该事务会被挂起(suspend),在方法调用结束后,原先的事务便会恢复(resume)执行。 - REQUIRESNEW(必须开启新事务)
表明不管是否存在事务,业务方法总会为自己发起一个新的事务。如果方法已经运行在一个事务中,则原有事务会被挂起,新的事务会被创建。直到方法执行结束,新事务才算结束,原先的事务才会恢复执行。 - MANDATORY:(强制)
该属性指定业务方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务。如果业务方法没有在事务的环境下调用,spring 容器就会抛出异常。 - SUPPORTS:(支持)
这一事务属性表明,如果业务方法在某个事务范围内被调用,则方法成为该事务的一部分。如果业务方法在事务范围外被调用,则方法在没有事务的环境下运行。 - NEVER: (从不)
指定业务方法绝对不能再事务范围内执行。如果业务方法在某个事务中执行spring 容器会抛出异常,只有业务方法没有关联到任何事务,才能正常执行。 - NESTED:(内部)
如果一个活动的事务存在,则运行在一个嵌套的事务中,如果没有活动事务,则按照REQUIRED 属性执行。它使用了一个单独的事务,这个事务拥有多个可以回滚的保存点,内部事务的回滚不会对外部事务造成影响。外部事务回滚会对内部事务造成影响。它只对 DataSourceTransactionManager 事务管理器有效。
事务的隔离性
@Transactional 还可以设置 readOnly、timeout、isolation 属性,例如:
@Transactional(propagation=Propagation.REQUIRED,readOnly=true,timeout=30,isolation=I solation.READ_COMMITTED)
(1). readOnly 表示只读事务,不允许更新数据,查询方法可以设置该属性,以提升效
率。
(2). timeout 是事务执行的超时时间,默认值是 30 秒
(3). isolation 是数据库的事务隔离级别
如果多个事务并发操作同一个资源(例如一个事务修改某条记录的同时,另外一个事务正在删除或者读取这条记录)可能会发生一下的问题。
(1). 脏读:一个事务读取到另外一个事务未提交的更新数据
(2). 不可重复读:在同一个事务中,多次读取同一个数据,返回的结果不同。意思是说后面的读取可以读到另外一个事务已经提交的更新数据。
(3). 可重复读:在同一个事务中多次读取数据时,能保证每次所读取的数据都是一样的,意思是说后面读取不能读到另外一个事务提交的更新数据。
(4). 幻想读:一个事务读取到另外一个事务已经提交的 insert 或 delete 数据。例如当对某行执行插入或删除操作,而该行属于某个事务正在读取的行的范围时,会发
生幻像读问题。事务第一次读的行范围显示出其中一行已不复存在于第二次读或
后续读中,因为该行已被其它事务删除。同样,由于其它事务的插入操作,事务
的第二次或后续读显示有一行已不存在于原始读中。
为了避免事务并发带来的问题,数据库系统提供了 4 中事务隔离级别供用户选择。不同
的隔离级别采用不同的锁来实现。
(1). Read Uncommited:未提交读隔离级别(会出现脏读、不可重复读、幻想读)
(2). Read Commited:已提交读隔离级别(不会出现脏读,会出现不可重复读、幻想读)
(3). Repeatable Read 可重复读(不会出现脏读、不可重复读,会出现幻想读)
(4). Serializable:串行化(不会出现脏读、不可重复读、幻想读)
MS SQL Server 默认隔离级别是 Read Commited,MySQL 默认隔离级别是 Repeatable Read, Oracle 默认隔离级别是 Read Commited。