SSM整合笔记

#Part1 SSM整合原理

一、SSM整合概述

1. SSM整合的概念

微观:将学习的Spring SpringMVC Mybatis框架应用到项目中。

  • SpringMVC框架负责控制层
  • Spring 框架负责整体和业务层的声明式事务管理
  • MyBatis框架负责数据库访问层

宏观:Spring接管一切(将框架核心组件交给Spring进行IoC管理),代码更加简洁。

  • SpringMVC管理表述层、SpringMVC相关组件
  • Spring管理业务层、持久层、以及数据库相关(DataSource,MyBatis)的组件
  • 使用IoC的方式管理一切所需组件

实施:通过编写配置文件,实现SpringIoC容器接管一切组件。

2. SSM整合的核心问题

2.1 SSM整合需要几个IoC容器?

两个容器。

本质上说,整合就是将三层架构和框架核心API组件交给SpringIoC容器管理。

一个容器可能就够了,但是我们常见的操作是创建两个IoC容器(web容器root容器),组件分类管理。

这种做法有以下好处和目的:

  1. 分离关注点:通过初始化两个容器,可以将各个层次的关注点进行分离。这种分离使得各个层次的组件能够更好地聚焦于各自的责任和功能。
  2. 解耦合:各个层次组件分离装配不同的IoC容器,这样可以进行解耦。这种解耦合使得各个模块可以独立操作和测试,提高了代码的可维护性和可测试性。
  3. 灵活配置:通过使用两个容器,可以为每个容器提供各自的配置,以满足不同层次和组件的特定需求。每个配置文件也更加清晰和灵活。

总的来说,初始化两个容器在SSM整合中可以实现关注点分离、解耦合、灵活配置等好处。它们各自负责不同的层次和功能,并通过合适的集成方式协同工作,提供一个高效、可维护和可扩展的应用程序架构!

2.2 每个IoC容器对应哪些类型组件?

容器名 盛放组件
web容器 web相关组件(controller,springmvc核心组件)
root容器 业务和持久层相关组件(service,aop,tx,dataSource,mybatis,mapper等)

image_5AumuliVMq

2.3 IoC容器之间关系和调用方向?

情况1:两个无关联IoC容器之间的组件无法注入。

image_nBgnuyIvr_-6897483

情况2:子IoC容器可以单向的注入父IoC容器的组件。

image__9nDqEIkGi-6897483

结论:web容器是root容器的子容器,父子容器关系。

  • 父容器:root容器,盛放service、mapper、mybatis等相关组件
  • 子容器:web容器,盛放controller、web相关组件

源码中调用流程的图解:

image_YVCAmV3Jcl

2.4 具体需要多少配置类以及对应容器关系?

配置类的数量不是固定的,但是至少要两个,为了方便编写,我们可以三层架构每层对应一个配置类,分别指定两个容器加载即可

建议配置文件:

配置名 对应内容 对应容器
WebJavaConfig controller,springmvc相关 web容器
ServiceJavaConfig service,aop,tx相关 root容器
MapperJavaConfig mapper,datasource,mybatis相关 root容器

image_jfq5s1X9ET

2.5 IoC初始化方式和配置位置?

在web项目下,我们可以选择web.xml和配置类方式进行ioc配置,推荐配置类

对于使用基于 web 的 Spring 配置的应用程序,建议这样做,如以下示例所示:

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    /** 指定root容器对应的配置类 */
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[] { ServiceJavaConfig.class,MapperJavaConfig.class };
    }

    /** 指定web容器对应的配置类 */
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] { WebJavaConfig.class };
    }

    /** 指定dispatcherServlet处理路径,通常为 /  */
    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }
}

图解配置类和容器配置:

image_7lcTGvPD2Q

二、SSM配置整合

§1. 准备工作

1.1 数据库准备

CREATE DATABASE `mybatis-example`;

USE `mybatis-example`;

CREATE TABLE `t_emp`(
    emp_id INT AUTO_INCREMENT,
    emp_name CHAR(100),
    emp_salary DOUBLE(10,5),
    PRIMARY KEY(emp_id)
);

INSERT INTO `t_emp`(emp_name,emp_salary) VALUES("tom",200.33);
INSERT INTO `t_emp`(emp_name,emp_salary) VALUES("jerry",666.66);
INSERT INTO `t_emp`(emp_name,emp_salary) VALUES("andy",777.77);

1.2 准备项目

创建IDEA项目,并转成web项目。

1.3 ☀️依赖导入

需要依赖清单分析:

  1. spring
    • ioc/di
      • spring-context / 6.0.6
      • jakarta.annotation-api / 2.1.1
    • aop
      • spring-aspects / 6.0.6
    • tx事务管理
      • spring-tx / 6.0.6
      • spring-jdbc / 6.0.6
  2. springmvc
    • spring-webmvc / 6.0.6
    • jakarta.jakartaee-web-api / 9.1.0
    • jackson-databind / 2.15.0
    • hibernate-validator / 8.0.0.Final
    • hibernate-validator-annotation-processor / 8.0.0.Final
  3. mybatis
    • mybatis / 3.5.11
    • mysql / 8.0.25
    • pagehelper / 5.1.11
  4. 整合需要
    • 加载spring容器:spring-web / 6.0.6
    • 整合mybatis:mybatis-spring x x
    • 数据库连接池:druid / x
    • lombok:lombok / 1.18.26
    • 日志输出:logback / 1.2.3

