SSM整合(借鉴版)




SSM整合系列

前言
总体来说,就是两件事情,两个层次:

  • 顶层:开发功能
  • 底层:搭建环境
    • Spring和Mybatis整合
    • Spring和SpringMVC整合

文章目录


第一章、Spring 和 MyBatis整合

第一节 连接数据库

1、总体 SSM 整合所需依赖

<!-- SpringMVC -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.1</version>
</dependency>

<!-- Spring 持久化层所需依赖 -->
<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>

<!-- 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>

<!-- Mybatis核心 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>

<!-- MySQL驱动 -->
<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>

<!-- junit5 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.7.0</version>
<scope>test</scope>
</dependency>

<!-- Spring 的测试功能 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.1</version>
</dependency>

<!-- Mybatis 和 Spring 的整合包 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>

2、配置数据源(连接数据库)

①创建 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>

  • 注意在配置数据源的过程中,其中property标签中的name属性dirverClassName必须写成这样,写成driver或者其它会报错
    报错参考

④创建 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());
}

}

第二节 整合Mybatis

1、思路

①理解方式一:简单版
在这里插入图片描述


② 理解方式二:进阶版
在这里插入图片描述

本质:Spring接管一切,代码更加简洁。

  • Spring管理数据源,连接数据库
  • Spring管理SqlSessionFactory,开启SqlSession的细节被屏蔽了
  • Spring的IOC容器负责注入Mapper接口
  • Spring管理声明式事务

2、Mybatis-Spring技术

官方介绍

相关技术之间版本匹配说明:
在这里插入图片描述

Mybatis-Spring 的依赖:

<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.6</version>
</dependency>

3、配置 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>

<!-- Mybatis全局配置 -->
<settings>
<!-- 将数据库表字段映射到驼峰式命名的Java实体类属性中 -->
<!-- 数据库表字段格式:单词_单词 -->
<!-- Java实体类属性:首字母小写的驼峰式命名 -->
<!--将_自动映射为驼峰,emp_name: empName-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

<!--指定实体类别名-->
<typeAliases>
<package name="com.atguigu.ssm.entity"/>
</typeAliases>

</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属性是找到当前配置的依据 -->
<!-- 由于最理想的Mybatis使用方式是:通过Mapper接口调用接口方法,访问数据库 -->
<!-- 这样的理想方式要求:能够通过接口全类名找到Mapper配置 -->
<!-- 所以:我们就用Mapper接口的全类名来给namespace属性赋值 -->
<mapper namespace="com.atguigu.ssm.mapper.EmpMapper">

<!-- List<Emp> selectAll(); -->
<select id="selectAll" resultType="Emp">
select emp_id,emp_name,emp_salary from t_emp
</select>

</mapper>

⑤配置 SqlSessionFactoryBean

[1]风格一:保留 Mybatis 全局配置文件
<!-- 配置 SqlSessionFactoryBean -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">

<!-- 指定 Mybatis 全局配置文件位置 -->
<property name="configLocation" value="classpath:mybatis-config.xml"/>

<!-- 指定 Mapper 配置文件位置 -->
<property name="mapperLocations" value="classpath:mappers/*Mapper.xml"/>

<!-- 装配数据源 -->
<property name="dataSource" ref="druidDataSource"/>

</bean>

[2]风格二:彻底舍弃 Mybatis 全局配置文件
<!-- 配置 SqlSessionFactoryBean -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">

<!-- 舍弃 Mybatis 全局配置文件,使用 configuration 属性 -->
<property name="configuration">
<bean class="org.apache.ibatis.session.Configuration">
<property name="mapUnderscoreToCamelCase" value="true"/>
</bean>
</property>

<!-- 舍弃 Mybatis 全局配置文件,使用 typeAliasesPackage 属性配置实体类所在包 -->
<property name="typeAliasesPackage" value="com.atguigu.ssm.entity"/>

<!-- 指定 Mapper 配置文件位置 -->
<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]方式一:使用扫描器
<!-- 配置 Mapper 接口类型的bean的扫描器 -->
<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) { //使用 iter 快捷键可以快速生成foreach循环
logger.debug(emp.toString());
}
}

第三节、加入声明式事务

1、配置事务管理器

<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!-- 装配数据源 -->
    <property name="dataSource" ref="druidDataSource"/>
</bean>

<!-- 开启基于注解的声明式事务 -->
<tx:annotation-driven transaction-manager="transactionManager"/>


