目录
五、MyBatis 与 Spring Boot 集成:现代开发标配
开篇:从 "复制粘贴工程师" 到 "SQL 架构师"
曾经有个程序员,每天的工作就是复制粘贴 JDBC 代码:加载驱动、获取连接、创建 Statement、处理 ResultSet... 直到有一天,他发现自己写的重复代码能绕地球三圈。当他濒临崩溃时,前辈扔给他一个 MyBatis 教程,从此他的世界清净了 —— 这不是神话,而是无数 Java 开发者的真实经历。
MyBatis 就像持久层开发的 "翻译官",让 Java 对象和数据库表能顺畅沟通,同时又不剥夺开发者对 SQL 的控制权。今天我们就来系统学习这个框架,保证专业术语一个不少,段子也管够。
一、MyBatis 核心架构:框架界的 "精密仪器"
MyBatis 的架构设计堪称教科书级,每个组件都有明确职责,如同精密手表的齿轮般协同工作:
SqlSessionFactory:会话工厂,基于配置文件构建,整个应用生命周期中只需要一个实例。你可以把它理解为数据库连接的 "总调度中心",负责生产 SqlSession。
SqlSession:数据库会话对象,相当于 JDBC 中的 Connection,但功能更强大。它是线程不安全的,就像一次性手套,用完就得扔(关闭)。
Executor:执行器,MyBatis 的 "心脏",负责 SQL 语句的执行和缓存管理。默认提供三种实现:SimpleExecutor(简单执行器)、ReuseExecutor(重用预处理语句)、BatchExecutor(批量执行器)。
StatementHandler:语句处理器,负责与 JDBC 的 Statement 交互,设置参数、执行 SQL、处理结果集。
ParameterHandler:参数处理器,将 Java 参数转换为 SQL 语句中的参数,就像给 SQL"填空" 的工具。
ResultSetHandler:结果集处理器,将 JDBC 的 ResultSet 转换为 Java 对象,完成 ORM 的核心映射工作。
Mapper 接口:数据访问接口,定义 CRUD 方法,无需实现类 —— 这大概是所有程序员梦寐以求的 "只说不做" 模式。
Mapper 映射器:将 Mapper 接口方法与 SQL 语句关联的组件,可通过 XML 或注解实现。
这些组件的协作流程就像医院看病:SqlSessionFactory 是挂号处,SqlSession 是导诊护士,Executor 是主治医生,StatementHandler 是检查仪器,ParameterHandler 是病历填写员,ResultSetHandler 是化验结果解读员,Mapper 接口则是就诊科室。
二、MyBatis 实战指南:从配置到 CRUD
2.1 环境搭建:给框架安个家
Maven 依赖配置:
xml
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.10</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
就像盖房子要先打地基,使用 MyBatis 第一步就是把必要的依赖加进来。
2.2 核心配置:MyBatis 的 "家规"
全局配置文件 (mybatis-config.xml):
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>
<!-- 引入外部属性文件,实现配置解耦 -->
<properties resource="db.properties"/>
<!-- 全局设置,MyBatis的"总开关" -->
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/> <!-- 下划线转驼峰命名 -->
<setting name="cacheEnabled" value="true"/> <!-- 开启二级缓存 -->
<setting name="lazyLoadingEnabled" value="true"/> <!-- 开启延迟加载 -->
<setting name="aggressiveLazyLoading" value="false"/> <!-- 关闭积极加载 -->
</settings>
<!-- 类型别名,简化类名书写 -->
<typeAliases>
<package name="com.example.entity"/> <!-- 批量扫描,别名为类名小写 -->
</typeAliases>
<!-- 类型处理器,处理Java类型与JDBC类型映射 -->
<typeHandlers>
<package name="com.example.handler"/>
</typeHandlers>
<!-- 数据库环境配置 -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/> <!-- JDBC事务管理 -->
<dataSource type="POOLED"> <!-- 池化数据源 -->
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<!-- 注册映射器 -->
<mappers>
<package name="com.example.mapper"/>
</mappers>
</configuration>
这个配置文件就像 MyBatis 的 "使用说明书",告诉框架如何连接数据库、如何处理映射关系。
2.3 核心组件实现
1. 实体类 (User.java):
public class User {
private Integer id;
private String userName;
private Integer age;
private LocalDateTime createTime;
// 构造方法、getter、setter、toString
}
实体类是 Java 世界与数据库世界的 "翻译官",每个属性对应数据库表的一个字段。
2. Mapper 接口 (UserMapper.java):
public interface UserMapper {
/**
* 根据ID查询用户
* @param id 用户ID
* @return 用户对象
*/
User selectById(Integer id);
/**
* 查询所有用户
* @return 用户列表
*/
List<User> selectAll();
/**
* 新增用户
* @param user 用户对象
* @return 影响行数
*/
int insert(User user);
/**
* 更新用户
* @param user 用户对象
* @return 影响行数
*/
int update(User user);
/**
* 删除用户
* @param id 用户ID
* @return 影响行数
*/
int delete(Integer id);
}
Mapper 接口定义了数据访问的 "契约",明确了可以对数据库做哪些操作。
3. Mapper 映射文件 (UserMapper.xml):
xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
<!-- 结果映射,处理复杂映射关系 -->
<resultMap id="UserResultMap" type="User">
<id column="id" property="id"/>
<result column="user_name" property="userName"/>
<result column="age" property="age"/>
<result column="create_time" property="createTime"/>
</resultMap>
<select id="selectById" parameterType="int" resultMap="UserResultMap">
SELECT id, user_name, age, create_time
FROM user
WHERE id = #{id}
</select>
<select id="selectAll" resultMap="UserResultMap">
SELECT id, user_name, age, create_time
FROM user
</select>
<insert id="insert" parameterType="User"
useGeneratedKeys="true" keyProperty="id">
INSERT INTO user(user_name, age, create_time)
VALUES(#{userName}, #{age}, #{createTime})
</insert>
<update id="update" parameterType="User">
UPDATE user
SET user_name = #{userName},
age = #{age},
create_time = #{createTime}
WHERE id = #{id}
</update>
<delete id="delete" parameterType="int">
DELETE FROM user WHERE id = #{id}
</delete>
</mapper>
映射文件是 SQL 语句的 "栖息地",通过 namespace 和 id 与 Mapper 接口方法绑定,实现了 SQL 与 Java 代码的解耦。
2.4 执行流程:让 MyBatis 跑起来
public class MyBatisExecutor {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
// 1. 读取配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 2. 创建SqlSessionFactory
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
// 3. 获取SqlSession
try (SqlSession session = sqlSessionFactory.openSession()) {
// 4. 获取Mapper代理对象
UserMapper userMapper = session.getMapper(UserMapper.class);
// 5. 执行查询操作
User user = userMapper.selectById(1);
System.out.println("查询结果: " + user);
// 6. 执行插入操作
User newUser = new User();
newUser.setUserName("test");
newUser.setAge(25);
newUser.setCreateTime(LocalDateTime.now());
int insertCount = userMapper.insert(newUser);
System.out.println("插入行数: " + insertCount);
// 7. 提交事务
session.commit();
}
}
}
这个流程就像开车:读取配置文件是检查车辆,创建 SqlSessionFactory 是启动发动机,获取 SqlSession 是挂挡,获取 Mapper 是握紧方向盘,执行操作是踩油门,提交事务是到达目的地。
2.5 动态 SQL:MyBatis 的 "智能拼图"
动态 SQL 是 MyBatis 的 "杀手锏",能根据条件自动拼接 SQL 语句,避免了手动拼接的繁琐和错误:
xml
<select id="selectByCondition" parameterType="map" resultMap="UserResultMap">
SELECT id, user_name, age, create_time
FROM user
<where>
<if test="userName != null and userName != ''">
AND user_name LIKE CONCAT('%', #{userName}, '%')
</if>
<if test="age != null">
AND age = #{age}
</if>
<if test="startTime != null">
AND create_time >= #{startTime}
</if>
<if test="endTime != null">
AND create_time <= #{endTime}
</if>
</where>
<choose>
<when test="orderBy != null">
ORDER BY ${orderBy}
</when>
<otherwise>
ORDER BY create_time DESC
</otherwise>
</choose>
</select>
这段代码展示了动态 SQL 的常用标签:<if>
用于条件判断,<where>
自动处理 AND/OR 前缀,<choose>
实现分支选择。有了这些标签,再也不用写 "WHERE 1=1" 这样的蹩脚代码了。
三、常见错误及解决方案:踩坑指南
3.1 配置错误:框架的 "启动失败"
错误 1:org.apache.ibatis.binding.BindingException: Type interface com.example.mapper.UserMapper is not known to the MapperRegistry.
这就像你去餐厅吃饭,服务员说 "没这个菜单",典型的 Mapper 未注册问题。
解决方案:
- 检查 mybatis-config.xml 的
<mappers>
配置,确保 Mapper 被正确注册 - 确认 Mapper 接口与 XML 文件在同一包下且名称相同
- Spring Boot 环境下,在接口上添加
@Mapper
注解或在启动类添加@MapperScan
错误 2:java.io.IOException: Could not find resource mybatis-config.xml
配置文件玩 "捉迷藏",框架找不到它。
解决方案:
- 确认配置文件放在 src/main/resources 目录下
- 检查文件名是否拼写正确(大小写敏感)
- Maven 项目需确保资源文件被正确打包,可在 pom.xml 中添加:
xml
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
</resource>
</resources>
</build>
3.2 映射错误:属性与字段的 "沟通障碍"
错误 3:org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'userName' in 'class java.lang.String'
参数名与映射文件中的表达式不匹配,就像你叫 "张三",别人却喊 "李四"。
解决方案:
- 单参数时使用
@Param
注解指定参数名:
User selectByUserName(@Param("userName") String name);
- 将参数封装到 Map 或实体类中传递
错误 4:Column 'user_name' not found
数据库字段与 Java 属性名称不匹配,典型的 "鸡同鸭讲" 问题。
解决方案:
- 开启下划线转驼峰自动映射:
xml
<setting name="mapUnderscoreToCamelCase" value="true"/>
- 使用
<resultMap>
手动配置映射关系:
xml
<resultMap id="UserResultMap" type="User">
<result column="user_name" property="userName"/>
</resultMap>
3.3 事务问题:数据的 "来去匆匆"
错误 5:执行 insert 后数据库无记录,但无异常抛出
这就像你在 ATM 取了钱,卡上扣了但钱没出来 —— 典型的事务未提交问题。
解决方案:
- 手动提交事务:
session.commit()
- 打开自动提交:
sqlSessionFactory.openSession(true)
- Spring 环境下使用
@Transactional
注解管理事务
3.4 缓存问题:数据的 "时空错乱"
错误 6:更新数据后,查询结果仍为旧数据
一级缓存在作祟,就像你看的还是昨天的报纸。
解决方案:
- 执行增删改操作后,一级缓存会自动失效
- 手动清除缓存:
session.clearCache()
- 对于需要实时数据的查询,可使用
flushCache="true"
强制刷新缓存:
xml
<select id="selectById" flushCache="true" ...>
四、MyBatis 的优缺点:框架界的 "性格分析"
优点:
- SQL 可控性高:允许手写 SQL,便于优化性能,适合复杂查询场景 —— 就像手动挡汽车,给你完全的驾驶控制权
- 灵活性强:动态 SQL 能根据条件灵活生成 SQL,应对多变的业务需求
- 学习成本低:相比 Hibernate,MyBatis 更简单直观,上手快
- 轻量级:核心 jar 包小巧,对项目侵入性低,容易集成
- 缓存机制:提供一级缓存和二级缓存,减少数据库访问,提高性能
- 支持存储过程:对存储过程有良好支持,适合复杂业务逻辑
缺点:
- SQL 编写工作量大:相比全自动 ORM 框架,需要手动编写大量 SQL
- 数据库移植性差:SQL 语句与具体数据库绑定,更换数据库时需修改 SQL
- 代码维护成本高:当表结构变更时,需要同步修改实体类和映射文件
- 缺少自动化 DDL 支持:不提供自动生成表结构的功能,需手动维护
五、MyBatis 与 Spring Boot 集成:现代开发标配
在实际开发中,MyBatis 常与 Spring Boot 结合使用,大幅简化配置:
1. 添加依赖:
xml
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
2. 配置 application.yml:
yaml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/test
username: root
password: root
mybatis:
mapper-locations: classpath:mappers/*.xml
type-aliases-package: com.example.entity
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印SQL日志
3. 使用 Mapper:
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public User getUserById(Integer id) {
return userMapper.selectById(id);
}
}
这种集成方式就像给 MyBatis 装上了 "自动驾驶系统",让开发效率飙升。
结语:为什么 MyBatis 能成为持久层框架的常青树
MyBatis 的成功并非偶然,它在 "全自动 ORM" 和 "原生 JDBC" 之间找到了完美平衡点 —— 既消除了 JDBC 的冗余代码,又保留了开发者对 SQL 的控制权。这种 "半自动" 的特性,让它在需要精细优化 SQL 的场景中大放异彩。
随着 MyBatis-Plus 等增强工具的出现,MyBatis 的开发效率短板也得到了弥补。它就像一把锋利的瑞士军刀,既可以应对简单的日常任务,也能处理复杂的专业场景。
对于 Java 开发者而言,掌握 MyBatis 不仅是一项基本技能,更是理解 ORM 思想和数据库优化的重要途径。毕竟,能优雅地操作数据库,是每个后端开发者的必备素养。