pom.xml 文件中添加依赖。

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">  

    <modelVersion>4.0.0</modelVersion>  
    <groupId>com.example</groupId>  
    <artifactId>part04-ssm-integration</artifactId>  
    <version>1.0-SNAPSHOT</version>  
    <packaging>war</packaging>

    <properties>
        <spring.version>6.0.6</spring.version>
        <jakarta.annotation-api.version>2.1.1</jakarta.annotation-api.version>
        <jakarta.jakartaee-web-api.version>9.1.0</jakarta.jakartaee-web-api.version>
        <jackson-databind.version>2.15.0</jackson-databind.version>
        <hibernate-validator.version>8.0.0.Final</hibernate-validator.version>
        <mybatis.version>3.5.11</mybatis.version>
        <mysql.version>8.0.25</mysql.version>
        <pagehelper.version>5.1.11</pagehelper.version>
        <druid.version>1.2.8</druid.version>
        <mybatis-spring.version>3.0.2</mybatis-spring.version>
        <jakarta.servlet.jsp.jstl-api.version>3.0.0</jakarta.servlet.jsp.jstl-api.version>
        <logback.version>1.2.3</logback.version>
        <lombok.version>1.18.26</lombok.version>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>  
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> 
    </properties>

    <dependencies>
        <!-- spring依赖 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>jakarta.annotation</groupId>
            <artifactId>jakarta.annotation-api</artifactId>
            <version>${jakarta.annotation-api.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <!-- springmvc依赖 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>jakarta.platform</groupId>
            <artifactId>jakarta.jakartaee-web-api</artifactId>
            <version>${jakarta.jakartaee-web-api.version}</version>
            <scope>provided</scope>
        </dependency>

        <!-- jsp需要依赖 -->
        <dependency>
            <groupId>jakarta.servlet.jsp.jstl</groupId>
            <artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
            <version>${jakarta.servlet.jsp.jstl-api.version}</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>${jackson-databind.version}</version>
        </dependency>

        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>${hibernate-validator.version}</version>
        </dependency>

        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator-annotation-processor</artifactId>
            <version>${hibernate-validator.version}</version>
        </dependency>

        <!-- mybatis依赖 -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>${mybatis.version}</version>
        </dependency>

        <!-- MySQL驱动:mybatis底层依赖jdbc驱动实现,本次不需要导入连接池,mybatis自带 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>

        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>${pagehelper.version}</version>
        </dependency>

        <!-- 整合第三方特殊依赖 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>${mybatis-spring.version}</version>
        </dependency>

        <!-- 日志:会自动传递slf4j门面-->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>${logback.version}</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>${druid.version}</version>
        </dependency>

    </dependencies>

</project>

1.4 实体类

创建 pojo包 中的 Employee实体类

@Data
public class Employee {

    private Integer empId;
    private String empName;
    private Double empSalary;
}

1.5 日志配置

resources目录 中创建 logback.xml日志文件

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
    <!-- 指定日志输出的位置,ConsoleAppender表示输出到控制台 -->
    <appender name="STDOUT"
              class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <!-- 日志输出的格式 -->
            <!-- 按照顺序分别是:时间、日志级别、线程名称、打印日志的类、日志主体内容、换行 -->
            <pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger] [%msg]%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!-- 设置全局日志级别。日志级别按顺序分别是:TRACE、DEBUG、INFO、WARN、ERROR -->
    <!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志。 -->
    <root level="DEBUG">
        <!-- 指定打印日志的appender,这里通过“STDOUT”引用了前面配置的appender -->
        <appender-ref ref="STDOUT" />
    </root>

    <!-- 根据特殊需求指定局部日志级别,可也是包名或全类名。 -->
    <logger name="com.example.mybatis" level="DEBUG" />

</configuration>

§2. 1️⃣控制层配置编写(SpringMVC整合)

主要配置controller、springmvc相关组件配置。

  1. 添加配置类注解@Configuration
  2. 添加mvc复合功能开关@EnableWebMvc(实现json转换器和HandlerMapping、HandlerAdapter)
  3. 声明@ComponentScan注解,进行控制层组件扫描
  4. 全局异常处理器
  5. 静态资源处理
  6. jsp视图解析器前后缀
  7. 拦截器
  8. ......

创建 config包 中的 WebJavaConfig配置类

@Configuration
@EnableWebMvc
@ComponentScan("com.example.controller")
public class WebJavaConfig implements WebMvcConfigurer {

    /** 开启静态资源 */
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable(); 
    }

    /** 配置视图解析器 */
    /*@Override
    public void configureViewResolvers(ViewResolverRegistry registry) { 
        registry.jsp("/WEB-INF/views/", "jsp");
    }*/
    
    /** 配置拦截器 */
    /*@Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new 拦截器类())
            	.addPathPatterns()
            	.excludePathPatterns();
    }*/
}

§3. 2️⃣业务层配置编写(AOP/TX整合)

主要配置service、注解aop和声明事务相关配置。

  1. 声明@Configuration注解,代表配置类
  2. 声明@EnableTransactionManagement注解,开启事务注解支持
  3. 声明@EnableAspectJAutoProxy注解,开启aspect aop注解支持
  4. 声明@ComponentScan注解,进行业务组件扫描
  5. 声明@EnableTransactionManagement注解,开启事务注解支持
  6. 声明transactionManager(DataSource dataSource)方法,指定具体的事务管理器

创建 config包 中的 ServiceJavaConfig配置类

@EnableTransactionManagement
@EnableAspectJAutoProxy
@Configuration
@ComponentScan("com.example.service")
public class ServiceJavaConfig {
    
    @Bean
    public DataSourceTransactionManager transactionManager(DataSource dataSource){
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
    
}

§4. 持久层配置编写(MyBatis整合)

主要配置mapper代理对象、连接池和mybatis核心组件配置。

4.0 MyBatis整合思路分析

MyBatis核心api使用回顾:

// 1. 读取外部配置文件
InputStream ips = Resources.getResourceAsStream("mybatis-config.xml");
// 2. 创建sqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(ips);
// 3. 创建sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 4. 获取mapper代理对象
EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class);
// 5. 数据库方法调用
int rows = empMapper.deleteEmpById(1);
System.out.println("rows = " + rows);
// 6. 提交和回滚
sqlSession.commit();
sqlSession.close();
  • SqlSessionFactoryBuilder

    这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。
    因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 无需ioc容器管理

  • SqlSessionFactory

    一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,因此 SqlSessionFactory 的最佳作用域是应用作用域。 需要ioc容器管理

  • SqlSession

    每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 无需ioc容器管理

  • Mapper映射器实例

    映射器是一些绑定映射语句的接口。映射器接口的实例是从 SqlSession 中获得的。虽然从技术层面上来讲,任何映射器实例的最大作用域与请求它们的 SqlSession 相同。但方法作用域才是映射器实例的最合适的作用域。

    从作用域的角度来说,映射器实例不应该交给ioc容器管理!

    但是从使用的角度来说,业务类(service)需要注入mapper接口,所以mapper需要ioc容器管理

SqlSessionFactoryBean源码展示(MyBatis提供):

为了提高整合效率,mybatis提供了提供封装SqlSessionFactory和Mapper实例化的逻辑的FactoryBean组件,我们只需要声明和指定少量的配置即可。

package org.mybatis.spring;

public class SqlSessionFactoryBean 
    implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ContextRefreshedEvent> {

    // 封装了实例化流程
    public SqlSessionFactory getObject() throws Exception {
        if (this.sqlSessionFactory == null) {
            // 实例化对象逻辑
            afterPropertiesSet();
        }
        // 返回对象逻辑
        return this.sqlSessionFactory;
    }
}

MyBatis整合思路总结:

  1. 需要将SqlSessionFactory和Mapper实例加入到IoC容器。
  2. 使用mybatis整合包提供的FactoryBean快速整合。

