1. MyBatis CRUD操作全流程解析
作为一名长期使用MyBatis的开发老兵,今天想和大家系统梳理下MyBatis中最核心的CRUD操作实现方式。很多人刚接触MyBatis时容易陷入"会用但不明原理"的状态,这里我会结合多年实战经验,从接口定义到SQL映射再到测试验证,带你完整走一遍开发流程,并分享那些官方文档里不会写的实操细节。
1.1 接口设计与方法定义规范
在MyBatis中,我们首先需要在Mapper接口中声明操作方法。以用户管理为例,基础的UserMapper接口应包含以下典型方法:
java复制public interface UserMapper {
// 模糊查询(参数绑定方式)
List<User> getUserLike(String value);
// 全量查询
List<User> getUserList();
// ID精确查询(基本类型参数)
User getUserById(int id);
// ID精确查询(Map参数)
User getUserById2(Map<String,Object> map);
// 新增用户(对象参数)
int addUser(User user);
// 新增用户(Map参数)
int addUser2(Map<String,Object> map);
// 更新用户
int updateUser(User user);
// 删除用户
int deleteUser(int id);
}
这里有几个设计要点需要注意:
- 查询方法返回类型分为单实体(User)和集合(List
),根据业务需求选择 - 参数传递支持基本类型、实体对象和Map三种形式
- 增删改方法返回int类型,表示受影响的行数
实际开发中建议保持接口方法名与SQL语句ID的命名一致性,如getXXX对应查询,add/insert对应插入等,这对后期维护非常重要
1.2 SQL映射文件编写技巧
在UserMapper.xml中,我们需要为每个接口方法配置对应的SQL语句:
xml复制<mapper namespace="com.findx.dao.UserMapper">
<!-- 结果集映射建议使用resultMap替代resultType -->
<select id="getUserList" resultType="User">
SELECT * FROM user
</select>
<!-- 参数类型可省略,MyBatis会自动推断 -->
<select id="getUserById" resultType="User">
SELECT * FROM user WHERE id = #{id}
</select>
<!-- Map参数查询示例 -->
<select id="getUserById2" resultType="User">
SELECT * FROM user WHERE id = #{useid}
</select>
<!-- 模糊查询防注入写法 -->
<select id="getUserLike" resultType="User">
SELECT * FROM user WHERE name LIKE CONCAT('%',#{value},'%')
</select>
<!-- 插入语句返回主键的两种方式 -->
<insert id="addUser" useGeneratedKeys="true" keyProperty="id">
INSERT INTO user(name, pwd) VALUES(#{name}, #{pwd})
</insert>
</mapper>
关键技巧说明:
- 使用
#{}占位符能有效防止SQL注入,切勿使用字符串拼接 - 模糊查询推荐使用CONCAT函数而非直接传
%值 - 插入操作建议配置useGeneratedKeys获取自增主键
- 复杂查询建议定义专门的resultMap而非使用resultType
1.3 事务控制与测试验证
测试环节需要特别注意事务控制问题:
java复制public class UserDaoTest {
@Test
public void testAddUser() {
// 获取SqlSession时设置自动提交(不推荐)
// SqlSession sqlSession = sqlSessionFactory.openSession(true);
try(SqlSession sqlSession = MybatisUtils.getSqlSession()) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int affected = mapper.addUser(new User("test", "123"));
// 必须显式提交
sqlSession.commit();
System.out.println("插入成功,影响行数:" + affected);
} catch (Exception e) {
// 异常时回滚
sqlSession.rollback();
}
}
}
事务处理的几个黄金法则:
- 增删改操作必须显式调用commit()
- 推荐使用try-with-resources确保资源释放
- 异常处理中要进行rollback()
- 考虑使用Spring声明式事务简化管理
2. MyBatis配置深度解析
2.1 多环境配置与属性管理
在mybatis-config.xml中,我们可以通过properties元素管理数据库配置:
xml复制<!-- 优先加载外部properties -->
<properties resource="db.properties">
<!-- 内置属性作为默认值 -->
<property name="jdbc.timeout" value="30"/>
</properties>
<environments default="dev">
<environment id="dev">
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}?connectTimeout=${jdbc.timeout}"/>
</dataSource>
</environment>
<!-- 生产环境配置 -->
<environment id="prod">
...
</environment>
</environments>
属性加载的优先级规则:
- 首先读取properties子元素内容
- 然后加载resource/url指定的外部文件
- 最后读取作为方法参数传递的属性
- 后加载的会覆盖先加载的同名属性
2.2 类型别名优化方案
MyBatis提供了两种类型别名配置方式:
xml复制<typeAliases>
<!-- 单个别名定义 -->
<typeAlias alias="User" type="com.findx.pojo.User"/>
<!-- 包扫描方式(推荐) -->
<package name="com.findx.pojo"/>
</typeAliases>
使用包扫描时,MyBatis会:
- 扫描指定包下的所有类
- 使用类名(首字母小写)作为别名
- 可通过@Alias注解自定义别名
2.3 映射器注册的四种方式
xml复制<mappers>
<!-- 1. 资源文件引用(最常用) -->
<mapper resource="mapper/UserMapper.xml"/>
<!-- 2. 类注册(需接口与XML同名同包) -->
<mapper class="com.findx.dao.UserMapper"/>
<!-- 3. 包扫描(需接口与XML同名同包) -->
<package name="com.findx.dao"/>
<!-- 4. 绝对路径(不推荐) -->
<mapper url="file:///var/mappers/UserMapper.xml"/>
</mappers>
实际开发中的选择建议:
- Maven项目推荐使用包扫描方式
- 传统项目可使用资源文件引用
- 确保XML与接口在相同包路径下
- 注意检查XML文件是否被正确编译到target目录
3. 核心对象生命周期管理
3.1 对象生命周期图解