注意:引入tx名称空间时千万注意名称空间的值
在这里插入图片描述


2、创建 Service 组件

在这里插入图片描述

@Service
public class EmpServiceImpl implements EmpService {

@Autowired
private EmpMapper empMapper;

@Override
@Transactional(readOnly = true) //为当前业务层添加事务
public List<Emp> getAll() {
return empMapper.selectAll();
}
}

3、配置自动扫描的包

在这里插入图片描述

<context:component-scan base-package="com.atguigu.ssm.service"/>

4、 测试

@Autowired
private EmpService empService;

@Test
public void testTx() {
List<Emp> empList = empService.getAll();
for (Emp emp : empList) {
System.out.println("emp = " + emp); //使用 soutv 快捷键可以生成
}
}


在框架打印的日志中能够看到事务打开、提交、回滚等操作即可确认声明式事务已生效。


相关日志信息节选:
urceTransactionManager - Acquired Connection [com.mysql.jdbc.JDBC4Connection@488d1cd7] for JDBC transaction
urceUtils - Setting JDBC Connection [com.mysql.jdbc.JDBC4Connection@488d1cd7] read-only
urceTransactionManager - Switching JDBC Connection [com.mysql.jdbc.JDBC4Connection@488d1cd7] to manual commit
ing a new SqlSession
tering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@babafc2]
edTransaction - JDBC Connection [com.mysql.jdbc.JDBC4Connection@488d1cd7] will be managed by Spring
ctAll - = = > Preparing: select emp_id,emp_name,emp_salary from t_emp
ctAll - = = > Parameters:
ctAll - <== Total: 15
sing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@babafc2]
action synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@babafc2]
action synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@babafc2]
action synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@babafc2]
urceTransactionManager - Initiating transaction commit
urceTransactionManager - Committing JDBC transaction on Connection [com.mysql.jdbc.JDBC4Connection@488d1cd7]
urceUtils - Resetting read-only flag of JDBC Connection [com.mysql.jdbc.JDBC4Connection@488d1cd7]
urceTransactionManager - Releasing JDBC Connection [com.mysql.jdbc.JDBC4Connection@488d1cd7] after transaction

第四节 最终bean图

在这里插入图片描述

