Spring 入门(四):SSM 整合


本文会以 XML方式全注解方式 两种方式分别介绍 SSM 框架的整合。

XML 方式整合

?? 建立 Web 的 Maven 工程

使用 IDEA 创建普通 Maven 项目之后,右击项目,选择Add Frameworks Support,勾选Java EE下面的Web Application,并选择 web.xml 的版本,点击确定,会自动在当前项目下创建 web 目录。

Web 项目还需要配置 Tomcat,此处设置项目的Application context/,访问端口为 8080。整个项目目录如下所示:

?? 在 pom.xml 中导入 jar 包依赖

    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 执行了!