各组件的作用域说明:
- SqlSessionFactoryBuilder:方法级别,构建后即可丢弃
- SqlSessionFactory:应用级别,应保持单例
- SqlSession:请求/方法级别,非线程安全
- Mapper实例:方法级别,从SqlSession中获取
3.2 SqlSession最佳实践
java复制// 正确使用方式示例
try (SqlSession session = sqlSessionFactory.openSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);
// 业务操作
session.commit();
}
// 典型错误用法(会导致连接泄漏)
SqlSession session = sqlSessionFactory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
// 忘记关闭session
使用SqlSession的注意事项:
- 必须确保finally块中关闭或使用try-with-resources
- 不要将SqlSession实例放在静态字段中
- 不要将SqlSession实例传递给多个线程
- 每个线程应该有它自己的SqlSession实例
4. 实战问题排查指南
4.1 常见异常解决方案
| 异常现象 | 可能原因 | 解决方案 |
|---|---|---|
| Invalid bound statement | Mapper未注册或XML路径错误 | 检查mappers配置和XML文件位置 |
| Parameter not found | 参数名不匹配 | 使用@Param注解或检查# |
| Transaction not active | 忘记commit | 增删改后调用sqlSession.commit() |
| TooManyResultsException | 查询返回多行但接口声明单个对象 | 修改接口返回类型为List |
4.2 性能优化建议
- 批量操作优化:
java复制// 批量插入示例
<insert id="batchInsert">
INSERT INTO user(name) VALUES
<foreach collection="list" item="u" separator=",">
(#{u.name})
</foreach>
</insert>
- 二级缓存配置:
xml复制<!-- 在SQL映射文件中启用 -->
<cache eviction="LRU" flushInterval="60000" size="512"/>
- 延迟加载策略:
xml复制<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
- 日志输出控制:
properties复制# log4j.properties
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
在项目开发中,我特别推荐使用MyBatis-Plus来简化CRUD操作,它能减少约80%的样板代码。但要注意,深入理解原生MyBatis的工作原理对于解决复杂问题仍然必不可少。当遇到性能瓶颈时,SQL日志分析和执行计划查看应该是你的第一选择。