第六章 SSM 整合(SpringMVC)
第一节 Spring 和 Mybatis 整合
1、思路
2、Mybatis-Spring技术
官方介绍
相关技术之间版本匹配说明:
Mybatis-Spring 的依赖:
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
3、总体 SSM 整合所需依赖
<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>
4、配置数据源
①创建 jdbc.properties
jdbc.user=root
jdbc.password=atguigu
jdbc.url=jdbc:mysql://192.168.198.100:3306/mybatis-example
jdbc.driver=com.mysql.jdbc.Driver
②加入日志配置文件
③创建 Spring 配置文件
<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>
④创建 junit 测试类
@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
①创建 Mybatis 全局配置文件
<?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>
②创建模型
public class Emp {
private Integer empId;
private String empName;
private Double empSalary;
③创建Mapper接口
public interface EmpMapper {
List<Emp> selectAll();
}
④创建Mapper配置文件
<?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.ssm.mapper.EmpMapper">
<select id="selectAll" resultType="Emp">
select emp_id,emp_name,emp_salary from t_emp
</select>
</mapper>
⑤配置 SqlSessionFactoryBean
[1]风格一:保留 Mybatis 全局配置文件
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:mappers/*Mapper.xml"/>
<property name="dataSource" ref="druidDataSource"/>
</bean>
[2]风格二:彻底舍弃 Mybatis 全局配置文件
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configuration">
<bean class="org.apache.ibatis.session.Configuration">
<property name="mapUnderscoreToCamelCase" value="true"/>
</bean>
</property>
<property name="typeAliasesPackage" value="com.atguigu.ssm.entity"/>
<property name="mapperLocations" value="classpath:mappers/*Mapper.xml"/>
<property name="dataSource" ref="druidDataSource"/>
</bean>
注意:上面两种方式如果并存,会抛出异常:
java.lang.IllegalStateException: Property ‘configuration’ and ‘configLocation’ can not specified with together
⑥配置 Mapper 接口扫描器
[1]方式一:使用扫描器
<bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.atguigu.ssm.mapper"/>
</bean>
[2]方式二:使用 mybatis-spring 名称空间
<mybatis-spring:scan base-package="com.atguigu.ssm.mapper"/>
⑦测试
@Autowired
private EmpMapper empMapper;
@Test
public void testMybatis() {
List<Emp> empList = empMapper.selectAll();
for (Emp emp : empList) {
logger.debug(emp.toString());
}
}
6、加入声明式事务
①配置事务管理器
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="druidDataSource"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
②测试
[1]创建 Service 组件
@Service
public class EmpServiceImpl implements EmpService {
@Autowired
private EmpMapper empMapper;
@Override
@Transactional(readOnly = true)
public List<Emp> getAll() {
return empMapper.selectAll();
}
}
[2]配置自动扫描的包
<context:component-scan base-package="com.atguigu.ssm.service"/>
[3]测试
@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配置
<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>
<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>
<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>
<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>
3、SpringMVC 配置
<context:component-scan base-package="com.atguigu.ssm.handler"/>
<bean id="thymeleafViewResolver" 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="characterEncoding" value="UTF-8"/>
<property name="templateMode" value="HTML5"/>
</bean>
</property>
</bean>
</property>
</bean>
<mvc:annotation-driven/>
<mvc:default-servlet-handler/>
<mvc:view-controller path="/" view-name="portal"/>
<mvc:view-controller path="/index.html" view-name="portal"/>
4、创建组件
@Controller
public class EmpHandler {
@Autowired
private EmpService empService;
@RequestMapping("/get/all")
public String getAll(Model model) {
List<Emp> empList = empService.getAll();
model.addAttribute("empList", empList);
return "emp-list";
}
}
5、页面操作
①首页超链接
<a th:href="@{/get/all}">显示全部数据</a>
②显示数据的页面
<table>
<tr>
<th>ID</th>
<th>NAME</th>
<th>SALARY</th>
</tr>
<tbody th:if="${#lists.isEmpty(empList)}">
<tr>
<td colspan="3">抱歉!没有查询到数据!</td>
</tr>
</tbody>
<tbody th:if="${not #lists.isEmpty(empList)}">
<tr th:each="emp : ${empList}">
<td th:text="${emp.empId}">这里显示员工ID</td>
<td th:text="${emp.empName}">这里显示员工NAME</td>
<td th:text="${emp.empSalary}">这里显示员工SALARY</td>
</tr>
</tbody>
</table>
<a th:href="@{/}">回首页</a>
第三节 分页
1、提出问题
如果应用程序显示数据不分页,会有三个问题:
- 用户查看数据非常不方便。
- 所有数据不分冷热全部显示出来,冷数据白白占用存储空间,浪费内存。
- 在服务器端查询全部数据占用内存很大,给整个系统增加了很大压力。
2、分页的概念
①分页本身的概念
把系统中要显示的数据分成较小的单元,每个单元作为『一页』显示给用户。每次访问服务器只查询一页数据。
分页的好处:
- 用户体验较好。
- 服务器端每次只查询一部分数据,内存压力减小。
- 对冷数据减少查询的次数,据此对系统性能进行优化。
②分页的细节
3、实现分页的基本逻辑
①物理分页
具体数据库不同,分页语法有区别。下面我们以 MySQL 为例来说明。MySQL 的分页需要借助 LIMIT 子句来完成。
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 语句最后。
②逻辑分页
[1]需求
为了能够在页面上全面显示分页相关的细节数据,总页数需要计算得到。
[2]总页数计算方式
[3]页码的合理化
页码的有效范围:1~总页数。修正方式:
- 用户输入的页码 < 1:将页码设定为第一页
- 用户输入的页码 > 总页数:将页码设定为最后一页
③分页执行流程
- 查询总记录数
- 查询当前页数据
- 根据总记录数和每页条数计算总页数
- 在1~总页数之间修正页码
- 封装上述所有数据,发送到页面显示
4、Mybatis 的分页插件
具体使用细节可以参考:官方文档
①依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
②配置
<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>
5、实现分页功能
①首页超链接
<a th:href="@{/get/page/1}">显示分页数据</a>
②handler 方法
@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";
}
③service 方法
@Override
public PageInfo<Emp> getPageInfo(Integer pageNo) {
int pageSize = 5;
PageHelper.startPage(pageNo, pageSize);
List<Emp> empList = empMapper.selectAll();
return new PageInfo<>(empList);
}
④页面展示
[1]显示数据
……
<tr th:each="emp : ${pageInfo.list}">
……
[2]显示翻页导航栏
<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>
⑤打印的 SQL 语句
6、为什么是 PageInfo 而不是 Page
①List接口的具体实现
当我们开启了分页功能后,查询一个 List 集合,实际返回的是:com.github.pagehelper.Page 类型。这个 Page 类继承了 ArrayList,所以也兼容 List 接口类型。
②提出问题
如果我们将 Page 类型的对象存入模型,转发到视图模板上显示数据,会存在一个问题:视图模板技术只承认这个对象是一个 List 集合,不识别 List 集合之外的其它属性。
这一点在其他场合也需要注意:我们开发时尽量不要继承 ArrayList、HashMap 等类似的集合实现类。如果继承了,那么页面视图模板技术或其他表达式往往只能识别我们的对象是一个集合,而无法访问额外封装的其他属性。
所以 Page 对象需要封装为 PageInfo,让 list、pageNum 等等数据作为 PageInfo 对象的属性;PageInfo 本身并不是一个 List 类型的集合。
③PageHelper 非侵入式的体现
PageHelper.startPage(pageNo, pageSize);
开启分页功能,就在 SQL 语句后面附加 LIMIT 子句并查询总记录数;不开启就还是按照原样查询。分页功能对原有的 Mapper 接口、SQL 语句没有任何影响。这个效果可以称之为是非侵入式,也可以说是可插拔的。
|