本文最后更新于:3 个月前
springmvc-day04 第一章 SpringMVC运行原理 第一节 启动过程 1. Servlet 生命周期回顾
生命周期环节
调用的方法
时机
次数
创建对象
无参构造器
默认:第一次请求 修改:Web应用启动时
一次
初始化
init(ServletConfig servletConfig)
创建对象后
一次
处理请求
service(ServletRequest servletRequest, ServletResponse servletResponse)
接收到请求后
多次
清理操作
destroy()
Web应用卸载之前
一次
2. 初始化操作调用路线图
3. IOC容器创建 所在类:org.springframework.web.servlet.FrameworkServlet
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 protected WebApplicationContext createWebApplicationContext (@Nullable ApplicationContext parent) { Class<?> contextClass = getContextClass(); if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException( "Fatal initialization error in servlet with name '" + getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext" ); } ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); wac.setEnvironment(getEnvironment()); wac.setParent(parent); String configLocation = getContextConfigLocation(); if (configLocation != null ) { wac.setConfigLocation(configLocation); } configureAndRefreshWebApplicationContext(wac); return wac; }
4. 将 IOC 容器对象存入应用域 所在类:org.springframework.web.servlet.FrameworkServlet
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 protected WebApplicationContext initWebApplicationContext () { WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); WebApplicationContext wac = null ; if (this .webApplicationContext != null ) { wac = this .webApplicationContext; if (wac instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac; if (!cwac.isActive()) { if (cwac.getParent() == null ) { cwac.setParent(rootContext); } configureAndRefreshWebApplicationContext(cwac); } } } if (wac == null ) { wac = findWebApplicationContext(); } if (wac == null ) { wac = createWebApplicationContext(rootContext); } if (!this .refreshEventReceived) { synchronized (this .onRefreshMonitor) { onRefresh(wac); } } if (this .publishContext) { String attrName = getServletContextAttributeName(); getServletContext().setAttribute(attrName, wac); } return wac; }
看到这一点的意义:SpringMVC 有一个工具方法,可以从应用域获取 IOC 容器对象的引用。
工具类:org.springframework.web.context.support.WebApplicationContextUtils
工具方法:getWebApplicationContext()
1 2 3 4 @Nullable public static WebApplicationContext getWebApplicationContext (ServletContext sc) { return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); }
5. 请求映射初始化 FrameworkServlet.createWebApplicationContext()→configureAndRefreshWebApplicationContext()→wac.refresh()→触发刷新事件→org.springframework.web.servlet.DispatcherServlet.initStrategies()→org.springframework.web.servlet.DispatcherServlet.initHandlerMappings()
6. 小结 整个启动过程我们关心如下要点:
DispatcherServlet 本质上是一个 Servlet,所以天然的遵循 Servlet 的生命周期。所以宏观上是 Servlet 生命周期来进行调度。
DispatcherServlet 的父类是 FrameworkServlet。
FrameworkServlet 负责框架本身相关的创建和初始化。
DispatcherServlet 负责请求处理相关的初始化。
FrameworkServlet 创建 IOC 容器对象之后会存入应用域。
FrameworkServlet 完成初始化会调用 IOC 容器的刷新方法。
刷新方法完成触发刷新事件,在刷新事件的响应函数中,调用 DispatcherServlet 的初始化方法。
在 DispatcherServlet 的初始化方法中初始化了请求映射等。
第二章 请求处理过程 第一节 总体阶段 1. 流程描述
目标 handler 方法执行前
建立调用链,确定整个执行流程
拦截器的 preHandle() 方法
注入请求参数
准备目标 handler 方法所需所有参数
调用 目标 handler 方法
目标 handler 方法执行后
拦截器的 postHandle() 方法
渲染视图
拦截器的 afterCompletion() 方法
2. 核心代码 整个请求处理过程都是 doDispatch() 方法在宏观上协调和调度,把握了这个方法就理解了 SpringMVC 总体上是如何处理请求的。
所在类:org.springframework.web.servlet.DispatcherServlet
所在方法:doDispatch()
核心方法中的核心代码:
1 2 mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
第二节 调用前阶段 1. 建立调用链 1.1 相关组件 全类名:org.springframework.web.servlet.HandlerExecutionChain
拦截器索引默认是 -1,说明开始的时候,它指向第一个拦截器前面的位置。每执行一个拦截器,就把索引向前移动一个位置。所以这个索引每次都是指向当前拦截器。所以它相当于拦截器的指针 。
1.2 对应操作 所在类:org.springframework.web.servlet.handler.AbstractHandlerMapping
结论:调用链是由拦截器和目标 handler 对象组成的。
2. 调用拦截器 preHandle() 所在类:org.springframework.web.servlet.DispatcherServlet
所在方法:doDispatch()
具体调用细节:正序调用
所在类:org.springframework.web.servlet.HandlerExecutionChain
所在方法:applyPreHandle
从这部分代码我们也能看到,为什么拦截器中的 preHandle() 方法通过返回布尔值能够控制是否放行。
每一个拦截器的 preHandle() 方法都返回 true:applyPreHandle() 方法返回 true,被取反就不执行 if 分支,继续执行后续操作,这就是放行。
任何一个拦截器的 preHandle() 方法返回 false:applyPreHandle() 方法返回 false,被取反执行 if 分支,return,导致 doDispatch() 方法结束,不执行后续操作,就是不放行。
3. 调用handler方法 3.1 相关组件 接口:org.springframework.web.servlet.HandlerAdapter
作用:字面含义是适配器的意思,具体功能有三个
将请求参数绑定到实体类对象中
给目标 handler 方法准备所需的其他参数,例如:
Model、ModelMap、Map……
原生 Servlet API:request、response、session……
BindingResult
@RequestParam 注解标记的零散请求参数
@PathVariable 注解标记的路径变量
调用目标 handler 方法
3.2 创建并获取这个组件 所在类:org.springframework.web.servlet.DispatcherServlet
所在方法:doDispatch()
3.3 具体操作:调用目标 handler 方法 所在类:org.springframework.web.servlet.DispatcherServlet
所在方法:doDispatch()
3.4 具体操作:注入请求参数
通过反射给对应属性注入请求参数应该是下面的过程:
获取请求参数名称
将请求参数名称首字母设定为大写
在首字母大写后的名称前附加 set,得到目标方法名
通过反射调用 setXxx() 方法
4. 准备其他参数 以 Model 为例来进行说明。
4.1 背景 在 handler 方法中,如果需要 Model、ModelMap、Map 等对象用来存放模型数据,那么直接在 handler 方法中声明这些类型的形参即可。
而不管我们声明 Model、ModelMap、Map 三者中的任何一个,其实实际传入的对象都是 BindingAwareModelMap 类型的。
4.2 相关组件 组件类:org.springframework.web.method.support.ModelAndViewContainer
相关属性:defaultModel
1 private final ModelMap defaultModel = new BindingAwareModelMap();
从这个属性的声明能够看出:defaultModel 直接就是用 BindingAwareModelMap 对象来初始化的。
4.3 相关操作 相关接口:org.springframework.web.servlet.HandlerAdapter
所在类:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
所在方法:invokeHandlerMethod()
操作1:创建 ModelAndViewContainer 对象
操作2:把 ModelAndViewContainer 对象传给 invokeAndHandle() 方法
第三节 调用后阶段 1. 调用拦截器的 postHandle() 方法 所在类:org.springframework.web.servlet.DispatcherServlet
所在方法:doDispatch()
调用细节:从拦截器集合长度 - 1 开始循环,循环到 0 为止。所以是倒序 执行
2. 渲染视图 2.1 所有后续操作的入口 所在类:org.springframework.web.servlet.DispatcherServlet
所在方法:doDispatch()
2.2 后续细节1:处理异常 所在类:org.springframework.web.servlet.DispatcherServlet
所在方法:processDispatchResult()
2.3 后续细节2:渲染视图 所在类:org.springframework.web.servlet.DispatcherServlet
所在方法:processDispatchResult()
补充细节:模型数据存入请求域的具体位置
所在类:org.thymeleaf.context.WebEngineContext.RequestAttributesVariablesMap
所在方法:setVariable()
3. 调用拦截器的 afterCompletion() 方法 所在类:org.springframework.web.servlet.DispatcherServlet
所在方法:processDispatchResult()
调用细节:从拦截器索引开始循环,直到循环变量 i 被减到 0 为止。这样的效果是前面执行拦截器到哪里,就从哪里倒回去执行;前面没有执行的拦截器,现在也不执行。
第三章 ContextLoaderListener 第一节 概述 目前情况:DispatcherServlet 加载 spring-mvc.xml,此时整个 Web 应用中只创建一个 IOC 容器。将来整合Mybatis、配置声明式事务,全部在 spring-mvc.xml 配置文件中配置也是可以的。可是这样会导致配置文件太长,不容易维护。
所以想到把配置文件分开:
处理浏览器请求相关:spring-mvc.xml 配置文件
声明式事务和整合Mybatis相关:spring-persist.xml 配置文件
配置文件分开之后,可以让 DispatcherServlet 加载多个配置文件。例如:
1 2 3 4 5 6 7 8 9 <servlet > <servlet-name > dispatcherServlet</servlet-name > <servlet-class > org.springframework.web.servlet.DispatcherServlet</servlet-class > <init-param > <param-name > contextConfigLocation</param-name > <param-value > classpath:spring-*.xml</param-value > </init-param > <load-on-startup > 1</load-on-startup > </servlet >
如果希望这两个配置文件使用不同的机制来加载:
DispatcherServlet 加载 spring-mvc.xml 配置文件:它们和处理浏览器请求相关
ContextLoaderListener 加载 spring-persist.xml 配置文件:不需要处理浏览器请求,需要配置持久化层相关功能
此时会带来一个新的问题:在 Web 一个应用中就会出现两个 IOC 容器
DispatcherServlet 创建一个 IOC 容器
ContextLoaderListener 创建一个 IOC 容器
注意:本节我们探讨的这个技术方案并不是『必须』 这样做,而仅仅是『可以』 这样做。
第二节 配置 ContextLoaderListener 1. 创建 spring-persist.xml
2. 配置 ContextLoaderListener 1 2 3 4 5 6 7 8 9 10 <context-param > <param-name > contextConfigLocation</param-name > <param-value > classpath:spring-persist.xml</param-value > </context-param > <listener > <listener-class > org.springframework.web.context.ContextLoaderListener</listener-class > </listener >
方法名
执行时机
作用
contextInitialized()
Web 应用启动时执行
创建并初始化 IOC 容器
contextDestroyed()
Web 应用卸载时执行
关闭 IOC 容器
3. ContextLoader 3.1 指定配置文件位置的参数名 1 2 3 4 5 6 7 public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation" ;
3.2 初始化 IOC 容器 方法名:initWebApplicationContext()
3.3 创建 IOC 容器 方法名:createWebApplicationContext()
第三节 探讨两个IOC容器之间的关系 打印两个 IOC 容器对象的 toString() 方法:
1 2 3 4 Object springIOC = servletContext.getAttribute("org.springframework.web.context.WebApplicationContext.ROOT" logger.debug(springIOC.toString()); Object springMVCIOC = servletContext.getAttribute("org.springframework.web.servlet.FrameworkServlet.CONTEXT.dispatcherServlet" ); logger.debug(springMVCIOC.toString());
打印效果是:
Root WebApplicationContext, started on Thu Jun 17 14:49:17 CST 2021
WebApplicationContext for namespace ‘dispatcherServlet-servlet’, started on Thu Jun 17 14:49:18 CST 2021, parent : Root WebApplicationContext
结论:两个组件分别创建的 IOC 容器是父子 关系。
父容器:ContextLoaderListener 创建的 IOC 容器
子容器:DispatcherServlet 创建的 IOC 容器
父子关系是如何决定的?
ContextLoaderListener 初始化时如果检查到有已经存在的根级别 IOC 容器,那么会抛出异常。
DispatcherServlet 创建的 IOC 容器会在初始化时先检查当前环境下是否存在已经创建好的 IOC 容器。
如果有:则将已存在的这个 IOC 容器设置为自己的父容器
如果没有:则将自己设置为 root 级别的 IOC 容器
同时 Tomcat 在读取 web.xml 之后,加载组件的顺序就是监听器、过滤器、Servlet。
DispatcherServlet 创建的 IOC 容器设置父容器的源码截图:
所在类:org.springframework.web.servlet.FrameworkServlet
所在方法:createWebApplicationContext()
第四节 探讨两个 IOC 容器之间 bean 的互相访问
spring-mvc.xml配置方式:
1 <context:component-scan base-package ="com.atguigu.spring.component.controller" />
spring-persist.xml配置方式:
1 <context:component-scan base-package ="com.atguigu.spring.component.service,com.atguigu.spring.component.dao" />
所以bean所属IOC容器的关系:
结论:子容器中的 EmpController 装配父容器中的 EmpService 能够正常工作。说明子容器可以访问父容器中的bean。
分析:“子可用父,父不能用子”的根本原因是子容器中有一个属性 getParent() 可以获取到父容器这个对象的引用。
源码依据:
在 AbstractApplicationContext 类中,有 parent 属性
在 AbstractApplicationContext 类中,有获取 parent 属性的 getParent() 方法
子容器可以通过 getParent() 方法获取到父容器对象的引用
进而调用父容器中类似 “getBean()” 这样的方法获取到需要的 bean 完成装配
而父容器中并没有类似 “getChildren()“ 这样的方法,所以没法拿到子容器对象的引用
第五节 有可能重复创建对象
1. 查看日志确认是否重复创建了对象
Root WebApplicationContext: initialization started
……
Creating shared instance of singleton bean ‘helloDao’ Creating shared instance of singleton bean ‘helloHandler’ Creating shared instance of singleton bean ‘helloService’
……
Root WebApplicationContext initialized in 1150 ms
……
Refreshing WebApplicationContext for namespace ‘dispatcherServlet-servlet’
……
Creating shared instance of singleton bean ‘helloDao’
Creating shared instance of singleton bean ‘helloHandler’
Creating shared instance of singleton bean ‘helloService’
……
2. 重复创建对象的问题
浪费内存空间
两个 IOC 容器能力是不同的
spring-mvc.xml:仅配置和处理请求相关的功能。所以不能给 service 类附加声明式事务功能。
结论:基于 spring-mvc.xml 配置文件创建的 EmpService 的 bean 不带有声明式事务的功能
影响:DispatcherServlet 处理浏览器请求时会调用自己创建的 EmpController,然后再调用自己创建的EmpService,而这个 EmpService 是没有事务的,所以处理请求时没有事务功能的支持 。
spring-persist.xml:配置声明式事务。所以可以给 service 类附加声明式事务功能。
结论:基于 spring-persist.xml 配置文件创建的 EmpService 有声明式事务的功能
影响:由于 DispatcherServlet 的 IOC 容器会优先使用自己创建的 EmpController,进而装配自己创建的EmpService,所以基于 spring-persist.xml 配置文件创建的有声明式事务的 EmpService 用不上。
3. 解决重复创建对象的问题 3.1 解决方案一 让两个配置文件配置自动扫描的包时,各自扫描各自的组件。
SpringMVC 就扫描 XxxHandler
Spring 扫描 XxxService 和 XxxDao
3.2 解决方案二 如果由于某种原因,必须扫描同一个包,确实存在重复创建对象的问题,可以采取下面的办法处理。
spring-mvc.xml 配置文件在整体扫描的基础上进一步配置:仅包含被 @Controller 注解标记的类。
spring-persist.xml 配置在整体扫描的基础上进一步配置:排除被 @Controller 注解标记的类。
具体spring-mvc.xml配置文件中的配置方式如下:
1 2 3 4 5 6 7 8 9 10 11 <context:component-scan base-package ="com.atguigu.spring.component" use-default-filters ="false" > <context:include-filter type ="annotation" expression ="org.springframework.stereotype.Controller" /> </context:component-scan >
具体spring-persist.xml配置文件中的配置方式如下:
1 2 3 4 5 6 <context:component-scan base-package ="com.atguigu.spring.component" > <context:exclude-filter type ="annotation" expression ="org.springframework.stereotype.Controller" /> </context:component-scan >
第六节 小结
DispatcherServlet 和 ContextLoaderListener 并存
DispatcherServlet 负责加载 SpringMVC 的配置文件,例如:spring-mvc.xml
ContextLoaderListener 负责加载 Spring 的配置文件,例如:spring-persist.xml
两个 IOC 容器的关系:
ContextLoaderListener 创建的容器是父容器
DispatcherServlet 创建的容器是子容器
bean 的装配
子容器可以访问父容器中的 bean
父容器不能访问子容器中的 bean
两个容器扫描同一个包会导致重复创建对象
解决办法一:各自扫描各自的包
解决办法二:
DispatcherServlet 创建的容器仅扫描 handler
ContextLoaderListener 创建的容器不扫描 handler
第四章 SSM整合(重点) 第一节 Spring 和 Mybatis 整合 1. 思路
2. Mybatis-Spring技术 官方介绍
相关技术之间版本匹配说明:
Mybatis-Spring 的依赖:
1 2 3 4 5 6 <dependency > <groupId > org.mybatis</groupId > <artifactId > mybatis-spring</artifactId > <version > 2.0.6</version > </dependency >
3. 总体 SSM 整合所需依赖 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 <dependency > <groupId > org.springframework</groupId > <artifactId > spring-webmvc</artifactId > <version > 5.3.1</version > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-orm</artifactId > <version > 5.3.1</version > </dependency > <dependency > <groupId > ch.qos.logback</groupId > <artifactId > logback-classic</artifactId > <version > 1.2.3</version > </dependency > <dependency > <groupId > javax.servlet</groupId > <artifactId > javax.servlet-api</artifactId > <version > 3.1.0</version > <scope > provided</scope > </dependency > <dependency > <groupId > org.thymeleaf</groupId > <artifactId > thymeleaf-spring5</artifactId > <version > 3.0.12.RELEASE</version > </dependency > <dependency > <groupId > org.mybatis</groupId > <artifactId > mybatis</artifactId > <version > 3.5.7</version > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 5.1.3</version > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > druid</artifactId > <version > 1.0.31</version > </dependency > <dependency > <groupId > org.junit.jupiter</groupId > <artifactId > junit-jupiter-api</artifactId > <version > 5.7.0</version > <scope > test</scope > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-test</artifactId > <version > 5.3.1</version > </dependency > <dependency > <groupId > org.mybatis</groupId > <artifactId > mybatis-spring</artifactId > <version > 2.0.6</version > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <version > 1.18.8</version > <scope > provided</scope > </dependency >
4. 配置数据源 4.1 创建 jdbc.properties 1 2 3 4 jdbc.user =root jdbc.password =123456 jdbc.url =jdbc:mysql://localhost:3306/mybatis-example jdbc.driver =com.mysql.jdbc.Driver
4.2 加入日志配置文件
4.3 创建spring配置文件
1 2 3 4 5 6 7 8 9 10 <context:property-placeholder location ="classpath:jdbc.properties" /> <bean id ="druidDataSource" class ="com.alibaba.druid.pool.DruidDataSource" > <property name ="username" value ="${jdbc.user}" /> <property name ="password" value ="${jdbc.password}" /> <property name ="driverClassName" value ="${jdbc.driver}" /> <property name ="url" value ="${jdbc.url}" /> </bean >
4.4 创建 junit 测试类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @SpringJUnitConfig(locations = {"classpath:spring-persist.xml"}) public class SSMTest { @Autowired private DataSource dataSource; Logger logger = LoggerFactory.getLogger(getClass()); @Test public void testConn () throws SQLException { Connection connection = dataSource.getConnection(); logger.debug(connection.toString()); } }
5. 配置 SqlSessionFactoryBean 5.1 创建 Mybatis 全局配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?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" /> </settings > </configuration >
5.2 创建模型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.atguigu.pojo;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;@Data @AllArgsConstructor @NoArgsConstructor public class Employee { private Integer empId; private String empName; private Double empSalary; }
5.3 创建Mapper接口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package com.atguigu.mapper;import com.atguigu.pojo.Employee;import java.util.List;public interface EmployeeMapper { List<Employee> findAll () ; }
5.4 创建Mapper配置文件(存放路径在resources中要与对应的接口的路径一致)
1 2 3 4 5 6 7 8 9 <?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.atguigu.mapper.EmployeeMapper" > <select id ="findAll" resultType ="Employee" > select * from t_emp </select > </mapper >
5.5 配置 SqlSessionFactoryBean 5.5.1 风格一:保留 Mybatis 全局配置文件 1 2 3 4 5 6 <bean id ="sqlSessionFactoryBean" class ="org.mybatis.spring.SqlSessionFactoryBean" > <property name ="dataSource" ref ="druidDataSource" /> <property name ="configLocation" value ="classpath:mybatis-config.xml" /> </bean >
5.5.2 风格二:彻底舍弃 Mybatis 全局配置文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <bean id ="sqlSessionFactoryBean" class ="org.mybatis.spring.SqlSessionFactoryBean" > <property name ="dataSource" ref ="druidDataSource" /> <property name ="configuration" > <bean class ="org.apache.ibatis.session.Configuration" > <property name ="mapUnderscoreToCamelCase" value ="true" /> <property name ="lazyLoadingEnabled" value ="true" /> </bean > </property > <property name ="typeAliasesPackage" value ="com.atguigu.pojo" /> <property name ="mapperLocations" value ="classpath:mappers/*Mapper.xml" /> </bean >
注意:上面两种方式如果并存,会抛出异常:
java.lang.IllegalStateException: Property ‘configuration’ and ‘configLocation’ can not specified with together
6. 配置 Mapper 接口扫描器 6.1 方式一:使用扫描器 1 2 3 4 <bean id ="mapperScannerConfigurer" class ="org.mybatis.spring.mapper.MapperScannerConfigurer" > <property name ="basePackage" value ="com.atguigu.mapper" /> </bean >
6.2 方式二:使用 mybatis-spring 名称空间 1 2 3 4 5 <mybatis-spring:scan base-package ="com.atguigu.mapper" />
7. 测试 1 2 3 4 5 6 7 8 9 10 11 @Autowired private EmpMapper empMapper; @Test public void testMybatis () { List<Emp> empList = empMapper.selectAll(); for (Emp emp : empList) { logger.debug(emp.toString()); } }
第二节 加入声明式事务 1. 配置事务管理器 1 2 3 4 5 6 7 8 <bean id ="transactionManager" class ="org.springframework.jdbc.datasource.DataSourceTransactionManager" > <property name ="dataSource" ref ="druidDataSource" /> </bean > <tx:annotation-driven transaction-manager ="transactionManager" />
2. 测试 2.1 创建 Service 组件
1 2 3 4 5 6 7 8 9 10 11 12 @Service public class EmpServiceImpl implements EmpService { @Autowired private EmpMapper empMapper; @Override @Transactional(readOnly = true) public List<Emp> getAll () { return empMapper.selectAll(); } }
2.2 配置自动扫描的包
1 2 <context:component-scan base-package ="com.atguigu.service" />
2.3 junit 1 2 3 4 5 6 7 8 9 10 @Autowired private EmpService empService; @Test public void testTx () { List<Emp> empList = empService.getAll(); for (Emp emp : empList) { System.out.println("emp = " + emp); } }
第三节 Spring 和 SpringMVC 整合 1. 整合的本质
ContextLoaderListener:读取 spring-persist.xml
DispatcherServlet:读取 spring-mvc.xml
2. web.xml配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xmlns ="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation ="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version ="2.5" > <servlet > <servlet-name > dispatcherServlet</servlet-name > <servlet-class > org.springframework.web.servlet.DispatcherServlet</servlet-class > <init-param > <param-name > contextConfigLocation</param-name > <param-value > classpath:spring-mvc.xml</param-value > </init-param > <load-on-startup > 1</load-on-startup > </servlet > <servlet-mapping > <servlet-name > dispatcherServlet</servlet-name > <url-pattern > /</url-pattern > </servlet-mapping > <context-param > <param-name > contextConfigLocation</param-name > <param-value > classpath:spring-persist.xml</param-value > </context-param > <listener > <listener-class > org.springframework.web.context.ContextLoaderListener</listener-class > </listener > <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 > <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 > </web-app >
3. SpringMVC 配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 <?xml version="1.0" encoding="UTF-8"?> <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/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd" > <context:component-scan base-package ="com.atguigu.handler" /> <mvc:annotation-driven /> <mvc:default-servlet-handler /> <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 > <mvc:view-controller path ="/" view-name ="portal" /> <mvc:view-controller path ="/index.html" view-name ="portal" /> </beans >
4. 创建组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package com.atguigu.handler;import com.atguigu.pojo.Employee;import com.atguigu.service.EmployeeService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.ui.Model;import org.springframework.web.bind.annotation.RequestMapping;import java.util.List;@Controller @RequestMapping("/employee") public class EmployeeHandler { @Autowired private EmployeeService employeeService; @RequestMapping("/findAll") public String findAll (Model model) { List<Employee> employeeList = employeeService.findAll(); model.addAttribute("list" ,employeeList); return "emp-list" ; } }
5. 页面操作 5.1 首页超链接
1 <a th:href ="@{/employee/findAll}" > 显示员工列表</a >
5.2 显示数据的页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 <!DOCTYPE html > <html lang ="en" xmlns:th ="http://www.thymeleaf.org" > <head > <meta charset ="UTF-8" > <title > 员工信息展示页面</title > <style type ="text/css" > table { border-collapse : collapse; margin : 0px auto 0px auto; } table th , td { border : 1px solid black; text-align : center; } </style > </head > <body > <table > <tr > <th > ID</th > <th > NAME</th > <th > SALARY</th > </tr > <tbody th:if ="${#lists.isEmpty(list)}" > <tr > <td colspan ="3" > 抱歉!没有查询到数据!</td > </tr > </tbody > <tbody th:if ="${not #lists.isEmpty(list)}" > <tr th:each ="emp : ${list}" > <td th:text ="${emp.empId}" > 这里显示员工ID</td > <td th:text ="${emp.empName}" > 这里显示员工NAME</td > <td th:text ="${emp.empSalary}" > 这里显示员工SALARY</td > </tr > </tbody > </table > </body > </html >
第五章 分页 第一节 分页的概述 1. 为什么要分页 如果应用程序显示数据不分页,会有三个问题:
用户查看数据非常不方便。
所有数据不分冷热全部显示出来,冷数据白白占用存储空间,浪费内存。
在服务器端查询全部数据占用内存很大,给整个系统增加了很大压力。
2. 分页本身的概念 把系统中要显示的数据分成较小的单元,每个单元作为『一页』显示给用户。每次访问服务器只查询一页数据。
分页的好处:
用户体验较好。
服务器端每次只查询一部分数据,内存压力减小。
对冷数据减少查询的次数,据此对系统性能进行优化。
3. 分页的细节
4. 实现分页的基本逻辑 4.1 物理分页 具体数据库不同,分页语法有区别。下面我们以 MySQL 为例来说明。MySQL 的分页需要借助 LIMIT 子句来完成。
1 2 3 select emp_id,emp_name,emp_salary from t_emp limit 0,5; # 查询第一页数据 select emp_id,emp_name,emp_salary from t_emp limit 5,5; # 查询第二页数据 select emp_id,emp_name,emp_salary from t_emp limit 10,5;# 查询第三页数据
LIMIT 子句的公式:
limit (pageNo-1)*pageSize,pageSize
注意:在 SQL 的语法中,LIMIT 子句必须出现在 SQL 语句最后。
4.2 逻辑分页 4.2.1 需求 为了能够在页面上全面显示分页相关的细节数据,总页数需要计算得到。
4.2.2 总页数计算方式
4.2.3 页码的合理化 页码的有效范围:1~总页数。修正方式:
用户输入的页码 < 1:将页码设定为第一页
用户输入的页码 > 总页数:将页码设定为最后一页
4.2.4 分页执行流程
查询总记录数(用count()函数)
查询当前页数据(使用limit查询)
根据总记录数和每页条数计算总页数
在1~总页数之间修正页码
封装上述所有数据,发送到页面显示
第二节 实现分页 1. Mybatis的分页插件 具体使用细节可以参考:官方文档
1.1 引入依赖 1 2 3 4 5 6 <dependency > <groupId > com.github.pagehelper</groupId > <artifactId > pagehelper</artifactId > <version > 5.2.0</version > </dependency >
1.2 配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <bean id ="sqlSessionFactory" class ="org.mybatis.spring.SqlSessionFactoryBean" > …… <property name ="plugins" > <array > <bean class ="com.github.pagehelper.PageInterceptor" > <property name ="properties" > <props > <prop key ="reasonable" > true</prop > <prop key ="helperDialect" > mysql</prop > </props > </property > </bean > </array > </property > </bean >
2. 具体代码 2.1 首页超链接 1 <a th:href ="@{/get/page/1}" > 显示分页数据</a >
2.2 handler 方法 1 2 3 4 5 6 7 8 9 10 11 12 13 @RequestMapping("/get/page/{pageNo}") public String getPage ( @PathVariable("pageNo") Integer pageNo, Model model) { PageInfo<Emp> pageInfo = empService.getPageInfo(pageNo); model.addAttribute("pageInfo" , pageInfo); return "emp-page" ; }
2.3 service 方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Override public PageInfo<Emp> getPageInfo (Integer pageNo) { int pageSize = 5 ; PageHelper.startPage(pageNo, pageSize); List<Emp> empList = empMapper.selectAll(); return new PageInfo<>(empList); }
2.4 页面展示分页数据 2.4.1 显示列表 1 2 3 …… <tr th:each ="emp : ${pageInfo.list}" > ……
2.4.2 显示翻页导航栏 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <span th:if ="${pageInfo.hasPreviousPage}" > <a th:href ="@{/get/page/1}" > 首页</a > <a th:href ="@{/get/page/}+${pageInfo.prePage}" > 上一页</a > </span > <span th:each ="navigator : ${pageInfo.navigatepageNums}" > <a th:if ="${navigator != pageInfo.pageNum}" th:href ="@{/get/page/}+${navigator}" th:text ="'['+${navigator}+']'" > </a > <span th:if ="${navigator == pageInfo.pageNum}" th:text ="'['+${navigator}+']'" > </span > </span > <span th:if ="${pageInfo.hasNextPage}" > <a th:href ="@{/get/page/}+${pageInfo.nextPage}" > 下一页</a > <a th:href ="@{/get/page/}+${pageInfo.pages}" > 最后一页</a > </span > <span th:text ="${pageInfo.pageNum}+'/'+${pageInfo.pages}" > </span >
2.5 打印的 SQL 语句
第三节 为什么是 PageInfo 而不是 Page 1. List接口的具体实现 当我们开启了分页功能后,查询一个 List 集合,实际返回的是:com.github.pagehelper.Page 类型。这个 Page 类继承了 ArrayList,所以也兼容 List 接口类型。
2. 提出问题 如果我们将 Page 类型的对象存入模型,转发到视图模板上显示数据,会存在一个问题:视图模板技术只承认这个对象是一个 List 集合,不识别 List 集合之外的其它属性。
这一点在其他场合也需要注意:我们开发时尽量不要继承 ArrayList、HashMap 等类似的集合实现类。如果继承了,那么页面视图模板技术或其他表达式往往只能识别我们的对象是一个集合,而无法访问额外封装的其他属性。
所以 Page 对象需要封装为 PageInfo,让 list、pageNum 等等数据作为 PageInfo 对象的属性;PageInfo 本身并不是一个 List 类型的集合。
3. PageHelper 非侵入式的体现 1 PageHelper . startPage(pageNo , pageSize ) ;
开启分页功能,就在 SQL 语句后面附加 LIMIT 子句并查询总记录数;不开启就还是按照原样查询。分页功能对原有的 Mapper 接口、SQL 语句没有任何影响。这个效果可以称之为是非侵入式,也可以说是可插拔的。
总结
ContextLoaderListener: 在服务器启动的时候加载配置文件创建IOC容器
ContextLoaderListener创建的IOC是DispatcherServlet 创建的IOC容器的父容器;
子容器中可以拿到父容器中的对象
在我们项目中DispatchServlet只负责表现层,只扫描Controller或者是RestController
ContextLoaderListener负责其它的
Spring与Mybatis整合
引入mybatis-spring的整合的依赖
mybatis的使用和以前一样,只是不用写全局配置文件,并且也不用写创建持久层代理对象的那一堆代码
整合的目的: 在Spring的IOC容器中持有持久层的代理对象
整合的步骤:
在spring的配置文件中配置SqlSessionFactoryBean
注入dataSource
别名包扫描
驼峰配置
懒加载等等配置
指定映射配置文件的路径
扫描持久层接口所在的包
PageHelper分页插件
目标: 以非侵入的方式在后端进行分页
使用步骤:
引入分页插件的依赖
在SqlSessionFactoryBean的配置中,配置分页插件
在业务层中:
调用PageHelper.startPage(pageNo,pageSize)开启分页
调用查询所有的持久层方法
使用PageInfo封装分页数据