SSM【已完结】

发布时间 2023-12-11 21:31:17作者: 悲惨的一天

认识SSM整合

1.Spring来整合MyBatis和SpringMVC,并提供业务层事务管理

2.SpringMVC本身就是Spring的一部分,所以整合相对简单

3.整合MyBatis需要使用由MyBatis社区来提供的整合插件mybatis-spring,而不是由Spring来提供的整合插件,主要是因为MyBatis出现的比Spring晚

4.整合插件官网:http://mybatis.org/spring/zh/index.html

5.SSM整合的结果是:

1)MyBatis的使用更加简单

2)MyBatis中的Bean,比如SqlSessionFactory、Mapper接口的动态代理类都交给Spring的IoC容器管理

3)对业务层应用Spring的声明式事务功能

4)整合SpringMVC(主要是ContextLoaderListener的使用,其他无差别)

Spring整合MyBatis

1.创建项目,创建包

2.添加依赖pom.xml

<dependencies>
<!--MySQL驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
<!--Druid数据源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.31</version>
</dependency>
<!--logback日志框架-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
<!--Junit5测试框架-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.7.0</version>
<scope>test</scope>
</dependency>
<!--Spring5 test-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.1</version>
</dependency>
<!--Mybatis核心依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<!--Mybatis和Spring整合依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
<!--Spring ORM依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.3.1</version>
</dependency>
<!--Servlet依赖-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!--SpringMVC依赖,含Spring依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.1</version>
</dependency>
<!--Spring和Thymeleaf整合依赖-->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.0.12.RELEASE</version>
</dependency>
<!--MyBatis分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
</dependencies>

3.日志文件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="INFO">
<!--指定打印日志的appender,这里通过“STDOUT”引用了前面配置的appender-->
<appender-ref ref="STDOUT" />
</root>
<!--根据特殊需求指定局部日志级别-->
<logger name="org.springframework.web.servlet.DispatcherServlet" level="DEBUG"/>
<!--包级别的日志设置-->
<logger name="com.atguigu.controller" level="DEBUG"></logger>
<logger name="com.atguigu.serivce" level="DEBUG"></logger>
<logger name="com.atguigu.mapper" level="DEBUG"></logger>
</configuration>

4.配置数据源

1)属性文件jdbc.properties

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url
=jdbc:mysql://localhost:3306/mybatis-example?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
jdbc.user
=root
jdbc.password
=123456

2)配置Druid

创建spring配置文件:spring-persist.xml

<!--加载属性文件-->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<!--配置Druid数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>

3)测试Druid

@SpringJUnitConfig(locations = "classpath:spring-persist.xml")
public class TestSSM {
@Autowired
DataSource dataSource;

@Test
public void testDruid() throws SQLException {
Connection conn = dataSource.getConnection();
System.out.println(conn);
}
}

5.整合MyBatis

1)MyBatis配置文件(可以全部省略)

2)配置SqlSessionFactoryBean

<!--配置SqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<!--指定映射文件的位置,classpath必须写,如果路径和Mapper接口路径相同,可以省略该配置-->
<!-- <property name="mapperLocations" value="classpath:com/atguigu/mapper/*Mapper.xml"></property>-->
<!--配置类型别名包-->
<property name="typeAliasesPackage" value="com.atguigu.pojo"></property>
<!--配置全局变量-->
<property name="configuration">
<bean class="org.apache.ibatis.session.Configuration">
<property name="mapUnderscoreToCamelCase" value="true"></property>
<property name="lazyLoadingEnabled" value="true"></property>
</bean>
</property>
</bean>

配置SqlSessionFactory,这是一个工厂Bean,返回的不是SqlSessionFactoryBean,而是该类的getObject()方法的返回值SqlSessionFactory

3)配置Mapper接口扫描器

<!--配置Mapper接口扫描,属于myBatis-spring整合包-->
<bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.atguigu.mapper"></property>
</bean>
<!-- <mybatis-spring:scan base-package="com.atguigu.mapper"></mybatis-spring:scan>-->

1.配置MapperScannerConfigurer:其实整合MyBatis还有其他方法,这个算是最给力的,这是一个强大的功能,让它扫描特定的包,自动帮我们成批地创建映射器Mapper,这样一来,就能大大减少配置的工作量

