1. MyBatis入门前的认知准备
作为Java开发者,当你准备系统学习MyBatis时,首先需要明确它的技术定位。MyBatis是一款半自动化的ORM框架,与Hibernate这类全自动框架不同,它允许开发者对SQL保持完全控制权。这种设计哲学决定了MyBatis更适合需要精细调优SQL性能的场景。
我第一次接触MyBatis是在2014年参与一个电商项目时。当时系统需要处理复杂的商品检索逻辑,Hibernate生成的SQL难以满足性能要求,而原生JDBC又过于繁琐。MyBatis恰好在这两个极端之间找到了平衡点——它用XML或注解配置SQL,既保留了SQL的灵活性,又通过自动映射减少了样板代码。
2. 技术栈依赖与前置知识
2.1 必备基础技能树
在正式学习MyBatis前,你应该已经掌握:
- Java核心语法:特别是泛型、反射、注解等特性,这些是理解框架实现的基础
- JDBC规范:包括DataSource、Connection、PreparedStatement等核心接口的使用
- SQL编写能力:能熟练编写多表关联查询、子查询等复杂SQL语句
- Maven/Gradle:项目依赖管理工具的基本使用
特别提醒:许多初学者直接跳过了JDBC学习,这是非常危险的。我曾见过有开发者连ResultSet都不会遍历就直接用MyBatis,导致出现性能问题后无从排查。
2.2 版本选择建议
当前MyBatis主要维护两个版本线:
- MyBatis 3.5.x:稳定版,适合生产环境
- MyBatis 3.4.x:维护版,只修复关键bug
建议新手从最新3.5.x版本开始学习。要注意的是,MyBatis 3.5+需要JDK 1.8+环境,这与旧版本有区别。
3. 核心架构解析
3.1 运行时组件模型
MyBatis的核心架构由以下几个关键组件构成:
| 组件 | 职责说明 | 生命周期 |
|---|---|---|
| SqlSessionFactory | 全局单例,负责创建SqlSession | 应用启动时创建 |
| SqlSession | 一次数据库会话的抽象 | 请求级别 |
| Executor | SQL执行引擎 | SqlSession内 |
| MappedStatement | 封装SQL和映射规则 | 应用启动时加载 |
| StatementHandler | 处理JDBC Statement | 执行SQL时创建 |
3.2 配置文件体系
MyBatis的配置分为两个层次:
- 全局配置文件(mybatis-config.xml):
xml复制<configuration>
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
<typeAliases>
<typeAlias alias="User" type="com.example.User"/>
</typeAliases>
</configuration>
- Mapper映射文件(UserMapper.xml):
xml复制<mapper namespace="com.example.mapper.UserMapper">
<select id="selectUser" resultType="User">
SELECT * FROM users WHERE id = #{id}
</select>
</mapper>
4. 开发模式对比
4.1 XML配置 vs 注解开发
MyBatis支持两种开发风格:
XML配置方式:
xml复制<!-- UserMapper.xml -->
<select id="findById" resultType="User">
SELECT * FROM user WHERE id = #{id}
</select>
注解方式:
java复制public interface UserMapper {
@Select("SELECT * FROM user WHERE id = #{id}")
User findById(@Param("id") Long id);
}
实际项目中,我建议采用混合模式:简单SQL用注解,复杂SQL用XML。在金融项目中,我们要求所有超过3行的SQL必须使用XML配置,这样可以保持代码可读性。
4.2 动态SQL实践
MyBatis提供了强大的动态SQL能力:
xml复制<select id="findUsers" resultType="User">
SELECT * FROM user
<where>
<if test="name != null">
AND name LIKE #{name}
</if>
<if test="age != null">
AND age = #{age}
</if>
</where>
ORDER BY id
</select>
经验之谈:在2016年处理一个CRM系统时,我们曾遇到一个包含17个可选查询条件的页面。合理使用
<choose>、<when>组合,最终将Mapper代码控制在可维护范围内。
5. 性能优化要点
5.1 一级缓存陷阱
MyBatis的SqlSession级别缓存(一级缓存)经常引发问题:
java复制// 示例:相同查询在同一个SqlSession中只会执行一次
User user1 = sqlSession.selectOne("getUser", 1L); // 实际查询数据库
User user2 = sqlSession.selectOne("getUser", 1L); // 从缓存获取
解决方案:
- 对于需要实时性的查询,可以在Mapper配置中添加
flushCache="true" - 或者在方法调用后手动调用
sqlSession.clearCache()
5.2 批处理优化
MyBatis的批处理有几种实现方式:
java复制// 方式1:使用BatchExecutor
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
try {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
for (User user : users) {
mapper.insert(user);
}
sqlSession.commit();
} finally {
sqlSession.close();
}
// 方式2:使用foreach标签
<insert id="batchInsert">
INSERT INTO user (name, age) VALUES
<foreach collection="list" item="user" separator=",">
(#{user.name}, #{user.age})
</foreach>
</insert>
在批量插入10万条数据的测试中,方式2比方式1快约30%,但消耗更多内存。
6. 扩展机制揭秘
6.1 插件开发实战
MyBatis的插件基于责任链模式实现,可以拦截四大核心对象:
java复制@Intercepts({
@Signature(type= Executor.class,
method="query",
args={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class QueryTimeInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
long start = System.currentTimeMillis();
Object result = invocation.proceed();
long end = System.currentTimeMillis();
System.out.println("执行耗时:" + (end - start) + "ms");
return result;
}
}
在配置文件中注册:
xml复制<plugins>
<plugin interceptor="com.example.QueryTimeInterceptor"/>
</plugins>
6.2 自定义类型处理器
处理枚举类型时特别有用:
java复制public class StatusEnumTypeHandler extends BaseTypeHandler<StatusEnum> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i,
StatusEnum parameter, JdbcType jdbcType) {
ps.setInt(i, parameter.getCode());
}
@Override
public StatusEnum getNullableResult(ResultSet rs, String columnName) {
return StatusEnum.of(rs.getInt(columnName));
}
}
7. 常见坑点排查指南
7.1 参数绑定问题
java复制// 错误示例:参数名不匹配
@Select("SELECT * FROM user WHERE name = #{username}")
User findByName(String name);
// 正确写法1:使用@Param
@Select("SELECT * FROM user WHERE name = #{username}")
User findByName(@Param("username") String name);
// 正确写法2:保持名称一致
@Select("SELECT * FROM user WHERE name = #{name}")
User findByName(String name);
7.2 延迟加载失效
配置延迟加载需要满足:
- 全局配置开启:
xml复制<settings>
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
- 关联查询使用
fetchType="lazy":
xml复制<resultMap id="userWithOrders" type="User">
<collection property="orders" column="id"
select="com.example.mapper.OrderMapper.findByUserId"
fetchType="lazy"/>
</resultMap>
8. 生态整合建议
8.1 与Spring集成
现代项目通常使用MyBatis-Spring整合:
java复制@Configuration
public class MyBatisConfig {
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.setTypeAliasesPackage("com.example.entity");
return factoryBean.getObject();
}
}
8.2 分页插件选型
推荐使用PageHelper:
java复制// 在查询前调用
PageHelper.startPage(1, 10); // 第1页,每页10条
List<User> users = userMapper.selectAll();
// 用PageInfo包装结果
PageInfo<User> pageInfo = new PageInfo<>(users);
在千万级数据表中,务必配合合理的SQL索引使用,我曾见过不加索引的分页查询导致全表扫描。
学习MyBatis的过程就像掌握一门新的语言——开始时觉得SQL写在XML里很别扭,但当你需要优化一个关键查询时,这种显式控制带来的优势就会显现。建议从一个小型项目开始实践,逐步体会它的设计哲学。