4.1 准备jdbc配置文件

resources目录 中创建 jdbc.properties数据库连接配置文件

jdbc.user=root
jdbc.password=123456
jdbc.url=jdbc:mysql:///mybatis-example
jdbc.driver=com.mysql.cj.jdbc.Driver

4.2.1 XML配置方式整合【不推荐】

(0) 介绍

依然保留mybatis的外部配置文件(xml),但是数据库连接信息交给Druid连接池配置。

缺点:依然需要mybatis-config.xml文件,进行xml文件解析,效率偏低。

image_f58uCdqvBM

(1) MyBatis配置文件

数据库信息以及mapper扫描包设置使用Java配置类处理,mybatis其他的功能(别名、settings、插件等信息)依然在mybatis-config.xml配置。

resources目录 中创建 mybatis-config.xml配置文件

<?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"/>
        <!-- 开启logback日志输出-->
        <setting name="logImpl" value="SLF4J"/>
        <!--开启resultMap自动映射 -->
        <setting name="autoMappingBehavior" value="FULL"/>
    </settings>

    <typeAliases>
        <!-- 给包中实体类起别名 -->
        <package name="com.example.pojo"/>
    </typeAliases>

    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <!--
                helperDialect:分页插件会自动检测当前的数据库链接,自动选择合适的分页方式。
                可以配置helperDialect属性来指定分页插件使用哪种方言。配置时,可以使用下面的缩写值:
                oracle,mysql,mariadb,sqlite,hsqldb,postgresql,db2,sqlserver,informix,h2,sqlserver2012,derby
             -->
            <property name="helperDialect" value="mysql"/>
        </plugin>
    </plugins>
</configuration>
(2) 持久层配置类

持久层Mapper配置、数据库配置、Mybatis配置信息。

创建 config包 中的 MapperJavaConfig配置类

@Configuration
@PropertySource("classpath:jdbc.properties")
public class MapperJavaConfig {

    @Value("${jdbc.user}")
    private String user;
    @Value("${jdbc.password}")
    private String password;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.driver}")
    private String driver;

    /** Druid数据库连接池配置 */
    @Bean
    public DataSource dataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        
        dataSource.setUsername(user);
        dataSource.setPassword(password);
        dataSource.setUrl(url);
        dataSource.setDriverClassName(driver);
        
        return dataSource;
    }

    /**
     * 将SqlSessionFactoryBean加入IoC容器:指定连接池对象 和 外部配置文件即可
     * @param dataSource 需要注入连接池对象
     * @return 工厂Bean
     */
    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
        // 1. 实例化SqlSessionFactory工厂
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        // 2. 指定数据库连接池对象
        sqlSessionFactoryBean.setDataSource(dataSource);
        // 3. 指定外部的mybatis配置文件
        // org.springframework.core.io.Resource
        Resource resource = new ClassPathResource("mybatis-config.xml");
        sqlSessionFactoryBean.setConfigLocation(resource);

        return sqlSessionFactoryBean;
    }

    /**
     * 将Mapper代理对象加入IoC容器:
     * 配置Mapper实例扫描工厂,配置 <mapper <package 对应接口和mapperxml文件所 在的包
     * @return mapper代理对象
     */
    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer(){
        // 1. 实例化Mapper代理对象的FactoryBean
        MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
        // 2. 指定mapper接口和xml文件所在的共同包
        mapperScannerConfigurer.setBasePackage("com.example.mapper");
        
        return mapperScannerConfigurer;
    }

}

这种配置方式出现的问题:

当在Spring配置类中添加了sqlSessionFactoryBeanmapperScannerConfigurer配置方法时,可能会导致@Value注解读取不到值为null的问题。

  • 这是因为SqlSessionFactoryBeanMapperScannerConfigurer是基于MyBatis框架的配置,它们的初始化顺序可能会导致属性注入的问题。
  • SqlSessionFactoryBeanMapperScannerConfigurer在配置类中通常是用来配置MyBatis相关的Bean,例如数据源、事务管理器、Mapper扫描等。这些配置类通常在@Configuration注解下定义,并且使用@Value注解来注入属性值。
  • 当配置类被加载时,Spring容器会首先处理Bean的定义和初始化,其中包括sqlSessionFactoryBeanmapperScannerConfigurer的初始化。在这个过程中,如果@Value注解所在的Bean还没有被完全初始化,可能会导致注入的属性值为null。

解决方案:分成两个配置类独立配置,互不影响,数据库提取一个配置类,mybatis提取另一个配置类即可解决。

(3) 解决问题:拆分配置
① 数据库配置类

创建 config包 中的 DataSourceJavaConfig配置类

@Configuration
@PropertySource("classpath:jdbc.properties")
public class DataSourceJavaConfig {

    @Value("${jdbc.user}")
    private String user;
    @Value("${jdbc.password}")
    private String password;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.driver}")
    private String driver;

    /** 数据库连接池配置 */
    @Bean
    public DataSource dataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUsername(user);
        dataSource.setPassword(password);
        dataSource.setUrl(url);
        dataSource.setDriverClassName(driver);
        return dataSource;
    }
}
② MyBatis配置类

创建 config包 中的 MapperJavaConfig配置类

@Configuration
public class MapperJavaConfig {

    /**
     * 将SqlSessionFactoryBean加入IoC容器:指定连接池对象 和 外部配置文件即可
     * @param dataSource 需要注入连接池对象
     * @return 工厂Bean
     */
    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){ // 这里报错没关系,编译后加入IoC容器就可以读取到了
        // 1. 实例化SqlSessionFactory工厂
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        // 2. 指定数据库连接池对象
        sqlSessionFactoryBean.setDataSource(dataSource);
        // 3. 指定外部的mybatis配置文件
        // org.springframework.core.io.Resource
        Resource resource = new ClassPathResource("mybatis-config.xml");
        sqlSessionFactoryBean.setConfigLocation(resource);

        return sqlSessionFactoryBean;
    }

    /**
     * 将Mapper代理对象加入IoC容器:
     * 配置Mapper实例扫描工厂,配置 <mapper <package 对应接口和mapperxml文件所 在的包
     * @return mapper代理对象
     */
    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer(){
        // 1. 实例化Mapper代理对象的FactoryBean
        MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
        // 2. 指定mapper接口和xml文件所在的共同包
        mapperScannerConfigurer.setBasePackage("com.example.mapper");
        
        return mapperScannerConfigurer;
    }

}

4.2.2 配置类方式整合【推荐】

(0) 介绍

不在保留mybatis的外部配置文件(xml),所有配置信息(settings、插件、别名等)全部在声明SqlSessionFactoryBean的代码中指定,数据库信息依然使用DruidDataSource实例替代。