2.底层采用了Spring针对自动侦测到的组件的默认命名策略,即把类/接口名字的首字母小写,其他不变,作为映射器的名字,例如映射器接口UserMapper被扫描后创建的映射器bean名为userMapper

4)开发MyBatis业务

实体类Employee:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Employee {
private Integer empId;
private String empName;
private Double empSalary;
}

Mapper接口EmployeeMapper

public interface EmployeeMapper {
public List<Employee> findAll();

public int saveEmp(Employee employee);
}

映射文件EmployeeMapper.xml

<mapper namespace="com.atguigu.mapper.EmployeeMapper">
<select id="findAll" resultType="employee">
select * from t_emp
</select>
<insert id="saveEmp">
insert into t_emp values(null, #{empName}, #{empSalary})
</insert>
</mapper>

5)测试整合MyBatis

@SpringJUnitConfig(locations = "classpath:spring-persist.xml")
public class TestSSM {
@Autowired
DataSource dataSource;

@Autowired
private EmployeeMapper employeeMapper;

@Test
public void testDruid() throws SQLException {
Connection conn = dataSource.getConnection();
System.out.println(conn);
}

@Test
public void testFindAll() {
List<Employee> list = employeeMapper.findAll();
list.forEach(employee-> System.out.println(employee));
}

@Test
public void testSaveEmp() {
Employee emp = new Employee(null, "张三", 123.45);
int i = employeeMapper.saveEmp(emp);
System.out.println(i);
}

//查看IoC容器中有哪些Bean
//DataSource,EmployeeMapper,SqlSessionFactory
@Test
public void testBeanName() {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-persist.xml");
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String beanDefinitionName: beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
}
}

Spring管理业务层事务

1.增加业务层

1)增加业务层接口

public interface EmployeeService {
public List<Employee> findAll();

public int saveEmp(Employee employee);

public int saveEmp2(Employee employee1, Employee employee2);
}

2)增加业务层实现类

@Service
public class EmployeeServiceImpl implements EmployeeService {
@Autowired
EmployeeMapper employeeMapper;

@Override
public List<Employee> findAll() {
return this.employeeMapper.findAll();
}

@Override
public int saveEmp(Employee employee) {
return this.employeeMapper.saveEmp(employee);
}

@Override
public int saveEmp2(Employee employee1, Employee employee2) {
int n1 = this.employeeMapper.saveEmp(employee1);
int n2 = this.employeeMapper.saveEmp(employee2);
return n1 + n2;
}
}

3)扫描业务层注解

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

4)测试:发现没有事务

@SpringJUnitConfig(locations = "classpath:spring-persist.xml")
public class TestSSM2 {
@Autowired
EmployeeService employeeService;

@Test
public void testFindAll() {
List<Employee> empList = employeeService.findAll();
empList.forEach(emp -> System.out.println(emp));
}

@Test
public void testSaveEmp() {
Employee emp = new Employee(null, "李四", 54.321);
int n = employeeService.saveEmp(emp);
System.out.println(n);
}

@Test
public void testSaveEmp2() {
Employee emp1 = new Employee(null, "黄杉女", 5413.21);
Employee emp2 = new Employee(null, "张无忌", 121312135243.21);
int n = employeeService.saveEmp2(emp1, emp2);
System.out.println(n);
}
}

2.添加业务层事务

1)配置事务管理器+启动事务注解

<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--启用事务注解驱动-->
<tx:annotation-driven></tx:annotation-driven>

2)给业务层方法添加事务功能

@Service
public class EmployeeServiceImpl implements EmployeeService {
@Autowired
EmployeeMapper employeeMapper;

@Override
@Transactional(readOnly = true)
public List<Employee> findAll() {
return this.employeeMapper.findAll();
}

@Override
@Transactional
public int saveEmp(Employee employee) {
return this.employeeMapper.saveEmp(employee);
}

@Override
@Transactional
public int saveEmp2(Employee employee1, Employee employee2) {
int n1 = this.employeeMapper.saveEmp(employee1);
int n2 = this.employeeMapper.saveEmp(employee2);
return n1 + n2;
}
}

3)测试事务功能

<logger name="org.springframework.jdbc.datasource.DataSourceTransactionManager" level="DEBUG"/>

Spring整合SpringMVC

1.环境准备

1)将Java项目变为Web项目

可以使用JBLJAVATOWEB插件:<packaging>war</packaging>

