Spring 入门(四):SSM 整合
本文会以 XML方式 和 全注解方式 两种方式分别介绍 SSM 框架的整合。
XML 方式整合
?? 建立 Web 的 Maven 工程
使用 IDEA 创建普通 Maven 项目之后,右击项目,选择Add Frameworks Support,勾选Java EE下面的Web Application,并选择 web.xml 的版本,点击确定,会自动在当前项目下创建 web 目录。
Web 项目还需要配置 Tomcat,此处设置项目的Application context为/,访问端口为 8080。整个项目目录如下所示:
5.2.8.RELEASE
2.13.3
org.springframework
spring-context
${spring-version}
org.springframework
spring-webmvc
${spring-version}
javax.servlet
javax.servlet-api
4.0.1
provided
javax.servlet.jsp
javax.servlet.jsp-api
2.3.3
provided
com.fasterxml.jackson.core
jackson-core
2.11.1
com.fasterxml.jackson.core
jackson-databind
2.11.1
com.fasterxml.jackson.core
jackson-annotations
2.11.1
org.aspectj
aspectjweaver
1.9.6
mysql
mysql-connector-java
8.0.21
org.mybatis
mybatis
3.5.5
org.mybatis
mybatis-spring
2.0.5
com.alibaba
druid
1.1.23
org.springframework
spring-jdbc
${spring-version}
commons-fileupload
commons-fileupload
1.4
org.projectlombok
lombok
1.18.12
provided
org.springframework
spring-test
${spring-version}
test
junit
junit
4.12
test
org.apache.logging.log4j
log4j-api
${log4j2.version}
org.apache.logging.log4j
log4j-core
${log4j2.version}
org.apache.logging.log4j
log4j-web
${log4j2.version}
org.apache.maven.plugins
maven-compiler-plugin
3.8.1
1.8
1.8
UTF-8
Spring 和 MyBatis 整合
?? 数据库脚本
DROP TABLE IF EXISTS `t_customer`;
CREATE TABLE `t_customer` (
`id` INT PRIMARY KEY AUTO_INCREMENT COMMENT '客户ID',
`username` VARCHAR(50) COMMENT '客户名',
`job` VARCHAR(50) COMMENT '职业',
`phone` VARCHAR(16) COMMENT '手机号码'
) COMMENT '客户表';
INSERT INTO `t_customer` VALUES(1, 'joy', 'doctor', '11111111111');
INSERT INTO `t_customer` VALUES(2, 'jack', 'teacher', '22222222222');
INSERT INTO `t_customer` VALUES(3, 'tom', 'worker', '33333333333');
?? 创建实体类
package com.example.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Customer implements Serializable {
private Integer id; // 客户 ID
private String username; // 客户名
private String job; // 职业
private String phone; // 手机号码
}
?? 编写 CustomerMapper 接口,并添加 Mapper 方法
package com.example.mapper;
import com.example.entity.Customer;
public interface CustomerMapper {
// 根据 id 查询客户
Customer selectById(Integer id);
// 插入客户
int insert(Customer customer);
}
?? 在resources/mapper目录下添加接口对应的 XML 映射文件 CustomerMapper.xml
insert into t_customer(username, job, phone) values(#{username}, #{job}, #{phone})
?? 编写 ICustomerService 接口
package com.example.service;
import com.example.entity.Customer;
import java.util.List;
public interface ICustomerService {
// 根据 id 查询客户
Customer findOne(int userId);
// 插入客户
int save(Customer customer);
}
?? 实现 ICustomerService 接口
package com.example.service.impl;
import com.example.entity.Customer;
import com.example.mapper.CustomerMapper;
import com.example.service.ICustomerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class CustomerServiceImpl implements ICustomerService {
@Autowired
private CustomerMapper customerMapper;
@Override
public Customer findOne(int userId) {
return customerMapper.selectById(userId);
}
@Override
@Transactional(rollbackFor = Exception.class)
public int save(Customer customer) {
return customerMapper.insert(customer);
}
}
?? 编写 jdbc.properties 文件,配置数据库连接信息
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/learning?characterEncoding=utf-8&useSSL=false&serverTimezone=Hongkong
jdbc.username=root
jdbc.password=123456
?? 编写 Spring 核心配置文件 spring-mybatis.xml
<?xml version="1.0" encoding="UTF-8"?>
?? 编写日志配置文件 log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
?? 编写测试方法
import com.example.entity.Customer;
import com.example.service.ICustomerService;
import junit.framework.TestCase;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
// 加载 Spring 核心配置文件,创建 IoC 容器
@ContextConfiguration("classpath:spring-mybatis.xml")
public class CustomerServiceImplTest extends TestCase {
@Autowired
private ICustomerService customerService;
@Test
public void testFindOne() {
System.out.println(customerService.findOne(1));
}
@Test
public void testSave() {
Customer customer1 = new Customer(null, "test1", "worker", "44444444444");
Assert.assertEquals(1, customerService.save(customer1));
System.out.println(customer1);
}
}
// testFindOne() 执行结果
==> Preparing: select id, username, job, phone from t_customer where id = ?
==> Parameters: 1(Integer)
<== Columns: id, username, job, phone
<== Row: 1, joy, doctor, 11111111111
<== Total: 1
Customer(id=1, username=joy, job=doctor, phone=11111111111)
// testSave() 执行结果
==> Preparing: insert into t_customer(username, job, phone) values(?, ?, ?)
==> Parameters: test1(String), worker(String), 44444444444(String)
<== Updates: 1
Customer(id=4, username=test1, job=worker, phone=44444444444)
整合 SpringMVC
?? 编写 SpringMVC 的核心配置文件 spring-mvc.xml
<?xml version="1.0" encoding="UTF-8"?>
?? 编写 web.xml 文件
<?xml version="1.0" encoding="UTF-8"?>
org.springframework.web.context.ContextLoaderListener
contextConfigLocation
classpath:spring-mybatis.xml
DispatcherServlet
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:spring-mvc.xml
1
DispatcherServlet
/
CharacterEncodingFilter
org.springframework.web.filter.CharacterEncodingFilter
encoding
UTF-8
CharacterEncodingFilter
/*
?? 编写自定义的类型转换器
package com.example.converter;
import org.springframework.core.convert.converter.Converter;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class DateConverter implements Converter {
// 定义日期格式
private String datePattern = "yyyy-MM-dd HH:mm:ss";
@Override
public Date convert(String s) {
// 格式化日期
SimpleDateFormat format = new SimpleDateFormat(datePattern);
try {
return format.parse(s);
} catch (ParseException e) {
throw new IllegalArgumentException("无效的日期格式,请使用这种格式: " + datePattern);
}
}
}
?? 编写自定义的拦截器
package com.example.interceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class CustomInterceptor implements HandlerInterceptor {
// 在 Controller 方法执行之前被调用
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandler 执行了!");
return true;
}
// 在 Controller 方法执行之后,视图解析之前被调用
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandler 执行了!");
}
// 在整个流程处理完成,即渲染视图结束之后被调用
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion 执行了!");
}
}
?? 编写 TestController 控制器
package com.example.controller;
import com.example.entity.Customer;
import com.example.service.ICustomerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class TestController {
@Autowired
private ICustomerService customerService;
@GetMapping("/get/{userId}")
public String showUser(@PathVariable int userId, Model model) {
Customer customer = customerService.findOne(userId);
model.addAttribute("customer", customer);
return "userInfo";
}
@GetMapping("/insert")
@ResponseBody
public Customer insertUser() {
Customer customer = new Customer(null, "test1", "worker", "44444444444");
customerService.save(customer);
return customer;
}
}
?? 启动项目,访问localhost:8080/get/1,页面显示:
编号:1
姓名:joy
职业:doctor
电话:11111111111
后端控制台显示:
preHandler 执行了!
==> Preparing: select id, username, job, phone from t_customer where id = ?
==> Parameters: 1(Integer)
<== Columns: id, username, job, phone
<== Row: 1, joy, doctor, 11111111111
<== Total: 1
postHandler 执行了!
afterCompletion 执行了!
?? 访问localhost:8080/insert,页面显示:
{"id":5,"username":"test","job":"xxx","phone":"xxxxxxxxx"}
后端控制台显示:
preHandler 执行了!
==> Preparing: insert into t_customer(username, job, phone) values(?, ?, ?)
==> Parameters: test(String), xxx(String), xxxxxxxxx(String)
<== Updates: 1
postHandler 执行了!
afterCompletion 执行了!
如果访问出现 404,服务器报错,可能原因是 IDEA 的项目发布中没有 lib 依赖,需要手动添加 jar 包(这是 IDEA 的 bug),流程如下:
全注解方式整合
此处将会使用全注解的方式替代 spring-mybatis.xml、spring-mvc.xml 和 web.xml 三个文件。
?? 编写 SpringConfig 配置类替代 spring-mybatis.xml
package com.example.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import java.io.IOException;
//@Configuration // 不需要添加 @Configuration 注解
@EnableTransactionManagement // 开启事务的注解支持,会自动加载事务管理器
@ComponentScan("com.example") // Spring 的注解扫描的包路径
@PropertySource("classpath:jdbc.properties") // 加载 jdbc.properties 配置文件
public class SpringConfig {
/**
* 配置数据库连接池(Druid)
*/
@Bean
public DruidDataSource dataSource(@Value("${jdbc.driver}") String driver,
@Value("${jdbc.url}") String url,
@Value("${jdbc.username}") String username,
@Value("${jdbc.password}") String password) {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
/**
* 配置 Mybatis 的 Session 工厂(参数 dataSource 自动注入)
*/
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DruidDataSource dataSource) throws IOException {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
// 设置数据库连接池
factory.setDataSource(dataSource);
// 设置 Mapper 的映射文件位置
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] mapperLocations = resolver.getResources("classpath:mapper/*.xml");
factory.setMapperLocations(mapperLocations);
// 设置别名的包路径
factory.setTypeAliasesPackage("com.example.entity");
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
// 是否开启自动驼峰命名规则(camel case)映射,默认值为 false
configuration.setMapUnderscoreToCamelCase(true);
// 是否开启延迟加载的全局开关,默认值为 false
configuration.setLazyLoadingEnabled(true);
// 设置为 false 表示按需加载。默认值在 3.4.1 版本之前为 true,之后为 false
configuration.setAggressiveLazyLoading(false);
// 是否开启二级缓存,默认为 true,所以可以不需要配置
configuration.setCacheEnabled(true);
factory.setConfiguration(configuration);
return factory;
}
/**
* 配置事务管理器,使用 Spring 的事务管理(参数 dataSource 自动注入)
*/
@Bean
public DataSourceTransactionManager transactionManager(DruidDataSource dataSource) {
DataSourceTransactionManager manager = new DataSourceTransactionManager();
manager.setDataSource(dataSource);
return manager;
}
/**
* 配置 Mapper 扫描器,将指定包的所有 Mapper 接口类注册为 bean
*/
@Bean
public MapperScannerConfigurer mapperScanner() {
MapperScannerConfigurer scanner = new MapperScannerConfigurer();
// Mapper 扫描的包路径
scanner.setBasePackage("com.example.mapper");
return scanner;
}
}
?? 编写 SpringMvcConfig 配置类替代 spring-mvc.xml
package com.example.config;
import com.example.converter.DateConverter;
import com.example.interceptor.CustomInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.format.FormatterRegistry;
import org.springframework.stereotype.Controller;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import java.nio.charset.StandardCharsets;
// @Configuration // 不需要加 @Configuration 注解
@ComponentScan(value = "com.example", // 配置 IoC 容器注解扫描的包路径
useDefaultFilters = false, // 关闭默认的注解扫描的 Filter
// 制定扫包规则,只扫描使用 @Controller 注解修饰的 Java 类
includeFilters = @Filter(type = FilterType.ANNOTATION, value = Controller.class)
)
@EnableWebMvc // 开启 SpringMVC 的注解支持
public class SpringMvcConfig implements WebMvcConfigurer {
/**
* 添加自定义类型转换器
*/
@Override
public void addFormatters(FormatterRegistry registry) {
// 添加日期数据的类型解析器
registry.addConverter(new DateConverter());
}
/**
* 配置视图解析器
*/
@Bean
public InternalResourceViewResolver viewResolver(){
// 真正的视图路径路径为:前缀 + 逻辑视图名 + 后缀
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/pages/"); // 前缀
viewResolver.setSuffix(".jsp"); // 后缀
return viewResolver;
}
/**
* 配置文件上传解析器
* 注意:文件上传解析器 bean 的 id 是固定的,只能为 multipartResolver
*/
@Bean
public CommonsMultipartResolver multipartResolver() {
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
// 设置编码方式
multipartResolver.setDefaultEncoding(String.valueOf(StandardCharsets.UTF_8));
// 设置允许上传文件的最大值,单位为字节
multipartResolver.setMaxUploadSize(5242880);
// 设置缓存中的最大尺寸,单位为字节
multipartResolver.setMaxInMemorySize(40960);
// 推迟文件解析,以便在 Controller 中捕获文件大小异常
multipartResolver.setResolveLazily(true);
return multipartResolver;
}
/**
* 配置静态资源的访问映射
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// Handler:对外暴露的访问路径,"/**" 表示静态资源目录下的所有文件都能访问
// Locations:映射到的本地静态资源路径,具体到某个文件夹
registry.addResourceHandler("/js/**").addResourceLocations("/js/");
registry.addResourceHandler("/css/**").addResourceLocations("/css/");
}
/**
* 配置自定义拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// addPathPatterns:配置需要拦截器作用的路径,/** 表示拦截全部的请求
// excludePathPatterns:配置不需要拦截器作用的路径,静态资源的访问 URL 一般不拦截
registry.addInterceptor(new CustomInterceptor())
.addPathPatterns("/**").excludePathPatterns("/js/**", "/css/**");
}
}
?? 编写 WebInitializer 配置类替代 web.xml
package com.example.config;
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
import javax.servlet.FilterRegistration;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import java.nio.charset.StandardCharsets;
/**
* 取代 web.xml
*/
public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
/**
* 指定 Spring 核心配置类,用于创建 Root WebApplicationContext 容器
*/
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{SpringConfig.class};
}
/**
* 指定 SpringMVC 的核心配置类,用于创建 Servlet WebApplicationContext 容器
*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[]{SpringMvcConfig.class};
}
/**
* 设置能被 DispatcherServlet 拦截并处理的 URL 请求
*/
@Override
protected String[] getServletMappings() {
// 默认配置,/ 表示拦截所有请求 URL,包括静态资源的请求,但不包括 *.jsp 请求;
// 对于 *.jsp 的请求会交由 JspServlet 处理,所以非 WEB-INF 目录下的 *.jsp 能直接访问
return new String[]{"/"};
}
/**
* 可以在此方法添加自定义的 Servlet、Listener、Filter
*/
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// 添加全局过滤的 filter,将请求消息的编码统一设置为 UTF-8
FilterRegistration.Dynamic encodingFilter = servletContext.addFilter("encodingFilter", CharacterEncodingFilter.class);
encodingFilter.setInitParameter("encoding", String.valueOf(StandardCharsets.UTF_8));
// * 是通配符,/* 表示拦截所有以 / 开头的请求 URL
encodingFilter.addMappingForUrlPatterns(null, false, "/*");
// 必须调用回父类的 onStartup 方法,否则不会初始化 DispatcherServlet
super.onStartup(servletContext);
}
}
?? 启动项目,访问localhost:8080/get/1,页面显示:
编号:1
姓名:joy
职业:doctor
电话:11111111111
后端控制台显示:
preHandler 执行了!
==> Preparing: select id, username, job, phone from t_customer where id = ?
==> Parameters: 1(Integer)
<== Columns: id, username, job, phone
<== Row: 1, joy, doctor, 11111111111
<== Total: 1
postHandler 执行了!
afterCompletion 执行了!
?? 访问localhost:8080/insert,页面显示:
{"id":6,"username":"test","job":"xxx","phone":"xxxxxxxxx"}
后端控制台显示:
preHandler 执行了!
==> Preparing: insert into t_customer(username, job, phone) values(?, ?, ?)
==> Parameters: test(String), xxx(String), xxxxxxxxx(String)
<== Updates: 1
postHandler 执行了!
afterCompletion 执行了!