优势:全部使用配置类,避免了XML文件解析效率低问题。

image_A4CC0V4UCR

(1) 3️⃣数据库配置类

创建 config包 中的 DataSourceJavaConfig配置类

@Configuration
@PropertySource("classpath:jdbc.properties")
public class DataSourceJavaConfig {

    @Value("${jdbc.user}")
    private String user;
    @Value("${jdbc.password}")
    private String password;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.driver}")
    private String driver;

    /** 数据库连接池配置 */
    @Bean
    public DataSource dataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUsername(user);
        dataSource.setPassword(password);
        dataSource.setUrl(url);
        dataSource.setDriverClassName(driver);
        return dataSource;
    }
}
(2) 4️⃣MyBatis配置类

创建 config包 中的 MapperJavaConfig配置类

@Configuration
public class MapperJavaConfig {

    /**
     * 将SqlSessionFactoryBean加入IoC容器:指定连接池对象 和 外部配置文件即可
     * @param dataSource 需要注入连接池对象
     * @return 工厂Bean
     */
    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){ // 这里报错没关系,编译后加入IoC容器就可以读取到了
        // 1. 实例化SqlSessionFactory工厂
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        // 2. 设置连接池
        sqlSessionFactoryBean.setDataSource(dataSource);

        // 3. 指定mybatis配置文件的功能,替代xml配置方式
        // 3.1 settings========================================
        /*
        <settings>
            <!-- 开启驼峰式映射-->
            <setting name="mapUnderscoreToCamelCase" value="true"/>
            <!-- 开启logback日志输出-->
            <setting name="logImpl" value="SLF4J"/>
            <!--开启resultMap自动映射 -->
            <setting name="autoMappingBehavior" value="FULL"/>
        </settings>
        */
        // 创建Configuration配置对象-----
        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
        // 开启驼峰式映射
        configuration.setMapUnderscoreToCamelCase(true);
        // 开启logback日志输出
        configuration.setLogImpl(Slf4jImpl.class);
        // 开启resultMap自动映射
        configuration.setAutoMappingBehavior(AutoMappingBehavior.FULL);
        // 放入Configuration配置对象-----
        sqlSessionFactoryBean.setConfiguration(configuration);

        // 3.2 typeAliases========================================
        /*
        <typeAliases>
            <!-- 给包中实体类起别名 -->
            <package name="com.example.pojo"/>
        </typeAliases>
        */
        // 给包中实体类起别名
        sqlSessionFactoryBean.setTypeAliasesPackage("com.example.pojo");

        
        // 3.3 plugins========================================
        /*
        <plugins>
            <plugin interceptor="com.github.pagehelper.PageInterceptor">
                <!--
                    helperDialect:分页插件会自动检测当前的数据库链接,自动选择合适的分页方式。
                    可以配置helperDialect属性来指定分页插件使用哪种方言。配置时,可以使用下面的缩写值:
                    oracle,mysql,mariadb,sqlite,hsqldb,postgresql,db2,sqlserver,informix,h2,sqlserver2012,derby
                 -->
                <property name="helperDialect" value="mysql"/>
            </plugin>
        </plugins>
        */
        // 分页插件配置-----
        // 创建分页插件对象
        PageInterceptor pageInterceptor = new PageIntereptor();
        // 指定分页插件配置
        Properties properties = new Properties();
        properties.setProperty("helperDialect","mysql");
        pageInterceptor.setProperties(properties);
        // 加入plugins中
        sqlSessionFactoryBean.addPlugins(pageInterceptor);

        return sqlSessionFactoryBean;
    }

    /**
     * 将Mapper代理对象加入IoC容器:
     * 配置Mapper实例扫描工厂,配置 <mapper <package 对应接口和mapperxml文件所 在的包
     * @return mapper代理对象
     */
    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer(){
        MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
        // 设置Mapper接口和对应的MapperXML文件所在共同的包
        mapperScannerConfigurer.setBasePackage("com.example.mapper");
        return mapperScannerConfigurer;
    }

}

§5. 容器初始化配置类

创建 config包 中的 WebAppInitializer初始化类,需要继承自 AbstractAnnotationConfigDispatcherServletInitializer 类:

public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    /** 指定root容器对应的配置类 */
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[] {MapperJavaConfig.class, ServiceJavaConfig.class, DataSourceJavaConfig.class };
    }

    /** 指定web容器对应的配置类 */
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] { WebJavaConfig.class };
    }

    /** 指定dispatcherServlet处理路径,通常为 / */
    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }
}

§6. 整合测试

查询所有员工信息,返回对应json数据。

6.1 controller

创建 controller包 下的 EmployeeController类

@Slf4j
@RestController
@RequestMapping("/employee")
public class EmployeeController {

    // 获取业务处对象
    @Autowired
    private EmployeeService employeeService;

    @GetMapping("/find")
    public List<Employee> find(){
        List<Employee> employees = employeeService.findAll();
        log.info("员工数据:{}",employees);
        return employees;
    }
}

6.2 service

创建 service包 下的 EmployeeService接口

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

创建 service/impl包 下的 EmployeeServiceImpl接口实现类

@Service
public class EmployeeServiceImpl implements EmployeeService {

    // 获取Mapper代理对象
    @Autowired
    private EmployeeMapper employeeMapper;

    /**
     * 查询所有员工信息
     */
    @Override
    public List<Employee> findAll() {
        List<Employee> employeeList =  employeeMapper.queryAll();
        return employeeList;
    }
}

6.3 mapper

创建 mapper包 下的 EmployeeMapper接口

public interface EmployeeMapper {
    List<Employee> queryAll();
}

resources目录 下创建 com/example/mapper目录(注意使用/作为分隔符)中的 EmployeeMapper.xml接口实现

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!-- namespace等于mapper接口类的全限定名,这样实现对应 -->
<mapper namespace="com.example.mapper.EmployeeMapper">

    <select id="queryAll" resultType="employee">
        <!-- 已开启驼峰式自动映射 -->
        select * from t_emp;
    </select>

</mapper>

#Part2 任务列表案例

一、案例介绍和接口分析

1. 案例功能预览

image_HyVUgkSLm9

2. 接口分析

2.1 学习计划分页查询

  1. 需求说明:查询全部数据页数据

  2. 请求uri

    schedule/{pageSize}/{currentPage}
    
  3. 请求方式

    get
    
  4. 响应的json

    {
        "code":200,
        "flag":true,
        "data":{
            //本页数据
            data:
            [
                {id:1,title:'学习java',completed:true},
                {id:2,title:'学习html',completed:true},
                {id:3,title:'学习css',completed:true},
                {id:4,title:'学习js',completed:true},
                {id:5,title:'学习vue',completed:true}
            ], 
            //分页参数
            pageSize:5, 	// 每页数据条数,页大小
            total:0 ,   	// 总记录数
            currentPage:1 	// 当前页码
        }
    }
    