2)web.xml

<!--配置ContextLoaderListener-->
<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>

<!--配置SpringMVC总控制器-->
<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>

<!--解决POST中文乱码过滤器-->
<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>

<!--解决Restful支持put、delete请求的过滤器-->
<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)spring-mvc.xml

<!--组件扫描:扫描@Controller-->
<context:component-scan base-package="com.atguigu.controller"></context:component-scan>
<!--启用SpringMVC注解驱动-->
<mvc:annotation-driven></mvc:annotation-driven>
<!--让静态资源可以正常访问-->
<mvc:default-servlet-handler></mvc:default-servlet-handler>
<!--配置视图控制器-->
<mvc:view-controller path="/" view-name="portal"></mvc:view-controller>
<!--配置Thymeleaf视图解析器-->
<bean 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>

4)部署项目并运行

可以使用idea绑定的tomcat,也可以使用tomcat7-maven-plugin

<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<!--配置tomcat启动的端口号-->
<port>80</port>
<!--配置项目的上下文路径-->
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>

2.查询所有员工

1)开发控制器

@Controller
@RequestMapping("/employee")
public class EmployeeController {
@Autowired
private EmployeeService employeeService;

@GetMapping
public String findAll(Model model) {
List<Employee> empList = employeeService.findAll();
model.addAttribute("empList", empList);
return "empList";
}
}

2)开发页面

<a th:href="@{/employee}">查询员工列表</a><br>

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.white{
background-color: white;
}
.beige{
background-color: beige;
}
</style>
</head>
<body>
<h3 align="center">员工列表</h3>
<table width="60%" align="center" border="1">
<tr>
<th>员工编号</th>
<th>员工姓名</th>
<th>员工薪水</th>
<th>status.index</th>
<th>status.count</th>
<th>操作</th>
</tr>
<tr th:each="emp,status:${empList}" th:class="${status.index}/2==0?'white':'beige'">
<td th:text="${emp.empId}"></td>
<td th:text="${emp.empName}"></td>
<td th:text="${emp.empSalary}"></td>
<td th:text="${status.index}"></td>
<td th:text="${status.count}"></td>
<td>
修改
删除
</td>
</tr>
<tr>
<td colspan="10" align="center">
<a th:href="@{/aaa/bbb}">添加员工</a>
</td>
</tr>
</table>
</body>
</html>

3)测试

3.添加员工

1)配置视图控制器

<mvc:view-controller path="/employee/page/toAdd" view-name="empAdd"></mvc:view-controller>

2)开发页面

<h3>添加员工</h3>
<form th:action="@{/employee}" method="post">
姓名:<input type="text" name="empName"><br>
薪水:<input type="text" name="empSalary"><br>
<input type="submit" value="提交">
</form>

3)开发控制器

@PostMapping
public String addEmp(Employee employee) {
employeeService.saveEmp(employee);
return "redirect:/employee";
}

SSM整合下的分页功能

1.理解分页

1)分页的好处:

用户体验较好

 服务器端每次只查询一部分数据,内存压力减小

 对冷数据减少查询的次数,据此对系统性能进行优化

2)分页的SQL语句:

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 (pageNum-1)*pageSize, pageSize

3)分页的三个基本要素:

 当前页号:pageNum pageIndex

 每页记录数:pageSize

 总的记录数:totalCount

4)分页的其他要素:由其他要素计算而来

 总页数:pages,100/5=20,101/5=21

 前一页:prePage,2--1,1--1

 后一页:nextPage,2—3,n--n

 是否为第一页:isFirstPage,1

 是否为最后一页:isLastPage,pages

 是否有前一页:hasPreviousPage

 是否有下一页:hasNextPage

 所有导航页号:navigatepageNums

2.分页插件

PageHelper是MyBatis框架的一个插件,用于支持在MyBatis执行分页操作,使用非常方便

GitHub:

https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.md

码云:

https://gitee.com/free/Mybatis_PageHelper

添加依赖

<!--MyBatis分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>

