1. 课程导入与工程环境搭建

企业级 Java 应用的数据访问层,长期面临两个核心诉求:一是把数据库操作从业务代码中解耦出来,二是让事务、连接、异常这些横切关注点得到统一治理。Spring 与 MyBatis 的组合正是围绕这两点形成的成熟方案。理解它们各自的角色定位,是搭建工程环境之前必须先建立的心智模型。

一、Spring 与 MyBatis 的角色分工

MyBatis 是一款半自动的持久层框架,它把 SQL 的控制权交还给开发者,同时负责参数映射、结果集映射、JDBC 资源释放等重复劳动。它的核心对象是 SqlSessionFactory、SqlSession 与 Mapper 接口,其中 Mapper 接口通过动态代理把方法调用翻译成具体的 SQL 执行。

Spring 则承担容器与基础设施的角色。它通过 IoC 容器管理 DataSource、SqlSessionFactory、Mapper 代理对象等 Bean 的生命周期;通过 DataSourceTransactionManager 提供声明式事务;通过 AOP 把事务、日志等横切逻辑织入 Service 方法。换句话说,MyBatis 解决“SQL 怎么写、结果怎么映射”,Spring 解决“对象谁来管、事务谁来控、资源谁来释放”。

两者结合后,业务代码只需注入 Mapper 接口并调用方法,事务边界由 @Transactional 声明,连接由连接池统一分配。这种分工使得数据访问层既保留了 SQL 的可控性,又获得了企业级的事务与资源管理能力。

Spring 与 MyBatis 在企业数据访问中的协作关系 Spring 容器(基础设施) DataSource HikariCP 连接池 · 连接复用 SqlSessionFactoryBean 构建 SqlSessionFactory DataSourceTransactionManager 声明式事务 · 提交/回滚 MapperScannerConfigurer 扫描 @Mapper · 注册代理 Bean IoC 容器统一管理 Bean 生命周期 MyBatis(持久层框架) SqlSessionFactory 全局配置 · 环境 · 映射注册 SqlSession 一次会话 · 执行 SQL · 事务边界 Mapper 接口 + XML/注解 动态代理 · 参数映射 · 结果映射 Executor / StatementHandler SQL 解析 · 参数设置 · 结果集处理 SQL 可控 · 映射灵活 · 半自动化 注入 代理 Service 层:@Transactional 声明事务边界 → 调用 Mapper 接口方法

二、Maven 多模块工程结构

企业项目通常不会把所有代码堆在一个模块里。推荐按职责拆分为父工程加若干子模块,父工程只做依赖版本与插件管理,子模块各自承担明确职责。一个典型结构如下:

enterprise-data-access/
├── pom.xml                    (父工程:dependencyManagement 统一版本)
├── eda-common/                (通用工具、统一响应、异常定义)
│   └── pom.xml
├── eda-dao/                   (实体、Mapper 接口、XML 映射文件)
│   └── pom.xml
├── eda-service/               (业务逻辑、事务声明)
│   └── pom.xml
└── eda-web/                   (Controller、启动类、配置文件)
    └── pom.xml

父工程的 packaging 必须是 pom,并在 dependencyManagement 中锁定 Spring Boot、MyBatis Starter、MySQL 驱动等版本。子模块通过 <parent> 继承父工程,引用依赖时无需再写版本号,从而避免多模块间版本漂移。模块依赖方向应保持单向:web 依赖 service,service 依赖 dao,dao 依赖 common,禁止反向依赖。

三、依赖版本管理与 Spring Boot 整合起步依赖

Spring Boot 通过 spring-boot-dependencies 这个 BOM 统一管理了大量常用依赖的版本。整合 MyBatis 时,官方推荐使用 mybatis-spring-boot-starter,它内部已经传递引入了 mybatis、mybatis-spring 以及 spring-boot-starter-jdbc。父工程中应这样声明:

<properties>
    <spring-boot.version>3.2.5</spring-boot.version>
    <mybatis-spring-boot.version>3.0.3</mybatis-spring-boot.version>
    <mysql.version>8.0.33</mysql.version>
</properties>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring-boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>${mybatis-spring-boot.version}</version>
        </dependency>
    </dependencies>
</dependencyManagement>

这里有一个容易踩的坑:mybatis-spring-boot-starter 的版本必须与 Spring Boot 大版本匹配。Spring Boot 3.x 要求 MyBatis Starter 3.x,因为 Spring Boot 3 基于 Jakarta EE 9+,包名已从 javax.* 迁移到 jakarta.*。若在 Spring Boot 3 项目中误用 2.x 的 Starter,启动时会因找不到 javax.sql.DataSource 相关类而失败。

四、自动配置与最小可运行验证

引入 Starter 后,Spring Boot 的自动配置会读取 application.yml 中的数据源信息,自动创建 DataSource、SqlSessionFactory 和 SqlSessionTemplate。开发者只需在启动类上添加 @MapperScan 指定 Mapper 接口所在包,或在每个 Mapper 接口上标注 @Mapper。

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/eda_demo?useSSL=false&serverTimezone=Asia/Shanghai
    username: eda_user
    password: eda_pass_2024
    driver-class-name: com.mysql.cj.jdbc.Driver
    hikari:
      maximum-pool-size: 10
      minimum-idle: 2

mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.example.eda.dao.entity
  configuration:
    map-underscore-to-camel-case: true

验证环境是否搭建成功,最直接的方式是写一个最小测试:在 eda-dao 中定义一个 UserMapper 接口,声明 User selectById(Long id),在 XML 中编写对应查询,然后在测试类中注入该 Mapper 并调用。若返回对象字段与数据库列正确映射,说明数据源、SqlSessionFactory、Mapper 代理、结果映射四条链路全部打通。若启动报 Failed to configure a DataSource,通常是 application.yml 未被加载或 URL 拼写错误;若报 Invalid bound statement,则多半是 mapper-locations 路径不匹配或 XML 的 namespace 与接口全限定名不一致。

建议在父工程中显式声明 maven-compiler-plugin 的 source 与 target 为 17(与 Spring Boot 3 要求一致),并开启 -parameters 编译参数,否则 MyBatis 在按参数名映射时可能抛出 Parameter 'xxx' not found。