2.2 学习计划删除

  1. 需求说明:根据id删除日程

  2. 请求uri

    schedule/{id}
    
  3. 请求方式

    delete
    
  4. 响应的json

    {
        "code":200,
        "flag":true,
        "data":null
    }
    

2.3 学习计划保存

  1. 需求说明:增加日程

  2. 请求uri

    schedule
    
  3. 请求方式

    post
    
  4. 请求体中的JSON

    {
        title: '',
        completed: false
    }
    
  5. 响应的json

    {
        "code":200,
        "flag":true,
        "data":null
    }
    

2.4 学习计划修改

  1. 需求说明:根据id修改数据

  2. 请求uri

    schedule
    
  3. 请求方式

    put
    
  4. 请求体中的JSON

    {
        id: 1,
        title: '',
        completed: false
    }
    
  5. 响应的json

    {
        "code":200,
        "flag":true,
        "data":null
    }
    

二、前端项目

0. 项目结构

image-20240921223728935

1. src/api

1.1 ScheduleApi.js

import request from '../utils/request'
/*
查询全部数据页数据
*/
export let loadByPage = (pageSize = 5,currentPage = 1)=>{
    return  request.get(`schedule/${pageSize}/${currentPage}`)
}

/* 
根据id修改数据
*/
export let updateScheduleById =  (schedule)=>{
    return request.put(`schedule`,schedule)
}

/* 
根据id删除日程
*/
export let removeScheduleById =  (id) =>{
    return request.delete(`schedule/${id}`)
}


/* 
增加日程
*/
export let addSchedule =  (schedule)=>{
    return  request.post(`schedule`,schedule)
}

2. src/assets

2.1 data.json

{
    "list": [
        {"id": 1, "title": "学习 Vue 3", "completed": false},
        {"id": 2, "title": "学习 Vite", "completed": true},
        {"id": 3, "title": "学习 Element Plus", "completed": true},
        {"id": 4, "title": "学习 javaWeb", "completed": true},
        {"id": 5, "title": "学习 javaSSM", "completed": true},
        {"id": 6, "title": "学习 java微服务", "completed": true},
        {"id": 7, "title": "学习 养生护发", "completed": false},
        {"id": 8, "title": "学习 养生护发", "completed": false},
        {"id": 9, "title": "学习 养生护发", "completed": false},
        {"id": 10, "title": "学习 养生护发", "completed": false},
        {"id": 11, "title": "学习 养生护发", "completed": false},
        {"id": 12, "title": "学习 养生护发", "completed": false},
        {"id": 13, "title": "学习 养生护发", "completed": false},
        {"id": 14, "title": "学习 养生护发", "completed": false},
        {"id": 15, "title": "学习 养生护发", "completed": false},
        {"id": 16, "title": "学习 养生护发", "completed": false}
    ]
}

3. src/components

3.1 AddItem.vue

<script  setup type="module">
    //引入store.js 

    import { reactive } from 'vue'
    import { useRouter } from 'vue-router'
    import { addSchedule } from '../api/ScheduleApi'

    //获取路由对象,编程路由
    let rt = useRouter()

    //定义展示标识和接收数据对象
    const editForm = reactive({
        title: '',
        completed: false
    })

    async function addItem() {
        await addSchedule(editForm)
        rt.push('/')
    }

    function returnList() {
        rt.push('/')
    }
</script>


<template>
<div>
    <br>
    <br>

    <el-text class="mx-1" size="large" type="success">添加事项</el-text>

    <br>
    <br>
    <el-form label-width="80px">
        <!-- 绑定数据的key title-->
        <el-form-item label="标题" prop="title">
            <el-input v-model="editForm.title" />
    </el-form-item>
        <el-form-item label="状态" prop="completed">
            <!-- 数据是否完成选项-->
            <el-radio-group v-model="editForm.completed">
                <el-radio :label="true">已完成</el-radio>
                <el-radio :label="false">未完成</el-radio>
    </el-radio-group>
    </el-form-item>
    </el-form>
    <el-row>
        <el-button type="success" @click="addItem" round>添加</el-button>
        <el-button type="info" @click="returnList" round>返回</el-button>
    </el-row>
    </div>
</template>

<style  scoped></style>

3.2 Error.vue

<script setup>
</script>
<template>
    <div>
        <h1>Error</h1>
    </div>
</template>
<style scoped>
</style>

3.3 Error404.vue

<script setup>
</script>
<template>
	<div>
    	<h1>Error404</h1>
    </div>
</template>
<style scoped>
</style>

3.4 List.vue

<script setup type="module">

    import { onMounted, ref, reactive , toRefs } from 'vue'
    import {loadByPage,updateScheduleById,removeScheduleById} from '../api/ScheduleApi'

    let schedule = reactive({
        //假数据!
        //本页数据
        data:[
            {id:1,title:'学习java',completed:true},
            {id:2,title:'学习html',completed:true},
            {id:3,title:'学习css',completed:true},
            {id:4,title:'学习js',completed:true},
            {id:5,title:'学习vue',completed:true}
        ], 
        //分页参数
        pageSize:5, // 每页数据条数 页大小
        total:39 ,   // 总记录数
        currentPage:1 // 当前页码
    })

    //封装数据查询和更新方法
    let showdata = (pageSize=5,currentPage=1)=>{
        loadByPage(pageSize,currentPage).then(
            response  =>{
                //加载完,获取axios数据,并赋值
                console.log(response.data.data)
                Object.assign(schedule, response.data.data)
                console.log(schedule)
            }
        )
    }


    // 1.初始化周期加载数据
    onMounted(()=>{
        showdata()
    })


    // 修改视图是否显示的控制数据
    let editVisible = ref(false)
    // 定义接收数据的对象
    const editForm = reactive({
        id: 0,
        title: '',
        completed: false
    })

    //自定义标签 编辑标签触发方法
    const handleEdit = (row) => {
        // 将要修改的本行数据放在editForm中
        editForm.id=row.id
        editForm.title=row.title
        editForm.completed=row.completed
        // 设置修改对话框显示
        editVisible.value=true
    }

    // 2. 触发数据修改更新
    const handleUpdate =async () => {
        // 发送异步请求,将修改的信息发送给后端
        await updateScheduleById(editForm)
        showdata() //查询新数据
        // 关闭视图
        editVisible.value=false

    }

    //3.触发数据删除
    const handleDelete =async (id,title) => {
        // 删除确认
        if(!confirm(`确定删除-${title}-日程`)){
            return 
        }
        // 异步删除数据
        await removeScheduleById(id)
        //查询新数据
        showdata()
    }

    //4.点击分页触发的方法 page为触发页数
    let handlePageChange = (page)=>{
        //点击分页器,会更新数据的页码和页容量,我们再调用分页查询方法即可!
        showdata(undefined,page)
    }