`

第二章、Spring 和 SpringMVC整合

前言

  • ContextLoaderListener:读取 spring-persist.xml
  • DispatcherServlet:读取 spring-mvc.xml

第一节、配置web.xml


classpath就是代表 /WEB-INF /classes/ 这个路径(所有src目录下面的java、xml、properties等文件编译后都会在此)


<!-- ContextLoaderListener -->
<!-- 通过 context-param 指定 Spring 框架的配置文件位置 -->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring-persist.xml</param-value>
</context-param>

<!-- 配置 ContextLoaderListener 监听器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!-- DispatcherServlet -->
<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 的顺序:字符集过滤器在前,转换请求方式过滤器在后 -->
<!-- CharacterEncodingFilter -->
<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>

<!-- HiddenHttpMethodFilter -->
<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>

第二节、SpringMVC 配置

<!-- SpringMVC 只扫描 handler 类所在的包 -->
<!-- Spring 和 SpringMVC 扫描各自负责的组件,扫描的范围没有重合的部分,直接避免了重复创建对象 -->
<context:component-scan base-package="com.atguigu.ssm.handler"/>

<!-- 配置 Thymeleaf 的视图解析器 -->
<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>

<!-- SpringMVC 注解驱动(标配) -->
<mvc:annotation-driven/>

<!-- 对于没有映射的请求直接转发放行,主要是静态资源 -->
<mvc:default-servlet-handler/>

<!-- 匹配请求路径直接前往视图,不经过 handler 方法 -->
<mvc:view-controller path="/" view-name="portal"/>
<mvc:view-controller path="/index.html" view-name="portal"/>

第三节 显示数据列表

1、显示首页

①声明view-controller

<!-- 匹配请求路径直接前往视图,不经过 handler 方法 -->
<mvc:view-controller path="/" view-name="portal"/>
<mvc:view-controller path="/index.html" view-name="portal"/>

②创建页面

<!DOCTYPE html>
<html lang="en" xml:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

</body>
</html>

在这里插入图片描述

③编写超链接

<a th:href="@{/get/all}">显示全部数据</a>

2、显示数据列表

在这里插入图片描述

①创建handler类

在这里插入图片描述

@Controller
public class EmpHandler {

@Autowired
private EmpService empService;

@RequestMapping("/get/all")
public String getAll(Model model) {

// 1、查询数据
List<Emp> empList = empService.getAll();

// 2.存入模型
model.addAttribute("empList", empList);

return "emp-list";
}

}

②在页面上显示数据列表

在这里插入图片描述

<table>
    <tr>
        <th>ID</th>
        <th>NAME</th>
        <th>SALARY</th>
    </tr>
    <tbody th:if="${#lists.isEmpty(empList)}"><!--#lists:thymeleaf内置对象-->
        <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>


第四节 最终bean图

在这里插入图片描述

第三章、 分页

第一节 分页的概念

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 子句,如果非要写 LIMIT 子句,那么 LIMIT 子句必须出现在 SQL 语句最后,什么 ORDER BY、GROUP BY、WHERE 等子句都必须在 LIMIT 子句前面。

②逻辑分页

[1]需求

为了能够在页面上全面显示分页相关的细节数据,总页数需要计算得到。

[2]总页数计算方式

在这里插入图片描述

[3]页码的合理化

合理化的英文表述是:reasonable。页码的有效范围:1~总页数。修正方式:

  • 用户输入的页码 < 1:将页码设定为第一页
  • 用户输入的页码 > 总页数:将页码设定为最后一页

③分页执行流程

  • 查询总记录数
  • 查询当前页数据
  • 根据总记录数和每页条数计算总页数
  • 在1~总页数之间修正页码
  • 封装上述所有数据,发送到页面显示

第二节、Mybatis 的分页插件

1、官方支持

具体使用细节可以参考:官方文档

在这里插入图片描述

2、操作步骤

①依赖

<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.2.0</version>
</dependency>

②配置

<!-- 配置 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 非侵入式的体现

PageHelper.startPage(pageNo, pageSize);

开启分页功能,就在 SQL 语句后面附加 LIMIT 子句并查询总记录数;不开启就还是按照原样查询。分页功能对原有的 Mapper 接口、SQL 语句没有任何影响。这个效果可以称之为是非侵入式,也可以说是可插拔的。

第三节 案例中的分页功能

1、首页超链接

<a th:href="@{/get/page/1}">显示分页数据</a>

2、handler 方法

@RequestMapping("/get/page/{pageNo}")
public String getPage(
        @PathVariable("pageNo") Integer pageNo, 
        Model model) {

// PageInfo 对象封装了和分页相关的所有信息
PageInfo<Emp> pageInfo = empService.getPageInfo(pageNo);

// 将 PageInfo 对象存入模型
model.addAttribute("pageInfo", pageInfo);

return "emp-page";
}

3、service 方法

@Override
public PageInfo<Emp> getPageInfo(Integer pageNo) {

// 1、确定每页显示数据的条数
int pageSize = 5;

// 2、设定分页数据:开启分页功能。开启后,后面执行的 SELECT 语句会自动被附加 LIMIT 子句,
// 而且会自动查询总记录数
PageHelper.startPage(pageNo, pageSize);

// 3、正常执行查询
List<Emp> empList = empMapper.selectAll();

// 4、封装为 PageInfo 对象返回
return new PageInfo<>(empList);
}

4、页面展示

[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}">

<!-- th:if 判断当前标签是否是当前页 -->
<!-- 如果不是当前页则显示超链接 -->
<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>

[3]打印的 SQL 语句

在这里插入图片描述

5、为什么是 PageInfo 而不是 Page

①List接口的具体实现

当我们开启了分页功能后,查询一个 List 集合,实际返回的是: com.github.pagehelper.Page 类型。这个 Page 类继承了 ArrayList,所以也兼容 List 接口类型。

②提出问题

如果我们将 Page 类型的对象存入模型,转发到视图模板上显示数据,会存在一个问题:视图模板技术只承认这个对象是一个 List 集合,不识别 List 集合之外的其它属性

这一点在其他场合也需要注意:我们开发时尽量不要继承 ArrayList、HashMap 等类似的集合实现类。如果继承了,那么页面视图模板技术或其他表达式往往只能识别我们的对象是一个集合,而无法访问额外封装的其他属性。

所以 Page 对象需要封装为 PageInfo,让 list、pageNum 等等数据作为 PageInfo 对象的属性;PageInfo 本身并不是一个 List 类型的集合。


第四节 增删改查

相关知识合集

-参考增删改查

总结

主要是参考
代码重工-SSM整合1.0

posted @ 2022-10-28 21:19  wylja  阅读(63)  评论(0)    收藏  举报