SpringMVC是一种基于Java的实现MVC设计模型的请求驱动类型的轻量级Web框架。它通过一套注解,让一个简单的Java类成为处理请求的控制器,而无须实现任何接口。同时它还支持RESTful编程风格的请求。
需求:客户端发起请求,服务端接收请求,执行逻辑并进行视图跳转。
开发步骤:
导入SpringMVC相关坐标
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>5.3.8version>
dependency>
dependencies>
web.xml中配置SpringMVC核心控制器DispathcerServlet
<servlet>
<servlet-name>dispatcherServletservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:springmvc.xmlparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>dispatcherServletservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
创建Controller类和视图页面
使用注解配置Controller类中业务方法的映射地址
@Controller
public class HeroController {
@RequestMapping("/hello")
public String hello(){
System.out.println("hello···");
return "hello.jsp";
}
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
Title
你好,SpringMVC!
配置SpringMVC核心文件spring-mvc.xml
<context:component-scan base-package="com.cqgcxy.controller">context:component-scan>
配置Tomcat并启动
客户端发起请求测试
@Controller
@RequestMapping("/hero")
public class HeroController {
@RequestMapping(value = "/hello",method = RequestMethod.GET,params = {"heroId"})
public String hello(){
System.out.println("hello···");
return "hello.jsp";
}
}
SpringMVC有默认组件配置,默认组件都是Dispatcherservlet.properties配置文件中配置的,该配置文件地址
org/springframework/web/ servlet/DispatcherServlet.properties,该文件中配置了默认的视图解析器,如下:
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceviewResolver
翻看该解析器源码,可以看到该解析器的默认设置,如下:
public static final String REDIRECT_URL_PREFIX = "redirect:"; //重定向前缀
public static final String FORWARD_URL_PREFIX = "forward:"; //请求转发前缀
private String prefix = ""; //视图名称前缀
private String suffix = ""; //视图名称后缀
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/">property>
<property name="suffix" value=".jsp">property>
bean>
@Controller
@RequestMapping("/hero")
public class HeroController {
@RequestMapping("/hello")
public String hello(){
System.out.println("hello···");
return "hello";
}
}
**直接返回字符串:**此种方式会将返回的字符串与视图解析器的前后缀拼接后跳转
资源地址:/WEB-INF/views/hello.jsp
返回带有前缀的字符串:
转发:forward:/WEB-INF/views/hello.jsp
重定向:redirect:/index.jsp
@RequestMapping("/forward")
public String forword(){
System.out.println("forward···");
return "forward:/WEB-INF/views/index.jsp";
}
@RequestMapping("/redirect")
public String redirect(){
System.out.println("redirect···");
return "redirect:/login.jsp";
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
Title
你好,SpringMVC!${username}
@RequestMapping("/hello2")
public ModelAndView hello2(){
//Model:模型,用于封装数据
//View:视图,用于展示数据
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("username","pep7chiao");
modelAndView.setViewName("hello");
return modelAndView;
}
@RequestMapping("/hello3")
public ModelAndView hello3(ModelAndView modelAndView){
modelAndView.addObject("username","pep7chiao");
modelAndView.setViewName("hello");
return modelAndView;
}
@RequestMapping("/hello4")
public String hello4(Model model){
model.addAttribute("username","messi");
return "hello";
}
@RequestMapping("/hello5")
public String hello5(HttpServletRequest reqest){ //HttpServletRequest需要添加依赖
reqest.setAttribute("username","ronaldo");
return "hello";
}
Web基础阶段,客户端访问服务器端,如果想直接回写字符串作为响应体返回的话,只需要使用response.getWriter().print(“hello world”))即可,那么在Controller中想直接回写字符串该怎样呢?
据,此时不需要视图跳转,业务方法返回值为void。
@RequestMapping("/data1")
public void data1(HttpServletResponse response) throws IOException {
response.setContentType("text/html;charset=utf-8");
response.getWriter().print("重庆工程学院");
}
将需要回写的字符串直接返回,但此时需要通过**@ResponseBody**注解告知SpringMVC框架,方法返回的字符串不是跳
转,而是直接在http响应体中返回。
@RequestMapping(value = "/data2",produces = "text/html;charset=utf-8")
@ResponseBody
public String data2(){
return "软件工程研究所";
}
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
<version>2.9.0version>
dependency>
<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>
@RequestMapping("/data3")
@ResponseBody
public Hero data3() throws IOException {
Hero hero = new Hero();
hero.setId(1L);
hero.setHeroName("李白");
return hero;
}
<mvc:annotation-driven/>
客户端请求参数的格式是:name=value&name=value…
服务器端要获得请求的参数,有时还需要进行数据的封装,SpringMVC可以接收如下类型的参数:
获取基本类型参数
Controller中的业务方法的参数名称要与请求参数的name一致,参数值会自动映射匹配。
http://localhost:8888/hero/param1?id=1&heroName=李白
@RequestMapping("/param1")
@ResponseBody
public void param1(int id,String heroName){
System.out.println(id);
System.out.println(heroName);
}
当请求的参数名称与Controller的业务方法参数名称不一致时,就需要通过@RequestParam注解显示的绑定。
http://localhost:8888/hero/param1?id=1&hname=李白
@RequestMapping("/param1")
@ResponseBody
public void param1(int id,@RequestParam("hname") String heroName){
System.out.println(id);
System.out.println(heroName);
}
注解@RequestParam还有如下参数可以使用:
http://localhost:8888/hero/param1?id=1
@RequestMapping("/param1")
@ResponseBody
public void param1(int id,@RequestParam(value = "hname",required = false,defaultValue = "李白") String heroName){
System.out.println(id);
System.out.println(heroName);
}
POJO类型参数
Controller中的业务方法的POJO参数的属性名与请求参数的name一致,参数值会自动映射匹配。
http://localhost:8888/hero/param1?id=1&heroName=李白
@RequestMapping("/param2")
@ResponseBody
public void param2(Hero hero){
System.out.println(hero.getId());
System.out.println(hero.getHeroName());
}
数组类型参数
Controller中的业务方法数组名称与请求参数的name一致,参数值会自动映射匹配。
http://localhost:8888/hero/param3?roles=诗人&roles=刺客
@RequestMapping("/param3")
@ResponseBody
public void param3(String[] roles){
System.out.println(Arrays.asList(roles));
}
@RequestMapping("/param3")
@ResponseBody
public void param3(@RequestParam List<String> roles){
System.out.println(roles);
}
集合类型参数
获取集合参数时,要将集合参数包装到一个POJO中才可以。
public class VO {
private List<Hero> heroList;
public List<Hero> getHeroList() {
return heroList;
}
public void setHeroList(List<Hero> heroList) {
this.heroList = heroList;
}
@Override
public String toString() {
return "VO{" +
"heroList=" + heroList +
'}';
}
}
@RequestMapping(value = "/param4",method = RequestMethod.POST)
@ResponseBody
public void param4(VO vo){
System.out.println(vo);
}
乱码问题:当表单提交post请求时,数据会出现乱码,可以设置一个过滤器来进行编码的过滤。
<filter>
<filter-name>characterEncodingFilterfilter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilterfilter-class>
<init-param>
<param-name>encodingparam-name>
<param-value>utf-8param-value>
init-param>
filter>
<filter-mapping>
<filter-name>characterEncodingFilterfilter-name>
<servlet-name>dispatcherServletservlet-name>
filter-mapping>
当使用ajax提交时,可以指定contentType为json形式,那么在方法参数位置使用@RequestBody可以直接接收集合数据而无需使用POJO进行包装。
@RequestMapping(value = "/param5",method = RequestMethod.POST)
@ResponseBody
public void param5(@RequestBody List<Hero> heroList){
System.out.println(heroList);
}
注意:这时会发现找不到js文件,需要开放资源访问。
<mvc:resources mapping="/js/**" location="/js/">mvc:resources>
<mvc:default-servlet-handler/>
获得Restful风格的参数
Restful是一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。主要用于客户端和服务器交互类的软件,基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存机制等。
Restful风格的请求是使用“url+请求方式”表示一次请求目的的,HTTP协议里面四个表示操作方式的动词如下:
GET:用于获取资源
POST:用于新建资源
PUT:用于更新资源
DELETE:用于删除资源
例如:
/user/1 GET :得到id =1的user
/user/1 DELETE:删除id = 1的user
/user/1 PUT:更新id = 1的user
/user POST:新增user
上述url地址/user/1中的1就是要获得的请求参数,在SpringMVC中可以使用占位符进行参数绑定。地址/user/1可以写成/user/{id},占位符{id}对应的就是1的值。在业务方法中我们可以使用@PathVariable注解进行占位符的匹配获取工作。
http://localhost:8888/hero/param6/zhangsan
@RequestMapping("/param6/{hname}")
@ResponseBody
public void param6(@PathVariable("hname") String heroName){
System.out.println(heroName);
}
自定义类型转换器
SpringMVC默认已经提供了一些常用的类型转换器,例如客户端提交的字符串转换成int型进行参数设置。
但是不是所有的数据类型都提供了转换器,没有提供的就需要自定义转换器,例如:日期类型的数据就需要自定义转换器。
自定义类型转换器的开发步骤:
(1)定义转换器类实现Converter接口
(2)在配置文件中声明转换器
(3)在
public class DateConverter implements Converter<String, Date> {
public Date convert(String dateStr) {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
Date date = null;
try {
date = format.parse(dateStr);
} catch (ParseException e) {
e.printStackTrace();
}
return date;
}
}
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<list>
<bean class="com.cqgcxy.converter.DateConverter">bean>
list>
property>
bean>
<mvc:annotation-driven conversion-service="conversionService"/>
http://localhost:8888/hero/param7/2022-10-10 2008:08:08
@RequestMapping("/param7/{date}")
@ResponseBody
public void param7(@PathVariable("date") Date date){
System.out.println(date);
}
Spring MVC 接收 LocalDate、LocalTime 和 LocalDateTime Java 8 时间类型参数
org.springframework.web.bind.annotation.RequestParam
org.springframework.format.annotation.DateTimeFormat
RequestParam 比较常见,用于标注 Controller 中方法的参数;
DateTimeFormat 用于声明一个对象属性或者方法参数会被格式化为日期或时间。两个注解结合使用时,Spring 会调用 FormattingConversionService.convert(Object, TypeDescriptor, TypeDescriptor) 将日期时间字符串转换成日期时间类型。
示例如下:
//http://localhost:8888/hero/param9/2022-02-02
@RequestMapping("/param8/{date}")
@ResponseBody
public void param8(@DateTimeFormat(iso = DateTimeFormat.ISO.DATE) @PathVariable("date") LocalDate date){
System.out.println(date);
}
//http://localhost:8888/hero/param9?date=2022-02-02
@RequestMapping("/param9")
@ResponseBody
public void param9(@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date){
System.out.println(date);
}
//http://localhost:8888/hero/param11?time=08:08:08
@RequestMapping("/param10")
@ResponseBody
public void param10(@RequestParam @DateTimeFormat(pattern = "HH:mm:ss") LocalTime time){
System.out.println(time);
}
//http://localhost:8888/hero/param11?dateTime=2022-08-08 08:08:08
@RequestMapping("/param11")
@ResponseBody
public void param11(@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime dateTime){
System.out.println(dateTime);
}
获取请求头
(1)@RequestHeader
使用@RequestHeader可以获得请求头信息,相当于web阶段学习的request.getHeader(name)
@RequestHeader注解的属性如下:
@RequestMapping("/param12")
@ResponseBody
public void param12(@RequestHeader(value = "user-Agent",required = false) String userAgent){
System.out.println(userAgent);
}
(2)@CookieValue
使用@CookieValue可以获得指定Cookie的值
@CookieValue注解的属性如下:
@RequestMapping("/param13")
@ResponseBody
public void param13(@CookieValue("JSESSIONID") String jSessionId){
System.out.println(jSessionId);
}
文件上传客户端三要素
文件上传原理
-----------------------------28609777630614176314163519108
Content-Disposition: form-data; name="heroName"
11
-----------------------------28609777630614176314163519108
Content-Disposition: form-data; name="heroImg"; filename="待办事项.txt"
Content-Type: text/plain
1、8月4日周五授课
2、项目实训
-----------------------------28609777630614176314163519108--
单文件上传步骤
(1)导入fileupload和io坐标
<dependency>
<groupId>commons-fileuploadgroupId>
<artifactId>commons-fileuploadartifactId>
<version>1.2.2version>
dependency>
<dependency>
<groupId>commons-iogroupId>
<artifactId>commons-ioartifactId>
<version>2.4version>
dependency>
(2)配置文件上传解析器
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="5242880">property>
<property name="maxUploadSizePerFile" value="5242880">property>
<property name="defaultEncoding" value="UTF-8">property>
bean>
(3)编写文件上传代码
@RequestMapping("/upload")
@ResponseBody
public void upload(String heroName, MultipartFile heroImg) throws IOException {
String originalFilename = heroImg.getOriginalFilename();
System.out.println("文件名称:"+originalFilename);
heroImg.transferTo(new File("D:\\DevelopWork\\javaweb\\springmvc-demo\\web\\upload\\"+originalFilename));
}
多文件上传实现
或者:
@RequestMapping("/uploads")
@ResponseBody
public void uploads(String heroName, MultipartFile[] heroImgs) throws IOException {
for (MultipartFile heroImg : heroImgs) {
String originalFilename = heroImg.getOriginalFilename();
System.out.println("文件名称:"+originalFilename);
heroImg.transferTo(new File("D:\\DevelopWork\\javaweb\\springmvc-demo\\web\\upload\\"+originalFilename));
}
}
拦截器的作用
Spring MVC的拦截器类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理。
将拦截器按一定的顺序联结成一条链,这条链称为拦截器链(Interceptor Chain)。在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。拦截器也是AOP思想的具体实现。
拦截器和过滤器的区别
区别 | 过滤器 | 拦截器 |
---|---|---|
使用范围 | 是servlet规范中的一部分,任何Java Web工程都可以使用 | 是SpringMVC框架自己的,只有使用了SpringMVC框架的工程才能用 |
拦截范围 | 在url-pattern中配置了/*之后,可以对所有要访问的资源拦截 | 只会拦截访问的控制器方法,如果访问的是jsp,html,css,image或者js 是不会进行拦截的 |
拦截器的快速入门
(1)创建拦截器类实现Handlerlnterceptor接口
public class MyIntercepter implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("目标方法执行之前执行···");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("目标方法执行之后,视图对象返回之前执行···");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("流程都执行完毕之后执行···");
}
}
(2)配置拦截器
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/hero/*"/>
<bean class="com.cqgcxy.interceptor.MyIntercepter">bean>
mvc:interceptor>
mvc:interceptors>
(3)测试拦截器的拦截效果
拦截器方法说明
方法名 | 说明 |
---|---|
preHandle | 方法将在请求处理之前进行调用,该方法的返回值是布尔值Boolean类型的,当它返回为false时,表示请求结束,后续的Interceptor和Controller都不会再执行;当返回值为true时就会继续调用下一个Interceptor的preHandle方法 |
postHandle | 该方法是在当前请求进行处理之后被调用,前提是preHandle方法的返回值为true时才能被调用,且它会在DispatcherServlet进行视图返回渲染之前被调用,所以我们可以在这个方法中对Controller处理之后的ModelAndView对象进行操作 |
afterCompletion | 该方法将在整个请求结束之后,也就是在DispatcherServlet渲染了对应的视图之后执行,前提是preHandle方法的返回值为true时才能被调用 |
练习:
案例需求:使用拦截器,验证用户是否登录。
public class LoginIntercepter implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession();
String username = (String)session.getAttribute("username");
if(StringUtils.hasText(username)){
return true;
}
response.sendRedirect(request.getContextPath()+"/hero/login");
return false;
}
}
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/hero/*"/>
<mvc:exclude-mapping path="/hero/login"/>
<bean class="com.cqgcxy.interceptor.LoginIntercepter">bean>
mvc:interceptor>
mvc:interceptors>
@RequestMapping(value = "/login",method = RequestMethod.GET)
public String login(){
return "forward:/login.jsp";
}
@RequestMapping(value = "/login",method = RequestMethod.POST)
public String login(String username, String password, HttpSession session){
//模拟登录验证
if("pep".equals(username)&&"123".equals(password)){
session.setAttribute("username",username);
}
return "redirect:/hero/index";
}
异常处理思路
系统中异常包括两类:预期异常和运行时异常RuntimeException,前者通过捕获异常从而获取异常信息,后者主要通过规范代码开发、测试等手段减少运行时异常的发生。
系统的Dao、Service、Controller出现都通过throws Exception向上抛出,最后由SpringMVC前端控制器交由异常处理器进行异常处理.
异常处理两种方式
(1)使用Spring MVC提供的简单异常处理器SimpleMappingExceptionResolver
(2)实现Spring的异常处理接口HandlerExceptionResolver自定义自己的异常处理器
简单异常处理器SimpleMappingExceptionResolver
SpringMVC已经定义好了该类型转换器,在使用时可以根据项目情况进行相应异常与视图的映射配置
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="defaultErrorView" value="error">property>
<property name="exceptionMappings">
<map>
<entry key="java.lang.ArithmeticException" value="error1">entry>
<entry key="java.lang.ClassCastException" value="error2"> entry>
map>
property>
bean>
自定义异常处理步骤
(1)创建异常处理器类实现HandlerExceptionResolver
public class MyResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
//创建要跳转的错误视图页面
ModelAndView modelAndView = new ModelAndView();
if(e instanceof ArithmeticException){
modelAndView.addObject("info","算数异常");
}else if(e instanceof ClassCastException){
modelAndView.addObject("info","类转换异常");
}
modelAndView.setViewName("error");
return modelAndView;
}
}
(2)配置异常处理器
<bean class="com.cqgcxy.resolver.MyResolver">bean>
(3)编写异常页面
(4)测试异常跳转
web.xml替换
public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
@Override
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(SpringMvcConfig.class);
return ctx;
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
@Override
protected WebApplicationContext createRootApplicationContext() {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(SpringConfig.class);
return ctx;
}
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
return new Filter[]{characterEncodingFilter};
}
}
springmvc.xml替换
@Configuration
//@ComponentScan({"com.cqgcxy.controller","com.cqgcxy.service"})
@ComponentScan("com.cqgcxy.controller")
public class SpringMvcConfig {
}
applicationContext.xml替换
@Configuration
//@ComponentScan({"com.cqgcxy.service","com.cqgcxy.dao"})
@ComponentScan(value = "com.cqgcxy",
excludeFilters = @ComponentScan.Filter(
type = FilterType.ANNOTATION,
classes = Controller.class
)
)
public class SpringConfig {
}
拦截器配置
添加扫描配置类所在的配置包和拦截器包
@ComponentScan({"com.cqgcxy.controller","com.cqgcxy.config","com.cqgcxy.interceptor"})
把创建的拦截器通过注解@Component放入spring容器中
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
@Autowired
private LoginIntercepter loginIntercepter;
@Autowired
private MyIntercepter myIntercepter;
@Override
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myIntercepter).addPathPatterns("/hero","/hero/**");
registry.addInterceptor(loginIntercepter).addPathPatterns("/hero","/hero/**").excludePathPatterns("/hero/login");
}
}
或者:
@Configuration
public class SpringMvcSupport implements WebMvcConfigurer {
@Autowired
private LoginIntercepter loginIntercepter;
@Autowired
private MyIntercepter myIntercepter;
//使用默认的servlet处理静态资源 default-servlet-handler
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myIntercepter).addPathPatterns("/hero","/hero/**");
registry.addInterceptor(loginIntercepter).addPathPatterns("/hero","/hero/**").excludePathPatterns("/hero/login");
}
}
使用默认的servlet处理静态资源 default-servlet-handler
@Configuration
public class SpringMvcSupport implements WebMvcConfigurer {
//使用默认的servlet处理静态资源 default-servlet-handler
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
配置视图解析器
@Bean
public ViewResolver getViewResolver(){
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/views/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
配置文件上传解析器
@Bean
public MultipartResolver multipartResolver(){
CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver();
commonsMultipartResolver.setMaxUploadSize(5242880);
commonsMultipartResolver.setMaxUploadSizePerFile(5242880);
commonsMultipartResolver.setDefaultEncoding("UTF-8");
return commonsMultipartResolver;
}
配置异常处理器
@Bean
public SimpleMappingExceptionResolver getSimpleMappingExceptionResolver(){
SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();
exceptionResolver.setDefaultErrorView("error");
Properties properties = new Properties();
properties.setProperty("java.lang.ArithmeticException","error1");
properties.setProperty("java.lang.ClassCastException","error2");
exceptionResolver.setExceptionMappings(properties);
return exceptionResolver;
}
@Bean
public HandlerExceptionResolver getMyResolver(){
return new MyResolver();
}
概念
mybatis是一个优秀的基于java的持久层框架,它内部封装了jdbc,使开发者只需要关注sql语句本身,而不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。
mybatis通过xml或注解的方式将要执行的各种statement配置起来,并通过java对象和statement中sql的动态参数进行映射生成最终执行的sql语句。
最后mybatis框架执行sql并将结果映射为java对象并返回。采用ORM思想解决了实体和数据库映射的问题,对jdbc进行了封装,屏蔽了jdbc api底层访问细节,使我们不用与jdbc api打交道,就可以完成对数据库的持久化操作。
开发步骤
(1)添加相关坐标
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.33version>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.4.6version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
(2)创建数据库表格和对应实体类XXX
(3)编写映射文件XXXMapper.xml
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="">
mapper>
(4)编写核心配置文件SqlMapConfig.xml
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties>properties>
<settings>
<setting name="" value=""/>
settings>
<typeAliases>typeAliases>
<typeHandlers>typeHandlers>
<objectFactory type="">objectFactory>
<plugins>
<plugin interceptor="">plugin>
plugins>
<environments default="">
<environment id="">
<transactionManager type="JDBC">transactionManager>
<dataSource type="POOLED">dataSource>
environment>
environments>
<databaseIdProvider type="">databaseIdProvider>
<mappers>mappers>
configuration>
(5)测试
//加裁核心义件
InputStream inputStream = Resources.getResounceAsStream( "sqlMapConfig.xml");
//获取SqlSession工厂对象
SqlSessionFactory sqLSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//获得SqlSession对象
sqlsession sqlsession = sqlSessionFactory.opensession();
//执行sql语句
List<User> userList = sqlSession.selectList( "userMapper.findAll");
//打印结果
system.out.println(userList);
//释放资源
sqlSession.close();
注意事项:
properties标签:该标签可以加载外部的properties文件
<properties resource="jdbc.properties">properties>
typeAliases标签:设置类型别名
<typeAlias type="com.cqgcxy.entity.User" alias="user">typeAlias>
settings:功能设置
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
settings>
mappers标签:加载映射配置
<!--使用相对于类路径的资源引用,例如: -->
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<!--使用完全限定资源定位符(URL),例如: -->
<mapper url="file:///var/mappers/AuthorMapper.xml" />
<!--使用映射器接口实现类的完全限定类名,例如: -->
<mapper class="org.mybatis.builder.AuthorMapper"/>
<!--将包内的映射器接口实现全部主册为映射器,例如: -->
<package name="org.mybatis.builder"/>
environments标签:环境配置
Resources 工具类,这个类在org.apache.ibatis.io包中。Resources从类路径下、文件系统或一个web URL中加载资源文件。
SqlSessionFactory build(InputStream inputStream)加载mybatis的核心文件输入流构建一个SqlSessionFactory对象。
SqlSessionFactory有多个个方法创建sqlSession实例。常用的有如下两个:
openSession()默认开启一个事务,不会自动提交,如更新数据需要手动提交事务。
openSession(boolean autoCommit)参数如果设置为true则自动提交事务。
SqlSession会话对象
SqlSession实例在MyBatis 中是非常强大的一个类。在这里你会看到所有执行语句、提交或回滚事务和获取映射器实例
的方法。
执行语句的方法主要有:
<T> T selectone(string statement,object parameter)
<E> List<E> selectList (string statement,object parameter)
int insert (string statement,object parameter)
int update (string statement,object parameter)
int delete (string statement,object parameter)
操作事务的方法主要有:
void commit()
void rollback()
传统开发方式
public interface UserMapper {
List<User> findAll() throws IOException;
}
public class UserMapperImpl implements UserMapper {
public List<User> findAll() throws IOException{
InputStream inputStream = Resources.getResounceAsStream( "sqlMapConfig.xml");
SqlSessionFactory sqLSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
sqlsession sqlsession = sqlSessionFactory.opensession();
List<User> userList = sqlSession.selectList( "userMapper.findAll");
sqlSession.close();
return userList;
}
}
代理开发方式
Mapper接口开发方法只需要程序员编写Mapper接口(相当于Dao接口),由Mybatis框架根据接口定义创建接口的动态代理对象,代理对象的方法体同上边Dao接口实现类方法。
Mapper接口开发需要遵循以下规范:
(1)Mapper.xml文件中的namespace与mapper接口的全限定名相同
(2)Mapper接口方法名和Mapper.xml中定义的每个statement的id相同
(3)Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql的parameterType的类型相同
(4)Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同
测试代理方式
@Test
public void findAllTest() throws IOException{
InputStream inputStream = Resources.getResounceAsStream( "sqlMapConfig.xml");
SqlSessionFactory sqLSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
sqlsession sqlsession = sqlSessionFactory.opensession();
//获得MyBatis框架生成的UserMapper接口的实现类
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = userMapper.findAll();
sqlSession.close();
return userList;
}
动态SQL之
我们根据实体类的不同取值,使用不同的SQL语句来进行查询。比如在id如果不为空时可以根据id查询,如果username不同空时还要加入用户名作为条件。这种情况在我们的多条件组合查询中经常会碰到。
List<User> findByCondition(User user);
<select id="findByCondition" parameterType="user" resultType="user">
select * from User
<where>
<if test="id!=0">and id=#{id}if>
<if test="username!=null">and username=#{username}if>
where>
select>
动态SQL之
循环执行sql的拼接操作,例: SELECT*FROM USER WHERE id IN (1,2,5)。
List<User> findByIds(int[] ids);
<select id="findByIds" parameterType="list" resultType="user">
select * from User
<where>
<foreach collection="array" open="id in(" close=")" item="id" separator=", ">
#{id}
foreach>
where>
select>
List<User> findByIds(List<In> ids);
<select id="findByIds" parameterType="list" resultType="user">
select * from User
<where>
<foreach collection="list" open="id in(" close=")" item="id" separator=", ">
#{id}
foreach>
where>
select>
无论是MyBatis在预处理语句(PreparedStatement)中设置参数时,还是从结果集中取出值时,都会用类型处理器将获取的值以合适的方式转换成Java类型。
你可以重写类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型。
需求:Java中的Date数据类型,存到数据库时存成毫秒数,取出来时转换成java的Date,即java的Date与数据库的varchar毫秒值之间转换。
步骤:
定义转换类继承类BaseTypeHandler
覆盖4个未实现的方法,其中setNonNullarameter为java程序设置数据到数据库的回调方法,getNullableResult
为查询时mysql的字符串类型转换成java的Type类型的方法
public class DateTypeHandler extends BaseTypeHandler<Date> {
//将java转成数据库需要类型
@Override
public void setNonNullParameter(PreparedStatement preparedStatement, int i, Date date, JdbcType jdbcType) throws SQLException {
long time=date.getTime();
//把第i个参数设置成转后的值
preparedStatement.setLong(i,time);
}
//将数据库类型转成java类型
//String s:要转换的字段名称
//ResultSet resultSet:查询出的结果集
@Override
public Date getNullableResult(ResultSet resultSet, String s) throws SQLException {
long time=resultSet.getLong(s);
return new Date(time);
}
//将数据库类型转成java类型
@Override
public Date getNullableResult(ResultSet resultSet, int i) throws SQLException {
long time = resultSet.getLong(i);
return new Date(time);
}
//将数据库类型转成java类型
@Override
public Date getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
long time = callableStatement.getLong(i);
return new Date(time);
}
}
在MyBatis核心配置文件中进行注册
<typeHandlers>
<typeHandler handler="com.cqgcxy.handler.DateTypeHandler">typeHandler>
typeHandlers>
测试转换是否正确
MyBatis可以使用第三方的插件来对功能进行扩展,分页助手PageHelper是将分页的复杂操作进行封装,使用简单的方式即可获得分页的相关数据。
步骤:
导入通用PageHelper的坐标
<dependency>
<groupId>com.github.pagehelpergroupId>
<artifactId>pagehelperartifactId>
<version>3.7.5version>
dependency>
<dependency>
<groupId>com.github.jsqlparsergroupId>
<artifactId>jsqlparserartifactId>
<version>0.9.5version>
dependency>
在mybatis核心配置文件中配置PageHelper插件
<plugins>
<plugin interceptor="com.github.pagehelper.PageHelper">
<property name="dialect" value="mysql"/>
plugin>
plugins>
测试分页数据获取
public class HeroMapperTest {
@Test
public void findAll() throws IOException {
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession();
HeroMapper heroMapper = sqlSession.getMapper(HeroMapper.class);
//启动分页并设置参数
PageHelper.startPage(1, 2);
List<Hero> heroList = heroMapper.findAll();
heroList.forEach(s-> System.out.println(s));
//获得分页相关数据
PageInfo<Hero> heroPageInfo = new PageInfo<>(heroList);
System.out.println("当前页:"+heroPageInfo.getPageNum());
System. out.println("每页显示条数: "+heroPageInfo.getPageSize());
System. out. println("总条数: "+heroPageInfo.getTotal());
System. out. println("总页数: "+heroPageInfo. getPages());
System. out.println("上一页: "+heroPageInfo.getPrePage());
System. out.println("下一页: "+heroPageInfo. getNextPage()) ;
System. out.println("是否是第一个: "+heroPageInfo.isIsFirstPage());
System. out. println("是否是最后一个: "+heroPageInfo. isIsLastPage());
sqlSession.close();
}
}
public class Order {
private Long id;
private BigDecimal totalPrice;
private Date orderDate;
//一对一
private Hero hero;
//···
}
//方式一:
<resultMap id="orderMap" type="com.cqgcxy.entity.Order">
<id column="id" property="id">id>
<id column="total_price" property="totalPrice">id>
<id column="order_date" property="orderDate">id>
<id column="hid" property="hero.id">id>
<id column="hero_name" property="hero.heroName">id>
<id column="create_time" property="hero.createTime">id>
resultMap>
<select id="findAll" resultMap="orderMap">
select o.*,h.id hid,h.hero_name,h.create_time from t_order o,t_hero h where o.hid=h.id
select>
//方式二:
<resultMap id="orderMap" type="com.cqgcxy.entity.Order">
<id column="id" property="id">id>
<id column="total_price" property="totalPrice">id>
<id column="order_date" property="orderDate">id>
<association property="hero" javaType="com.cqgcxy.entity.Hero">
<id column="hid" property="id">id>
<id column="hero_name" property="heroName">id>
<id column="create_time" property="createTime">id>
association>
resultMap>
<select id="findAll" resultMap="orderMap">
select o.*,h.id hid,h.hero_name,h.create_time from t_order o,t_hero h where o.hid=h.id
select>
一对多
public class Hero {
private Long id;
private String heroName;
private Date createTime;
private List<Order> orderList;
//···
}
<resultMap id="heroMap" type="com.cqgcxy.entity.Hero">
<id column="id" property="id">id>
<result column="hero_name" property="heroName">result>
<result column="create_time" property="createTime">result>
<collection property="orderList" ofType="com.cqgcxy.entity.Order">
<id column="id" property="id">id>
<id column="total_price" property="totalPrice">id>
<id column="order_date" property="orderDate">id>
collection>
resultMap>
<select id="findAll" resultMap="heroMap">
select h.*,o.id oid,o.total_price,o.order_date from t_order o,t_hero h where o.hid=h.id
select>
多对多
**注意:**多对多实现同一对多。不同点在于,多对多至少是三张表之前的关系(包含关联表)。
@lnsert:实现新增
@Update:实现更新
@Delete:实现删除
@Select:实现查询
@Result:实现结果集封装
@Results:可以与@Result一起使用,封装多个结果集
@One:实现一对一结果集封装
@Many:实现一对多结果集封装
注意:mapper配置不用在配置mapper文件地址,而是配置包的路径。
<mappers>
<package name="com.cqgcxy.dao"/>
mappers>
一对一
//方式一:
@Select("select o.*,h.id hid,h.hero_name,h.create_time from t_order o,t_hero h where o.hid=h.id")
@Results({
@Result(column = "id",property = "id"),
@Result(column = "total_price",property = "totalPrice"),
@Result(column = "order_date",property = "orderDate"),
@Result(column = "hid",property = "hero.id"),
@Result(column = "hero_name",property = "hero.heroName"),
@Result(column = "create_time",property = "hero.createTime")
})
List<Order> findAll();
//方式二:
//HeroMapper.java中
@Select("select * from t_hero where id = #{id}")
Hero findById(Integer id);
//OrderMapper.java中
@Select("select o.*,h.id hid,h.hero_name,h.create_time from t_order o,t_hero h where o.hid=h.id")
@Results({
@Result(column = "id",property = "id"),
@Result(column = "total_price",property = "totalPrice"),
@Result(column = "order_date",property = "orderDate"),
@Result(column = "hid",
property = "hero",
javaType = Hero.class,
one = @One(select="com.cqgcxy.dao.HeroMapper.findById")
)
})
List<Order> findAll();
一对多
//OrderMapper.java中
@Select("select * from t_order where id =#{id}")
Order findById(Integer id);
//HeroMapper.java中
@Select("select h.*,o.id oid,o.total_price,o.order_date from t_order o,t_hero h where o.hid=h.id")
@Results({
@Result(column = "id",property = "id"),
@Result(column = "hero_name",property = "heroName"),
@Result(column = "create_time",property = "createTime"),
@Result(column = "oid",
property = "orderList",
javaType = List.class,
many = @Many(select = "com.cqgcxy.dao.OrderMapper.findById")
)
})
List<Hero> findAll();
多对多
**注意:**多对多实现同一对多。不同点在于,多对多@Many中的查询语句要根据条件id查询包括关联表在内的两张表。
动态sql
@Select({""
})
List<Hero> findByCondition(Hero hero);
导入坐标依赖
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>5.3.8version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-jdbcartifactId>
<version>5.3.8version>
dependency>
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
<version>2.9.0version>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
<version>3.1.0version>
<scope>providedscope>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.33version>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.4.6version>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatis-springartifactId>
<version>1.3.0version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.2.4version>
dependency>
编写实体类
编写mapper接口
编写service接口和实现类
编写控制器
整合配置文件
<context-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:applicationContext*.xmlparam-value>
context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
listener>
<servlet>
<servlet-name>dispatcherServletservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:springmvc.xmlparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>dispatcherServletservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
<filter>
<filter-name>characterEncodingFilterfilter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilterfilter-class>
<init-param>
<param-name>encodingparam-name>
<param-value>utf-8param-value>
init-param>
filter>
<filter-mapping>
<filter-name>characterEncodingFilterfilter-name>
<servlet-name>dispatcherServletservlet-name>
filter-mapping>
springmvc.xml
<context:component-scan base-package="com.cqgcxy.controller">context:component-scan>
<mvc:annotation-driven />
applicationContext.xml
<context:component-scan base-package="com.cqgcxy.service">context:component-scan>
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="ds" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value= "${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
bean>
<bean id="sqlSessionFactory" class=" org.mybatis.spring.SqlSessionFactoryBean ">
<property name="dataSource" ref="ds" />
<property name="typeAliasesPackage" value="com.cqgcxy.entity"/>
<property name="configuration">
<bean class="org.apache.ibatis.session.Configuration">
<property name="mapUnderscoreToCamelCase" value="true"/>
bean>
property>
bean>
<bean class=" org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.cqgcxy.mapper"/>
bean>
创建Web项目入口配置类替换web.xml
public class ServletConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
//加载Spring配置类
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}
//加载SpringMVC配置类
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}
//设置SpringMVC请求地址拦截规则
protected String[] getServletMappings() {
return new String[]{"/"};
}
//设置post请求中文乱码过滤器
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("utf-8");
return new Filter[]{filter};
}
}
创建SpringMVC配置类替换springmvc.xml
@Configuration
@ComponentScan("com.cqgcxy.controller")
@EnableWebMvc
public class SpringMvcConfig {
}
创建SpringConfig配置类替换applicationContext.xml
@Configuration
@ComponentScan({"com.cqgcxy.service"})
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MyBatisConfig.class})
@EnableTransactionManagement
public class SpringConfig {
}
创建JdbcConfig配置类
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource dataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){
DataSourceTransactionManager ds = new DataSourceTransactionManager();
ds.setDataSource(dataSource);
return ds;
}
}
创建MybatisConfig配置类
public class MyBatisConfig {
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.setTypeAliasesPackage("com.cqgcxy.entity");
//用于处理数据库表下划线跟实体类的字段名称不一致问题
Configuration configuration = new Configuration();
configuration.setMapUnderscoreToCamelCase(true);
factoryBean.setConfiguration(configuration);
return factoryBean;
}
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer msc = new MapperScannerConfigurer();
msc.setBasePackage("com.cqgcxy.mapper");
return msc;
}
}
.cqgcxy.service"})
@PropertySource(“classpath:jdbc.properties”)
@Import({JdbcConfig.class,MyBatisConfig.class})
@EnableTransactionManagement
public class SpringConfig {
}
4. 创建JdbcConfig配置类
```java
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource dataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){
DataSourceTransactionManager ds = new DataSourceTransactionManager();
ds.setDataSource(dataSource);
return ds;
}
}
创建MybatisConfig配置类
public class MyBatisConfig {
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.setTypeAliasesPackage("com.cqgcxy.entity");
//用于处理数据库表下划线跟实体类的字段名称不一致问题
Configuration configuration = new Configuration();
configuration.setMapUnderscoreToCamelCase(true);
factoryBean.setConfiguration(configuration);
return factoryBean;
}
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer msc = new MapperScannerConfigurer();
msc.setBasePackage("com.cqgcxy.mapper");
return msc;
}
}