</script>

<template>
<div class="common-layout">
    <el-container>
        <router-link to="/add"><el-button type="primary">添加</el-button></router-link>
    </el-container>
    <el-container>
        <el-main>
            <!-- 表格 -->
            <el-table :data="schedule.data" style="width: 100%">
                <el-table-column label="编号" width="100">
                    <template #default="scope">
                        <div style="display: flex; align-items: center">
                            <span style="margin-left: 10px">{{ scope.row.id }}</span>
    </div>
</template>
</el-table-column>
<el-table-column label="学习计划" width="180">
    <template #default="scope">
<div style="display: flex; align-items: center">
    <span style="margin-left: 10px">{{ scope.row.title }}</span>
        </div>
    </template>
</el-table-column>
<el-table-column label="是否完成" width="130">
    <template #default="scope">
<div style="display: flex; align-items: center">
    <el-icon></el-icon>
    <span style="margin-left: 10px">{{ scope.row.completed ? '完成' : '未完成' }}</span>
        </div>
    </template>
</el-table-column>
<el-table-column label="其他操作">
    <template #default="scope">
<el-button size="small" @click="handleEdit(scope.row)">编辑</el-button>
<el-button size="small" type="danger" @click="handleDelete(scope.row.id,scope.row.title)">删除</el-button>
    </template>
</el-table-column>
</el-table>
<!-- 分页处理 -->
<br>
<div class="pagination">
    <el-pagination background layout="prev, pager, next" 
                   :total="schedule.total"
                   :current-page="schedule.currentPage" 
                   :page-size="schedule.pageSize" 
                   @current-change="handlePageChange" />
</div>


<!-- 添加编辑dialog框 -->
<!-- :visible.sync="editVisible"  获取一个值,通过控制他的true和false进行展示控制-->
<el-dialog title="编辑任务项" v-model="editVisible" :close-on-click-modal="false">
    <!-- :model='数据对象 应该是一个响应对象'-->
    <el-form label-width="80px">
        <!-- 绑定数据的key title-->
        <el-form-item label="标题" prop="title">
            <el-input v-model="editForm.title" />
        </el-form-item>
        <el-form-item label="状态" prop="completed">
            <!-- 数据是否完成选项-->
            <el-radio-group v-model="editForm.completed">
                <el-radio :label="true">已完成</el-radio>
                <el-radio :label="false">未完成</el-radio>
            </el-radio-group>
        </el-form-item>
    </el-form>
    <div slot="footer">
        <el-button @click="editVisible = false">取 消</el-button>
        <el-button type="primary" @click="handleUpdate">确 定</el-button>
    </div>
</el-dialog>
</el-main>
</el-container>

</div>
</template>

<style scoped></style>

4. src/router

4.1 router.js

//路由配置的js
//1.引入路由依赖
import {createRouter,createWebHashHistory} from 'vue-router'

//引入vue组件
import List from '../components/List.vue'
import AddItem from '../components/AddItem.vue'
import Error404 from '../components/Error404.vue'
import Error from '../components/Error.vue'

//2.创建路由对象,并且设置路由配置 (首页,404,error页)
const router = createRouter({
    history:createWebHashHistory(),
    routes:[
        {
            path:'/',
            component: List
        },
        {
            path: '/404',
            component: Error404
        },
        {
            path:'/error',
            component:Error
        },
        {
            path:'/add',
            component:AddItem
        }
    ]
})

//3.对外暴漏路由对象
export default router

5. src/utils

5.1 request.js

import axios from 'axios'
import router from '../router/router'

// 创建 Axios 实例 设置一些基础属性
const request = axios.create({
    baseURL:'http://localhost:8080/ssm',
    timeout: 10000
})

// 添加请求拦截器
request.interceptors.request.use(
    config => {
        //可以前置设置,例如请求头token等
        config.headers.head1 = 'ceshi-head1'
        return config
    },
    error => {
        return Promise.reject(error)
    }
)
// 添加响应拦截器
request.interceptors.response.use(
    response => {
        return response
    },
    error => {
        if (error.response.status === 404) {
            //到404页面
            router.push('/404');
        }else{
            // 其他情况 返回一个promise    
            return Promise.reject(error)
        }
    }
)
//对外暴漏axios实例。
export default request

6. src/App.vue

<script setup>
</script>
<template>
    <!-- 存放一个展示vue的标签-->
    <router-view></router-view>
</template>
<style scoped>
</style>

7. src/main.js

import { createApp } from 'vue'
import App from './App.vue'
// elementPlus
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
// 路由
import router from './router/router.js'


//  应用组件
let app =createApp(App)
app.use(ElementPlus)
app.use(router)

app.mount('#app')

8. package.json

{
    "name": "vue3-demo2",
    "private": true,
    "version": "0.0.0",
    "type": "module",
    "scripts": {
        "dev": "vite",
        "build": "vite build",
        "preview": "vite preview"
    },
    "dependencies": {
        "axios": "^1.4.0",
        "element-plus": "^2.3.5",
        "pinia": "^2.1.3",
        "vue": "^3.2.47",
        "vue-router": "^4.2.1"
    },
    "devDependencies": {
        "@vitejs/plugin-vue": "^4.1.0",
        "vite": "^4.3.2"
    }
}

9. vite.config.js

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
    plugins: [vue()],
})

三、后端项目

1. 准备工作

1.1 准备数据库

CREATE TABLE schedule (
    id INT NOT NULL AUTO_INCREMENT,
    title VARCHAR(255) NOT NULL,
    completed BOOLEAN NOT NULL,
    PRIMARY KEY (id)
);

INSERT INTO schedule (title, completed)
VALUES
    ('学习java', true),
    ('学习Python', false),
    ('学习C++', true),
    ('学习JavaScript', false),
    ('学习HTML5', true),
    ('学习CSS3', false),
    ('学习Vue.js', true),
    ('学习React', false),
    ('学习Angular', true),
    ('学习Node.js', false),
    ('学习Express', true),
    ('学习Koa', false),
    ('学习MongoDB', true),
    ('学习MySQL', false),
    ('学习Redis', true),
    ('学习Git', false),
    ('学习Docker', true),
    ('学习Kubernetes', false),
    ('学习AWS', true),
    ('学习Azure', false);