给SqlSessionFactoryBean注入插件属性

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<!--指定映射文件的位置,classpath必须写,如果路径和Mapper接口路径相同,可以省略该配置-->
<!-- <property name="mapperLocations" value="classpath:com/atguigu/mapper/*Mapper.xml"></property>-->
<!--配置类型别名包-->
<property name="typeAliasesPackage" value="com.atguigu.pojo"></property>
<!--配置全局变量-->
<property name="configuration">
<bean class="org.apache.ibatis.session.Configuration">
<property name="mapUnderscoreToCamelCase" value="true"></property>
<property name="lazyLoadingEnabled" value="true"></property>
</bean>
</property>
<property name="plugins">
<array>
<bean class="com.github.pagehelper.PageInterceptor">
<property name="properties">
<props>
<!--合理化分页,页码的有效范围:1~总页数
当前页是第一页,上一页还是第一页
当前页是最后一页,下一页还是最后一页
-->
<prop key="reasonable">true</prop>
<!--数据库方言:
同样都是SQL语句,拿到不同数据库中,在语法上会有差异
默认情况下,按照MySQL作为数据库方言来运行
-->
<prop key="helperDialect">mysql</prop>
</props>
</property>
</bean>
</array>
</property>
</bean>

3.分页操作

1)开发控制器

@GetMapping("/get/page/{pageNum}")
public String findEmp(@PathVariable("pageNum") Integer pageNum, Model model) {
//int pageSize = 5; //每页显示5条记录
int pageSize = 1;
PageInfo<Employee> pageInfo = employeeService.findByPage(pageNum, pageSize);
model.addAttribute("pageInfo", pageInfo);
return "empList2";
}

2)开发业务层

public PageInfo<Employee> findByPage(Integer pageNum, int pageSize);

@Override
public PageInfo<Employee> findByPage(Integer pageNum, int pageSize) {
//启用分页插件(底层会做很多工作,比如查询记录总数,给查询所有的语句添加limit)
PageHelper.startPage(pageNum, pageSize);
//查询所有员工(分页插件会自动添加limit子句)
List<Employee> empList = this.employeeMapper.findAll();
//返回分页数据(包含员工)
//return new PageInfo<Employee>(empList);
return new PageInfo<Employee>(empList, 8);
}

3)开发视图层

<a th:href="@{/employee/get/page/1}">查询员工列表(分页)</a>

<tr th:each="emp,status:${pageInfo.list}" th:class="${status.index}/2==0?'white':'beige'">
<td th:text="${emp.empId}"></td>
<td th:text="${emp.empName}"></td>
<td th:text="${emp.empSalary}"></td>
<td th:text="${status.index}"></td>
<td th:text="${status.count}"></td>
<td>
修改
删除
</td>
</tr>
<tr>
<td colspan="10" align="center">
<span th:if="${pageInfo.hasPreviousPage}">
<a th:href="@{/employee/get/page/1}">首页</a>
<a th:href="@{|/employee/get/page/${pageInfo.prePage}|}">上一页</a>
</span>
<span th:each="num:${pageInfo.navigatepageNums}">
<a th:if="${num!=pageInfo.pageNum}" th:href="@{|/employee/get/page/${num}|}" th:text="${num}"></a>&nbsp;&nbsp;
<span th:if="${num==pageInfo.pageNum}" th:text="|[${num}]|"></span>
</span>
<span th:if="${pageInfo.hasNextPage}">
<a th:href="@{|/employee/get/page/${pageInfo.nextPage}|}">下一页</a>
<a th:href="@{|/employee/get/page/${pageInfo.pages}|}">末页</a>
</span>
<span th:text="|${pageInfo.pageNum}/${pageInfo.pageSize}|"></span>
</td>
</tr>
<tr>
<td colspan="10" align="center">
<a th:href="@{/employee/page/toAdd}">添加员工</a>
</td>
</tr>

4.PageHelper的更多细节

问题1:调用findAll()方法返回是empList,并作为参数传给PageInfo对象,而PageInfo类的其他分页属性是如何计算出来的

List<Employee> empList = this.employeeMapper.findAll();

return new PageInfo<Employee>(empList, 8);

返回值其实是Page,是ArrayList的一个子类,不仅包含emp数据,也包括各种分页属性,也就是emp数据以及各种分页属性都是包含在findAll()的返回值中的,进入PageInfo只是进行了结构的转换

问题2:既然Page中已经有了分页的员工数据,以及分页的各个属性,为什么还要封装到PageInfo中,直接返回Page不可以吗?

可以,但是没有navigatepageNums、navigatePages、prePage、nextPage、startRow、endRow的相关信息,所以功能受限