SpringMVC
SpringMVC常用配置
搭建环境
<dependencies>
<!-- SpringMVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.1</version>
</dependency>
<!-- 日志 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!-- ServletAPI -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- Spring5和Thymeleaf整合包 -->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.0.12.RELEASE</version>
</dependency>
</dependencies>
日志配置文件
logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
<!-- 指定日志输出的位置 -->
<appender name="STDOUT"
class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!-- 日志输出的格式 -->
<!-- 按照顺序分别是:时间、日志级别、线程名称、打印日志的类、日志主体内容、换行 -->
<pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger] [%msg]%n</pattern>
</encoder>
</appender>
<!-- 设置全局日志级别。日志级别按顺序分别是:DEBUG、INFO、WARN、ERROR -->
<!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志。 -->
<root level="DEBUG">
<!-- 指定打印日志的appender,这里通过“STDOUT”引用了前面配置的appender -->
<appender-ref ref="STDOUT" />
</root>
<!-- 根据特殊需求指定局部日志级别 -->
<logger name="org.springframework.web.servlet.DispatcherServlet" level="DEBUG" />
</configuration>
web.xml配置
<!-- 配置SpringMVC中负责处理请求的核心Servlet,也被称为SpringMVC的前端控制器 -->
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<!-- DispatcherServlet的全类名 -->
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 通过初始化参数指定SpringMVC配置文件位置 -->
<init-param>
<!-- 如果不记得contextConfigLocation配置项的名称,可以到DispatcherServlet的父类FrameworkServlet中查找 -->
<param-name>contextConfigLocation</param-name>
<!-- 使用classpath:说明这个路径从类路径的根目录开始才查找 -->
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<!-- 作为框架的核心组件,在启动过程中有大量的初始化操作要做,这些操作放在第一次请求时才执行非常不恰当 -->
<!-- 我们应该将DispatcherServlet设置为随Web应用一起启动 -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<!-- 对DispatcherServlet来说,url-pattern有两种方式配置 -->
<!-- 方式一:配置“/”,表示匹配整个Web应用范围内所有请求。 -->
<!-- 方式二:配置“*.扩展名”,表示匹配整个Web应用范围内部分请求 -->
<url-pattern>/</url-pattern>
</servlet-mapping>
Spring配置文件
复制即可
<!-- 自动扫描包 -->
<context:component-scan base-package="com.kevin.mvc.handler"/>
<!-- Thymeleaf视图解析器 -->
<bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
<!-- 视图解析器优先级-->
<property name="order" value="1"/>
<!-- 解析视图编码-->
<property name="characterEncoding" value="UTF-8"/>
<!-- 设置模板-->
<property name="templateEngine">
<bean class="org.thymeleaf.spring5.SpringTemplateEngine">
<property name="templateResolver">
<bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
<!-- 视图前缀 -->
<property name="prefix" value="/WEB-INF/templates/"/>
<!-- 视图后缀 -->
<property name="suffix" value=".html"/>
<property name="templateMode" value="HTML5"/>
<property name="characterEncoding" value="UTF-8" />
</bean>
</property>
</bean>
</property>
</bean>
常见注解
RequestMapping注解(重点)
可以标记在类上和方法上
注解标记在类上
网页访问时需要添加类的注解和方法上的注解路径
<h3>测试@RequestMapping注解标记在类上</h3>
<a th:href="@{/user/login}">用户登录</a><br/>
<a th:href="@{/user/register}">用户注册</a><br/>
<a th:href="@{/user/logout}">用户退出</a><br/>
指定请求方式
//如果不加请求方式,post和get都可以进入该方法,设置了则不行
原版 | 进阶版 |
---|---|
@RequestMapping(value = "/emp", method = RequestMethod.GET)指定为GET请求 | @GetMapping("/emp") |
@RequestMapping(value = "/emp", method = RequestMethod.POST)指定为POST请求 | @PostMapping("/emp") |
@RequestMapping(value = "/emp", method = RequestMethod.PUT)指定为PUT请求 | @PutMapping("/emp") |
@RequestMapping(value = "/emp", method = RequestMethod.DELETE)指定为DELETE请求 | @DeleteMapping("/emp") |
获取请求参数(重点)
根据一个参数名获取一个参数值
最简单的方式获取单个请求参数:就是在handler方法中添加一个和请求参数名同名的参数,来接收请求参数
使用RequestParam注解
@RequestMapping("/oneParameter")
public String oneParameter(@RequestParam("username") String username){
//最简单的方式获取单个请求参数:就是在handler方法中添加一个和请求参数名同名的参数,来接收请求参数
//其实这个地方String username此处省略了一个注解 @RequestParam
//@RequestParam("username")就是通过参数名获取请求参数
logger.debug(username);
return "target";
}
注意
如果这个值没有传输将会报错,默认属性是required=true自动装配
@RequestParam 注解的 required 属性:默认值为true,表示请求参数默认必须提供
可以通过将required 属性设置为 false 表示这个请求参数可有可无:
@RequestParam(value = "userName", required = false)可以设置为false
也可以通过设置请求参数的默认值来解决上述400
错误-貌似常用
@RequestParam(value = "userName", defaultValue = "missing")
根据一个参数名获取多个参数值
如果是多选框,同名参数,会把他们的值用逗号隔开
可以使用数组或者list或者map或者pojo装配
参数中使用对应的装配对象
可以设置@RequestParam的属性value设置映射值
@RequestParam("team")List<String> teamList
Employee employee
@RequestParam Map map
访问静态资源(重点)和标配配置
SpringMVC 配置文件中增加配置:
<!-- 加入这个配置,SpringMVC 就会在遇到没有 @RequestMapping 的请求时放它过去 -->
<!-- 所谓放它过去就是让这个请求去找它原本要访问的资源 -->
<mvc:default-servlet-handler/>
<!-- 开启 SpringMVC 的注解驱动功能。这个配置也被称为 SpringMVC 的标配。 -->
<!-- 标配:因为 SpringMVC 环境下非常多的功能都要求必须打开注解驱动才能正常工作。 -->
<mvc:annotation-driven/>
请求转发的指令
@RequestMapping("/forwardCommand")
public String forwardCommand(){
//使用转发指令:在handler方法中,访问Thymeleaf前后缀控制范围外的页面
//"forward:要转发到的资源路径",相当于使用的是请求转发跳转
//请求转发的绝对路径是:在uri的基础上省略"/项目名"
// return "forward:/outter.html";
return "forward:/forwardCommand2"
}
重定向的指令
@RequestMapping("/redirectCommand")
public String redirectCommand(){
//使用重定向指令:在handler方法中,访问Thymeleaf前后缀控制范围外的页面
//"redirect:要转发到的资源路径",相当于使用的是请求转发跳转
//重定向的绝对路径:就是uri "/项目名/资源路径"
//因为SpringMVC框架在接收到redirect指令之后,会自动给访问路径加上"/项目名"
//return "redirect:/outter.html";
return "redirect:www.baidu.com";
}
mvc:view-controller访问页面(重点)
如果只有跳转某个页面而没有方法体,就可以不用写方法,可以使用
mvc配置文件配置标签即可替代
跳转到首页
<mvc:view-controller path="/" view-name="index"/>
SpringMVC使用域对象
ModelAndView -----推荐使用
有model和view功能
model主要用于请求域共享数据
view主要用于设置视图,实现页面跳转
@RequestMapping("/attr/request/mav")
public ModelAndView testAttrByModelAndView(ModelAndView modelAndView) {
// 2.存入模型数据
modelAndView.addObject("key", "value");
// 3.设置视图名称
modelAndView.setViewName("target");
return modelAndView;
}
使用 Model 类型的形参操作
@RequestMapping("/testAttrRequestModel")
public String testAttrRequestModel(Model model){
//目标:目标:将数据存储到请求域对象,然后跳转到target页面
//方式二:使用Model对象往请求域中存储值
model.addAttribute("username","kevin");
return "target";
}
模型的本质
不管是model还是modelMap,map等都会返回成BindingAwareModelMap
最后都会调用控制器,控制器封装成ModelAndView
会话域对象session
使用会话域最简单直接的办法就是使用原生的 HttpSession 对象
@RequestMapping("/attrSession")
public String attrSession(HttpSession httpSession){
//目标:往会话域中存储数据
httpSession.setAttribute("address","召唤师峡谷");
return "target";
}
应用域
可以使用使用IOC注入的方式来操作:
@Autowired
private ServletContext servletContext;
@RequestMapping("/attr/application")
public String attrApplication() {
servletContext.setAttribute("appScopeMsg", "i am hungry...");
return "target";
}
第二种方式
//获得context第二种方法
public String attrSession(HttpSession httpSession){
ServletContext application = httpSession.getServletContext();
application.setAttribute(key,value);
return "target";
}
解决乱码问题
解决POST请求的中文字符乱码问题
放在servlet之前
mvc自带过滤器
webxml配置文件
<!-- 配置过滤器解决 POST 请求的字符乱码问题 -->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<!-- encoding参数指定要使用的字符集名称 -->
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<!-- 请求强制编码 -->
<init-param>
<param-name>forceRequestEncoding</param-name>
<param-value>true</param-value>
</init-param>
<!-- 响应强制编码 -->
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<!-- 处理所有请求 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
RESTFul风格交互方式
操作 | 请求方式 |
---|---|
查询操作 | GET |
保存操作 | POST |
删除操作 | DELETE |
更新操作 | PUT |
不是通过方法名增删改查,而是通过请求方式不同做不同的反应
从前到后各个单词使用斜杠分开,不使用问号键值对方式携带请求参数
请求方式的映射
DELETE 和 PUT 请求无法直接做到,所以需要映射
修改web.xml文件
<!--一定要配置在解决乱码的Filter之后-->
<filter>
<filter-name>hiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>hiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
注意
- 要点1:原请求方式必须是 post
- 要点2:新的请求方式名称通过请求参数发送
- 要点3:请求参数名称必须是 _method
- 要点4:请求参数的值就是要改成的请求方式
<form th:action="@{/rest/movie}" method="post">
<input type="hidden" name="_method" value="put"/>
<button>发送请求</button>
</form>
由于删除一般是超链接,所以可以用vue或者原生做一个点击响应
因为超链接默认 是get请求,post只能通过form表单发送
所以可以在点击事件里取消默认行为,绑定表单发送post请求
<div id="app">
<a @click="deleteUser" th:href="@{/user/1}">删除用户信息</a>
<form method="post" id="deleteForm">
<input type="hidden" name="_method" value="delete">
</form>
</div>
<script type="text/javascript">
let vm = new Vue({
el:"#app",
methods:{
deleteUser(){
//根据id获取表单元素
let formId = document.getElementById("deleteForm");
//触发点击事件的超连接属性赋值给表单action
formId.action = event.target.href;
//提交表单
formId.submit();
// 取消超链接默认行为
event.preventDefault();
}
}
})
</script>
获取路径参数
handler里的{}表示占位符
表示获取该位置的值,@PathVariable("id")表示获取占位符为"id"的值
此值必须传送,否则报错
单个路径参数
<a th:href="@{/user/1}">根据id查询用户信息</a><br>
handler
@GetMapping("/user/{id}")
public String getUserById(@PathVariable("id") Integer id){
System.out.println("根据id查询用户信息");
System.out.println(id);
return "target";
}
多个路径参数
<a th:href="@{/rest/movie/2/22/123}">携带多个参数</a>
handler
@GetMapping("/movie/{categoryId}/{groupId}/{movieId}")
public String findMovieById(@PathVariable("categoryId") Integer categoryId,@PathVariable("groupId")Integer groupId,@PathVariable("movieId") Integer movieId){
logger.debug("GET请求...."+categoryId+":"+groupId+":"+movieId);
return "target";
}
Ajax交互
注解使用的是jackson转换,需要导入jar包
用来接受json数据才用这个参数
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.1</version>
</dependency>
常用注解
ResponseBody注解
拿当前方法的返回值转成JSON个数数据作为响应体,不要再找视图.
@ResponseBody
@RequestMapping("/jsonBodyParameter")
public Movie jsonBodyParameter(@RequestBody User user){
logger.debug(user.toString());
Movie movie = new Movie(1, "西游记", 40.0);
//目标:将movie转成json字符串响应给客户端
//1. handler方法的返回值就是你要转成json的那个对象
//2. handler方法上必须添加ResponseBody注解
//3. 你的项目中一定要引入了jackson的依赖
return movie;
}
RequestBody注解
当前段用axios传输json数据的时候封装bean,需要在参数
上使用这个注解,表示是json请求体
主要用来接收前端传递给后端的json字符串中的数据的,注解只能加在方法的参数上
前端的请求方式必须为post,因为post请求方式才具有请求体数据。
RestController注解
如果该类每个方法都是返回的json数据就可以使用这个注解再类上
表示类上的ResponseBody 注解可以和Controller 注解合并为RestController 注解
@RestController
public class Movie{}
数据校验(重要)
导入依赖jar包
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.2.0.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator-annotation-processor</artifactId>
<version>6.2.0.Final</version>
</dependency>
注解 | 规则 |
---|---|
@Null | 标注值必须为 null |
@NotNull | 标注值不可为 null |
@AssertTrue | 标注值必须为 true |
@AssertFalse | 标注值必须为 false |
@Min(value) | 标注值必须大于或等于 value |
@Max(value) | 标注值必须小于或等于 value |
@DecimalMin(value必须是字符串) | 标注值必须大于或等于 value,用于校验double精度问题 |
@DecimalMax(value必须是字符串) | 标注值必须小于或等于 value,用于校验double精度问题 |
@Size(max,min)集合的长度 | 集合长度,标注值大小必须在 max 和 min 限定的范围内 |
@Digits(integer,fratction) | 标注值值必须是一个数字,且必须在可接受的范围内,integer整数部位精度,fratction小数部位精度 |
@Past | 标注值只能用于日期型,且必须是过去的日期,或者当天 |
@Future | 标注值只能用于日期型,且必须是将来的日期,不能是当天 |
@Pattern(value) | 标注值必须符合指定的正则表达式 |
标注值必须是格式正确的 Email 地址 | |
@Length | 标注值字符串大小必须在指定的范围内 |
@NotEmpty | 标注值字符串不能是空字符串 |
给要进行校验的字段添加上校验规则注解
@Data
@AllArgsConstructor
@NoArgsConstructor
public class President {
// 字符串长度:[3,6]
@Size(min = 3, max = 6)
private String username;
// 字符串必须满足Email格式
@Email
private String email;
}
给handler方法的形参加上@Validated注解
@Validated : 对当前的参数进行数据校验
@RequestMapping("/savePresident")
public String savePresident(@Validated President president){
logger.debug(president.toString());
return "target";
}
校验错误显示友好提示信息
参数加上BindingResult bindingResult
接口: BindingResult 绑定是数据校验的结果
boolean boolean hasErrors() 判断是有校验的数据错误
FieldError getFieldError(); 返回验证错误的字段
FieldError类的方法getField() 返回出错的字段名字
@RequestMapping("/save/president")
public String savePresident(
// 在实体类参数和 BindingResult 之间不能有任何其他参数
@Validated President president, BindingResult bindingResult) {
//判断是否有字段验证失败
if (bindingResult.hasErrors()) {
//验证结果的绑定对象,方法返回出错误的字段对象
FieldError fieldError = bindingResult.getFieldError();
//出错的字段对象的方法,获取出现的详细信息
String defaultMessage = fieldError.getDefaultMessage();
//字段对象,获取字段名,获得发生错误的字段
String field = fieldError.getField();
//跳转自定义错误的网页
return "error";
}
logger.debug(president.getEmail());
return "target";
}
文件上传(重要)
前段需要满足的要求:
- 第一点:请求方式必须是 POST
- 第二点:请求体的编码方式必须是 multipart/form-data(通过 form 标签的 enctype 属性设置)
- 不然会默认传参,&&的方式,所以需要设置为enctype,设置为二进制传输
- 第三点:使用 input 标签、type 属性设置为 file 来生成文件上传框
- 文件域inputy标签,必须有name属性值
表单
<form th:action="@{/kevin/upload}" method="post" enctype="multipart/form-data">
<input type="file" name="picture" />
<button type="submit">上传头像</button>
</form>
MultipartFile接口-文件上传解析器
引入依赖--胡
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
在 SpringMVC 的配置文件中加入 multipart 类型数据的解析器: --胡
id名必须叫这个!!!!!!!!
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 由于上传文件的表单请求体编码方式是 multipart/form-data 格式,所以要在解析器中指定字符集 -->
<property name="defaultEncoding" value="UTF-8"/>
</bean>
具体代码
Logger logger = LoggerFactory.getLogger(this.getClass());
//文件的上传
@RequestMapping("testUp")
//多文件上传,改为数组MultipartFile[] photo,然后遍历即可
public String testUp(MultipartFile photo) throws IOException {
logger.debug(photo.getName());
//获得上传文件名,防止重名覆盖,需要uuid
String filename = photo.getOriginalFilename();
//获得文件后缀,防止重名覆盖,通过最后一个点截取
String suffixName = filename.substring(filename.lastIndexOf("."));
//将其文件名改成一个唯一的名字,这样就能保证不会出现上传文件同名的情况
//唯一的文件名就是UUIDName拼接后缀
filename = UUID.randomUUID().toString()+suffixName;
//1. 指定转存的目录路径:动态获取部署的目录路径
String phptoPath = servletContext.getRealPath("photo");
//如果文件路径不存在则创建
File file = new File(phptoPath);
if (!file.exists()){
// 不存在则创建目录
file.mkdirs();
}
// 父路径和文件名
File finalPath = new File(phptoPath,filename);
//要将该文件转存到目录中
photo.transferTo(finalPath);
return "success";
}
拦截器(重点)
拦截器与过滤器的对比
相同点
- 拦截(配置拦截路径):必须先把请求拦住,才能执行后续操作
- 过滤(根据某种规则/业务逻辑进行筛选):拦截器或过滤器存在的意义就是对请求进行统一处理
- 放行(满足规则/筛选条件,就让你访问你想访问的资源):对请求执行了必要操作后,放请求过去,让它访问原本想要访问的资源
不同点
- 工作平台不同
- 过滤器工作在 Servlet 容器中
- 拦截器工作在 SpringMVC 的基础上
- 拦截的范围
- 过滤器:能够拦截到的最大范围是整个 Web 应用
- 拦截器:能够拦截到的最大范围是整个 SpringMVC 负责的请求(handler方法、view-controller跳转页面)
- IOC 容器支持
- 过滤器:想得到 IOC 容器需要调用专门的工具方法,是间接的
- 拦截器:它自己就在 IOC 容器中,所以可以直接从 IOC 容器中装配组件,也就是可以直接得到 IOC 容器的支持
自定义拦截器
- 实现接口HandlerInterceptor
- 接口有默认方法,手动实现
- 重写方法:
- preHandle:handler之前执行,返回true表示放行
- postHandle:handler逻辑真正执行完成但尚未返回页面
- 性能测试
- afterCompletion:返回页面之后----渲染完之后
- 性能测试
springmvc配置拦截器
默认拦截全部请求
//配置多个拦截器
<mvc:interceptors>
//配置单个拦截器
<mvc:interceptor>
//要拦截的handler,/*拦截一层目录,/**拦截多层目录(web应用程序下的所有handler)
<mvc:mapping path="/**"/>
//不拦截的handler
<mvc:exclude-mapping path="/user/**"/>
<bean class="com.kevin.interceptor.MyInterceptor" id="interceptor"/>
</mvc:interceptor>
</mvc:interceptors>
多个拦截器执行顺序
- 拦截器链执行时,拦截器链正常流程测试
- preHandle顺序执行 i++
- postHandle倒序执行 i--
- if判断拦截,true走postHandle,false调用afterCompletion
- afterCompletion倒序执行 i--
- 拦截器链中断流程测试
- 拦截器链中有中断时,整个链中的拦截器的postHandle都不会执行
SSM整合(重点)
总体 SSM 整合所需依赖
<dependencies>
<!-- 日志依赖-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!-- spring依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- spring和thymeleaf整合包-->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.0.12.RELEASE</version>
</dependency>
<!-- Mybatis核心-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.5</version>
</dependency>
<!-- mybatis和spring整合包-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.5</version>
</dependency>
<!-- mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<!-- 德鲁伊连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<!-- 分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.2</version>
</dependency>
<!-- lombok依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
<!-- servlet依赖-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- 导入jackson依赖-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
<!-- junit依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
业务层和持久层的配置分成2个文件,各自配置各自需要的内容,相互之间不干扰
配置数据源
db.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?characterEncoding=UTF8
jdbc.username=root
jdbc.password=root
加入日志配置文件
logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
<!-- 指定日志输出的位置 -->
<appender name="STDOUT"
class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!-- 日志输出的格式 -->
<!-- 按照顺序分别是:时间、日志级别、线程名称、打印日志的类、日志主体内容、换行 -->
<pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger] [%msg]%n</pattern>
</encoder>
</appender>
<!-- 设置全局日志级别。日志级别按顺序分别是:DEBUG、INFO、WARN、ERROR -->
<!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志。 -->
<root level="DEBUG">
<!-- 指定打印日志的appender,这里通过“STDOUT”引用了前面配置的appender -->
<appender-ref ref="STDOUT" />
</root>
<!-- 根据特殊需求指定局部日志级别 -->
<logger name="com.stu.mapper" level="DEBUG"/>
<logger name="org.springframework.web.servlet.DispatcherServlet" level="DEBUG" />
</configuration>
创建业务层配置文件
加入声明式事务
application-service.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 配置包扫描-->
<context:component-scan base-package="com.stu.service"/>
<!-- 配置事务管理-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<constructor-arg name="dataSource" ref="dataSource"/>
</bean>
<!-- 开启事务注解-->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
创建 Mybatis 全局配置文件
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 全局设置-->
<settings>
<!-- 配置自动映射-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 懒加载-->
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
</configuration>
创建Mapper配置文件(存放路径在resources中要与对应的接口的路径一致)
resources下面包路径取名必须是/
com/stu/mapper 会自动用点代替
MovieMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.stu.mapper.MovieMapper">
<!-- 查询所有用户-->
<select id="movieList" resultType="movie">
select movie_id,movie_name,movie_price from movie
</select>
</mapper>
持久层的配置
application-mapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 引入外部文件-->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 配置德鲁伊-->
<bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource">
<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>
<!--
配置Spring框架和MyBatis框架合体
mybatis-spring.jar包中类
SqlSessionFactoryBean 框架的整合类: 定义了SqlSessionFactoryBuilder
SqlSessionFactoryBean类配置 插件 (MyBatis框架功能没有,插件是增强)
字段 : plugins 插件
-->
<bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sqlSessionFactoryBean">
<!-- 注入数据源-->
<property name="dataSource" ref="dataSource"/>
<!--配置别名包扫描-->
<property name="typeAliasesPackage" value="com.stu.pojo"/>
<!--映射mybatis配置文件-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<!--set方法注入,分页助手插件-->
<property name="plugins">
<array>
<bean class="com.github.pagehelper.PageInterceptor" id="interceptor">
<!--
对象需要注入 集合 Properties
配置数据库的方言
SQL语句:标准化,CRUD数据库都是一样,普通话
SQL语句是有区别的 方法
-->
<property name="properties">
<props>
<prop key="helperDialect">mysql</prop>
</props>
</property>
</bean>
</array>
</property>
</bean>
<!--配置MyBatis框架的接口扫描,扫描mapper接口,动态代理-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 扫描包名-->
<property name="basePackage" value="com.stu.mapper"/>
<!--set方法注入, 注入对象SqlSessionFactoryBean, 字段类型是字符串-->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryBean"/>
</bean>
</beans>
Spring 和 SpringMVC 整合
springmvc配置
springmvc.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置包扫描-->
<context:component-scan base-package="com.stu.handler"/>
<!-- 开启注解驱动-->
<mvc:annotation-driven/>
<!--3. 静态资源处理-->
<mvc:default-servlet-handler/>
<!-- 配置视图转发器-->
<!-- Thymeleaf视图解析器 -->
<bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
<property name="order" value="1"/>
<property name="characterEncoding" value="UTF-8"/>
<property name="templateEngine">
<bean class="org.thymeleaf.spring5.SpringTemplateEngine">
<property name="templateResolver">
<bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
<!-- 视图前缀 -->
<property name="prefix" value="/WEB-INF/templates/"/>
<!-- 视图后缀 -->
<property name="suffix" value=".html"/>
<!--模板类型-->
<property name="templateMode" value="HTML5"/>
<!--模板的字符编码-->
<property name="characterEncoding" value="UTF-8"/>
</bean>
</property>
</bean>
</property>
</bean>
<!--配置view-controller-->
<mvc:view-controller path="/" view-name="index"/>
</beans>
web.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<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">
<!--使用ContextLoaderListener加载spring-persist.xml-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:application*.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 配置过滤器解决 POST 请求的字符乱码问题 -->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<!-- encoding参数指定要使用的字符集名称 -->
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<!-- 请求强制编码 -->
<init-param>
<param-name>forceRequestEncoding</param-name>
<param-value>true</param-value>
</init-param>
<!-- 响应强制编码 -->
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--
在服务器启动的时候加载配置文件,创建ioc容器有两种方式:
1. DispatcherServlet
2. ContextLoaderListener
-->
<!--1. 使用DispatcherServlet加载spring-mvc.xml-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
分页
浸入式,即插即用,热拔式
分页执行流程
- 查询总记录数(用count()函数)
- 查询当前页数据(使用limit查询)
- 根据总记录数和每页条数计算总页数
- 在1~总页数之间修正页码
- 用户输入的页码 < 1:将页码设定为第一页
- 用户输入的页码 > 总页数:将页码设定为最后一页
- 封装上述所有数据,发送到页面显示
Mybatis的分页插件
依赖
<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
配置
持久层的配置
application-mapper.xml
<!-- 配置 SqlSessionFactoryBean -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
……
<!-- 在 plugins 属性中配置 Mybatis 插件 -->
<property name="plugins">
<array>
<bean class="com.github.pagehelper.PageInterceptor">
<property name="properties">
<props>
<!-- 设置 reasonable 为 true 表示将页码进行合理化修正。页码的有效范围:1~总页数 -->
<prop key="reasonable">true</prop>
<!-- 数据库方言:同样都是 SQL 语句,拿到不同数据库中,在语法上会有差异 -->
<!-- 默认情况下,按照 MySQL 作为数据库方言来运行 -->
<prop key="helperDialect">mysql</prop>
</props>
</property>
</bean>
</array>
</property>
</bean>
- PageHelper类:开启分页功能
- 静态方法:startPage(int 当前页数,int 每页显示条数)
开启分页功能,就在 SQL 语句后面附加 LIMIT 子句并查询总记录数;不开启就还是按照原样查询。分页功能对原有的 Mapper 接口、SQL 语句没有任何影响。这个效果可以称之为是非侵入式。
- PageInfo类:封装分页结果集数据
- 方法:long getTotal() 查询到的总记录数
- 方法:int getPages()//查询到的总页数
- 方法:List getList() 查询到的数据表的数据
handler层
//查询分页
@RequestMapping("queryByPage")
public PageInfo<Movie> queryByPage(Integer currentPage){
//每页查询数量
Integer pageSize = 4;
PageInfo<Movie> moviePageInfo = movieService.queryByPage(currentPage, pageSize);
return moviePageInfo;
}
service层
//查询分页
@Override
public PageInfo<Movie> queryByPage(Integer currentPage, Integer pageSize) {
//1 开启分页,传递当前的页数,每页显示的条数
//返回分页对象
PageHelper.startPage(currentPage,pageSize);
List<Movie> movies = movieMapper.movieList();
return new PageInfo<>(movies);
}
持久层
MovieMapper.xml
<select id="movieList" resultType="movie">
select movie_id,movie_name,movie_price from movie
</select>
/*,/,和*.do的区别
SpringMVC访问静态资源:/*,/,和*.do的区别
第一种配置:.do,还可以写.action等等,表示以.do结尾的或者以.action结尾的URL都由前端控制器DispatcherServlet来解析
第二种配置:/,所有访问的 URL 都由DispatcherServlet来解析,但是这里最好配置静态文件不由DispatcherServlet来解析,搭配下面使用
<mvc:annotation-driven/>
<mvc:default-servlet-handler/>
错误配置:/*,注意这里是不能这样配置的,应为如果这样写,最后转发到 jsp 页面的时候,仍然会由DispatcherServlet进行解析,
而这时候会找不到对应的Handler,从而报错!!!
"/*"表示匹配所有请求(包含所有后缀)
/*的配置不适用于DispatcherServlet,一般用于过滤器来拦截所有资源