1.2 对象实体类

创建 pojo包 下的 Schedule实体类

@Data
public class Schedule {

    private Integer id;
    private String title;
    private Boolean completed;
}

1.3 分页实体类

utils包 下创建 PageBean结果返回工具类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageBean<T> {
    private int currentPage;   // 当前页码
    private int pageSize;      // 每页显示的数据量
    private long total;    	   // 总数据条数
    private List<T> data;      // 当前页的数据集合
}

1.4 日志配置

resources目录 中创建 logback.xml日志文件

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
    <!-- 指定日志输出的位置,ConsoleAppender表示输出到控制台 -->
    <appender name="STDOUT"
              class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <!-- 日志输出的格式 -->
            <!-- 按照顺序分别是:时间、日志级别、线程名称、打印日志的类、日志主体内容、换行 -->
            <pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger] [%msg]%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!-- 设置全局日志级别。日志级别按顺序分别是:TRACE、DEBUG、INFO、WARN、ERROR -->
    <!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志。 -->
    <root level="DEBUG">
        <!-- 指定打印日志的appender,这里通过“STDOUT”引用了前面配置的appender -->
        <appender-ref ref="STDOUT" />
    </root>

    <!-- 根据特殊需求指定局部日志级别,可也是包名或全类名。 -->
    <logger name="com.example.mybatis" level="DEBUG" />

</configuration>

1.5 jdbc配置文件

resources目录 中创建 jdbc.properties数据库连接配置文件

jdbc.user=root
jdbc.password=123456
jdbc.url=jdbc:mysql:///mybatis-example
jdbc.driver=com.mysql.cj.jdbc.Driver

1.6 结果返回工具类

utils包 下创建 R结果返回工具类

public class R {

    private int code = 200; //200成功状态码

    private boolean flag = true; //返回状态

    private Object data;  //返回具体数据

    public static R ok(Object data){
        R r = new R();
        r.data = data;
        return r;
    }

    public static R fail(Object data){
        R r = new R();
        r.code = 500; //错误码
        r.flag = false; //错误状态
        r.data = data;
        return r;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }
}

2. 配置类

创建 config包 中的配置类。

configrelationship

2.1 WebJavaConfig--controller

@Configuration
@EnableWebMvc
@ComponentScan("com.example.controller")
public class WebJavaConfig implements WebMvcConfigurer {

    /** 开启静态资源 */
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable(); 
    }

    /** 配置视图解析器 */
    /*@Override
    public void configureViewResolvers(ViewResolverRegistry registry) { 
        registry.jsp("/WEB-INF/views/", "jsp");
    }*/
    
    /** 配置拦截器 */
    /*@Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new 拦截器类())
            	.addPathPatterns()
            	.excludePathPatterns();
    }*/
}

2.2 ServiceJavaConfig--service

@EnableTransactionManagement
@EnableAspectJAutoProxy
@Configuration
@ComponentScan("com.example.service")
public class ServiceJavaConfig {
    
    @Bean
    public DataSourceTransactionManager transactionManager(DataSource dataSource){
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
    
}

2.3 DataSourceJavaConfig--mapper

@Configuration
@PropertySource("classpath:jdbc.properties")
public class DataSourceJavaConfig {

    @Value("${jdbc.user}")
    private String user;
    @Value("${jdbc.password}")
    private String password;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.driver}")
    private String driver;

    /** 数据库连接池配置 */
    @Bean
    public DataSource dataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUsername(user);
        dataSource.setPassword(password);
        dataSource.setUrl(url);
        dataSource.setDriverClassName(driver);
        return dataSource;
    }
}

2.4 MapperJavaConfig--mapper

@Configuration
public class MapperJavaConfig {

    /**
     * 将SqlSessionFactoryBean加入IoC容器:指定连接池对象 和 外部配置文件即可
     * @param dataSource 需要注入连接池对象
     * @return 工厂Bean
     */
    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){ // 这里报错没关系,编译后加入IoC容器就可以读取到了
        // 1. 实例化SqlSessionFactory工厂
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        // 2. 设置连接池
        sqlSessionFactoryBean.setDataSource(dataSource);

        // 3. 指定mybatis配置文件的功能,替代xml配置方式
        // 3.1 settings========================================
        // 创建Configuration配置对象-----
        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
        // 开启驼峰式映射
        configuration.setMapUnderscoreToCamelCase(true);
        // 开启logback日志输出
        configuration.setLogImpl(Slf4jImpl.class);
        // 开启resultMap自动映射
        configuration.setAutoMappingBehavior(AutoMappingBehavior.FULL);
        // 放入Configuration配置对象-----
        sqlSessionFactoryBean.setConfiguration(configuration);

        // 3.2 typeAliases========================================
        // 给包中实体类起别名
        sqlSessionFactoryBean.setTypeAliasesPackage("com.example.pojo");

        
        // 3.3 plugins========================================
        // 分页插件配置-----
        // 创建分页插件对象
        PageInterceptor pageInterceptor = new PageIntereptor();
        // 指定分页插件配置
        Properties properties = new Properties();
        properties.setProperty("helperDialect","mysql");
        pageInterceptor.setProperties(properties);
        // 加入plugins中
        sqlSessionFactoryBean.addPlugins(pageInterceptor);

        return sqlSessionFactoryBean;
    }

    /**
     * 将Mapper代理对象加入IoC容器:
     * 配置Mapper实例扫描工厂,配置 <mapper <package 对应接口和mapperxml文件所 在的包
     * @return mapper代理对象
     */
    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer(){
        MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
        // 设置Mapper接口和对应的MapperXML文件所在共同的包
        mapperScannerConfigurer.setBasePackage("com.example.mapper");
        return mapperScannerConfigurer;
    }

}

2.5 容器初始化配置类

public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    /** 指定root容器对应的配置类 */
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[] {MapperJavaConfig.class, ServiceJavaConfig.class, DataSourceJavaConfig.class };
    }

    /** 指定web容器对应的配置类 */
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] { WebJavaConfig.class };
    }

    /** 指定dispatcherServlet处理路径,通常为 / */
    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }
}

3. 功能实现

3.1 分页查询

(1) controller

创建 controller包 下的 ScheduleController控制类

@CrossOrigin	// @CrossOrigin 注释在带注释的控制器方法上启用跨源请求
@RequestMapping("schedule")
@RestController
public class ScheduleController {

    @Autowired
    private ScheduleService scheduleService;

    @GetMapping("/{pageSize}/{currentPage}")
    public R showList(@PathVariable(name = "pageSize") int pageSize, @PathVariable(name = "currentPage") int currentPage){
        PageBean<Schedule> pageBean = scheduleService.findByPage(pageSize,currentPage);
        return  R.ok(pageBean);
    }
}    
(2) service

