1. 为什么我们需要MyBatis:从JDBC到ORM的进化之路
第一次接触数据库编程时,我像大多数Java开发者一样从JDBC开始。还记得当时写了上百行的代码,仅仅是为了完成一个简单的用户查询——创建Connection、准备Statement、处理ResultSet、捕获各种SQLException,最后还要记得关闭所有资源。这种重复劳动不仅效率低下,而且容易出错,特别是资源泄漏问题经常在深夜让系统崩溃。
这就是ORM(Object-Relational Mapping)框架出现的背景。ORM的核心思想是将数据库表与Java对象建立映射关系,让我们能够用面向对象的方式操作数据库。MyBatis作为半自动化的ORM框架,在灵活性和易用性之间找到了完美平衡点。
关键区别:相比Hibernate这样的全自动ORM,MyBatis不会自动生成SQL,而是让你完全掌控SQL语句,同时帮你处理了所有繁琐的JDBC操作。这种"半自动化"设计让它在复杂查询场景中尤其出色。
我曾在电商项目中同时使用过Hibernate和MyBatis:商品分类这类简单CRUD用Hibernate很合适,但到了需要多表关联、复杂统计的订单报表模块,MyBatis的SQL直接编写优势就显现出来了。特别是当DBA要求优化SQL时,你能清楚地知道最终执行的每条语句是什么。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 搭建MyBatis开发环境:从零开始的配置指南
2.1 基础依赖配置
创建一个Maven项目,在pom.xml中添加核心依赖:
xml复制<dependencies>
<!-- MyBatis核心 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<!-- 数据库驱动(以MySQL为例) -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>
</dependencies>
2.2 核心配置文件详解
创建mybatis-config.xml,这是MyBatis的中枢神经系统:
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>
<!-- 环境配置,默认使用development -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis_test?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!-- 映射文件配置 -->
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
</configuration>
实际项目中我推荐将数据库配置提取到外部properties文件,通过
标签引入,这样不同环境可以轻松切换配置。
2.3 初始化SqlSessionFactory
这是使用MyBatis的入口点:
java复制String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory =
new SqlSessionFactoryBuilder().build(inputStream);
3. SQL映射文件:从XML到Java接口的魔法
3.1 基础CRUD映射
创建一个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.dao.UserMapper">
<resultMap id="userResultMap" type="User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="email" column="email"/>
</resultMap>
<select id="selectUserById" resultMap="userResultMap">
SELECT * FROM users WHERE id = #{id}
</select>
<insert id="insertUser" parameterType="User">
INSERT INTO users(username, email)
VALUES(#{username}, #{email})
</insert>
</mapper>
3.2 动态SQL实战
MyBatis强大的动态SQL能力让复杂查询变得简单:
xml复制<select id="findUsersByCondition" resultMap="userResultMap">
SELECT * FROM users
<where>
<if test="username != null">
AND username LIKE CONCAT('%', #{username}, '%')
</if>
<if test="email != null">
AND email = #{email}
</if>
<if test="ids != null and ids.size() > 0">
AND id IN
<foreach item="id" collection="ids"
open="(" separator="," close=")">
#{id}
</foreach>
</if>
</where>
ORDER BY id
</select>
3.3 注解方式 vs XML方式
MyBatis支持两种映射方式,这是我的使用建议:
java复制public interface UserMapper {
// 简单查询适合用注解
@Select("SELECT * FROM users WHERE id = #{id}")
User selectUserById(int id);
// 复杂查询还是用XML更清晰
List<User> findUsersByCondition(UserQuery query);
}
实际项目中我发现:80%的简单操作可以用注解,但涉及动态SQL、结果映射复杂时,XML的可读性和维护性更好。团队可以约定:CRUD用注解,复杂查询用XML。
4. 高级特性与实战技巧
4.1 插件开发:自定义MyBatis行为
MyBatis的插件机制非常强大,比如实现一个SQL执行时间监控插件:
java复制@Intercepts({
@Signature(type= StatementHandler.class,
method="query",
args={Statement.class, ResultHandler.class})
})
public class SqlCostTimeInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
long start = System.currentTimeMillis();
try {
return invocation.proceed();
} finally {
long cost = System.currentTimeMillis() - start;
StatementHandler handler = (StatementHandler)invocation.getTarget();
System.out.println("SQL执行耗时[" + cost + "ms]: " +
handler.getBoundSql().getSql());
}
}
}
然后在配置文件中注册:
xml复制<plugins>
<plugin interceptor="com.example.plugin.SqlCostTimeInterceptor"/>
</plugins>
4.2 枚举类型处理
MyBatis对枚举的支持很灵活,比如用户状态枚举:
java复制public enum UserStatus {
ACTIVE(1), INACTIVE(0), LOCKED(-1);
private final int code;
// 构造方法、getter省略
}
// 在配置文件中配置类型处理器
<typeHandlers>
<typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler"
javaType="com.example.enums.UserStatus"/>
</typeHandlers>
4.3 分页查询实现
虽然MyBatis本身不提供分页功能,但配合PageHelper插件非常方便:
java复制// 在查询方法前调用
PageHelper.startPage(1, 10); // 第1页,每页10条
List<User> users = userMapper.selectAll();
PageInfo<User> pageInfo = new PageInfo<>(users);
5. 生产环境中的最佳实践
5.1 SQL注入防护
虽然MyBatis的#{}方式已经能防止大部分SQL注入,但仍需注意:
xml复制<!-- 错误示范:使用${}有注入风险 -->
<select id="findByOrder" resultMap="userResultMap">
SELECT * FROM users ORDER BY ${columnName}
</select>
<!-- 正确做法:使用白名单校验 -->
<select id="findByOrder" resultMap="userResultMap">
SELECT * FROM users ORDER BY
<choose>
<when test="columnName == 'name'">username</when>
<when test="columnName == 'email'">email</when>
<otherwise>id</otherwise>
</choose>
</select>
5.2 性能优化建议
- 批量操作:使用
<foreach>批量插入时,MySQL建议每批500条左右 - 二级缓存:在mapper.xml中添加
<cache/>声明开启,但要注意缓存一致性 - 延迟加载:复杂对象关联时使用
fetchType="lazy"
5.3 常见问题排查
问题1:XML中的特殊符号(如<>)需要转义吗?
在CDATA区中不需要:
<![CDATA[ SELECT * FROM table WHERE id < 100 ]]>。其他区域需要转义:<表示<,>表示>。
问题2:接口方法与XML映射如何绑定?
通过namespace+id对应,如namespace="com.example.UserMapper"接口中的selectById方法对应XML中的<select id="selectById">。
问题3:如何查看实际执行的SQL?
安装MyBatis Log Free插件,或者在日志配置中添加:
properties复制logging.level.org.mybatis=debug
在电商项目中,我们曾遇到一个性能问题:用户列表查询突然变慢。通过MyBatis日志发现生成的SQL包含了一个不必要的多表关联。原来是XML中一个动态SQL条件判断错误,导致总是关联了订单表。这个经历让我深刻体会到MyBatis透明性的价值——你能清楚地知道每条SQL是怎么生成的。