创建 service包 下的 ScheduleService接口

public interface ScheduleService {

    PageBean<Schedule> findByPage(int pageSize, int currentPage);
}

创建 service/impl包 下的 ScheduleServiceImpl接口实现类

@Slf4j
@Service
public class ScheduleServiceImpl  implements ScheduleService {

    @Autowired
    private ScheduleMapper scheduleMapper;

    /**
     * 分页数据查询,返回分页pageBean
     */
    @Override
    public PageBean<Schedule> findByPage(int pageSize, int currentPage) {
        //1.设置分页参数
        PageHelper.startPage(currentPage, pageSize);
        //2.数据库查询
        List<Schedule> list = scheduleMapper.queryPage();
        //3.结果获取
        PageInfo<Schedule> pageInfo = new PageInfo<>(list);
        //4.pageBean封装
        PageBean<Schedule> pageBean = new PageBean<>(pageInfo.getPageNum(), pageInfo.getPageSize(),
                                                     pageInfo.getTotal(), pageInfo.getList());

        log.info("分页查询结果:{}", pageBean);

        return pageBean;
    }

}
(3) mapper

创建 mapper包 下的 EmployeeMapper接口

public interface ScheduleMapper {
    
    List<Schedule> queryPage();
}

resources目录 下创建 com/example/mapper目录(注意使用/作为分隔符)中的 ScheduleMapper.xml接口实现

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.example.mapper.ScheduleMapper">

    <select id="queryPage" resultType="schedule">
        select * from schedule
    </select>
</mapper>    

3.2 计划添加

(1) controller

创建 controller包 下的 ScheduleController控制类

@CrossOrigin
@RequestMapping("schedule")
@RestController
public class ScheduleController {

    @Autowired
    private ScheduleService scheduleService;

    // ...showList()...
    
    @PostMapping
    public R saveSchedule(@RequestBody Schedule schedule){
        scheduleService.saveSchedule(schedule);
        return R.ok(null);
    }
}    
(2) service

创建 service包 下的 ScheduleService接口

public interface ScheduleService {
    
    // ...findByPage()...
    
	void saveSchedule(Schedule schedule);
}

创建 service/impl包 下的 ScheduleServiceImpl接口实现类

@Slf4j
@Service
public class ScheduleServiceImpl  implements ScheduleService {

    @Autowired
    private ScheduleMapper scheduleMapper;

    // ...findByPage()...
    
    
    /**
     * 保存学习计划
     */
    @Override
    public void saveSchedule(Schedule schedule) {
        scheduleMapper.insert(schedule);
    }

}
(3) mapper

创建 mapper包 下的 EmployeeMapper接口

public interface ScheduleMapper {
    
    // ...queryPage()...
    
    void insert(Schedule schedule);
}

resources目录 下创建 com/example/mapper目录(注意使用/作为分隔符)中的 ScheduleMapper.xml接口实现

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.example.mapper.ScheduleMapper">

    <!-- ...select queryPage... -->
    
    <insert id="insert">
        insert into schedule (title, completed)
        values
        (#{title}, #{completed});
    </insert>
    
</mapper>    

3.3 计划删除

(1) controller

创建 controller包 下的 ScheduleController控制类

@CrossOrigin
@RequestMapping("schedule")
@RestController
public class ScheduleController {

    @Autowired
    private ScheduleService scheduleService;

    // ...showList()、removeSchedule()...
    
    @DeleteMapping("/{id}")
    public R removeSchedule(@PathVariable Integer id){
        scheduleService.removeById(id);
        return R.ok(null);
    }
}    
(2) service

创建 service包 下的 ScheduleService接口

public interface ScheduleService {
    
    // ...findByPage()、saveSchedule()...
    
	void removeById(Integer id);
}

创建 service/impl包 下的 ScheduleServiceImpl接口实现类

@Slf4j
@Service
public class ScheduleServiceImpl  implements ScheduleService {

    @Autowired
    private ScheduleMapper scheduleMapper;

    // ...findByPage()、saveSchedule()...
    
    /**
     * 移除学习计划
     */
    @Override
    public void removeById(Integer id) {
        scheduleMapper.delete(id);
    }

}
(3) mapper

创建 mapper包 下的 EmployeeMapper接口

public interface ScheduleMapper {
    
    // ...queryPage()、insert()...
    
    void delete(Integer id);
}

resources目录 下创建 com/example/mapper目录(注意使用/作为分隔符)中的 ScheduleMapper.xml接口实现

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.example.mapper.ScheduleMapper">

    <!-- ...select id="queryPage"、insert id="insert"... -->
    
    <delete id="delete">
        delete from schedule where id = #{id}
    </delete>
    
</mapper>    

3.4 计划修改

(1) controller

创建 controller包 下的 ScheduleController控制类

@CrossOrigin
@RequestMapping("schedule")
@RestController
public class ScheduleController {

    @Autowired
    private ScheduleService scheduleService;

    // ...showList()、removeSchedule()、removeSchedule()...
    
    @PutMapping
    public R changeSchedule(@RequestBody Schedule schedule){
        scheduleService.updateSchedule(schedule);
        return R.ok(null);
    }
}    
(2) service

创建 service包 下的 ScheduleService接口

public interface ScheduleService {
    
    // ...findByPage()、saveSchedule()、removeById()...
    
	void updateSchedule(Schedule schedule)
}

创建 service/impl包 下的 ScheduleServiceImpl接口实现类

@Slf4j
@Service
public class ScheduleServiceImpl  implements ScheduleService {

    @Autowired
    private ScheduleMapper scheduleMapper;

    // ...findByPage()、saveSchedule()、removeById()...
    
    /**
     * 更新学习计划
     *
     * @param schedule
     */
    @Override
    public void updateSchedule(Schedule schedule) {
        scheduleMapper.update(schedule);
    }

}
(3) mapper

创建 mapper包 下的 EmployeeMapper接口

public interface ScheduleMapper {
    
    // ...queryPage()、insert()、delete()...
    
    void update(Schedule schedule);
}

resources目录 下创建 com/example/mapper目录(注意使用/作为分隔符)中的 ScheduleMapper.xml接口实现

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.example.mapper.ScheduleMapper">

    <!-- ...select id="queryPage"、insert id="insert"、delete id="delete"... -->
    
    <update id="update">
        update schedule set title = #{title} , completed = #{completed}
             where id = #{id}
    </update>
    
</mapper>    
posted @ 2024-09-22 14:41  雪与锄  阅读(152)  评论(0)    收藏  举报