1. Mybatis框架核心特性解析
作为Java生态中最主流的ORM框架之一,Mybatis通过XML或注解的方式将SQL与业务代码解耦。我在实际项目中最常使用的几个核心特性包括:
- 动态SQL:通过
<if>、<choose>、<foreach>等标签实现条件拼接。例如处理多条件查询时,可以避免在Java代码中拼接SQL字符串:
xml复制<select id="findUsers" resultType="User">
SELECT * FROM users
<where>
<if test="name != null">
AND name like #{name}
</if>
<if test="email != null">
AND email = #{email}
</if>
</where>
</select>
- 结果集映射:支持通过
<resultMap>定义复杂的对象关系映射。遇到过一对多查询时,这种映射方式比JPA的关联查询更直观可控:
xml复制<resultMap id="blogResultMap" type="Blog">
<id property="id" column="blog_id"/>
<result property="title" column="blog_title"/>
<collection property="posts" ofType="Post">
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
</collection>
</resultMap>
- 插件机制:通过Interceptor接口可以拦截Executor、StatementHandler等核心组件。我们团队就基于此实现了分页插件和SQL执行耗时监控。
提示:Mybatis的SQLSession是线程不安全的,最佳实践是在方法内创建使用后立即关闭,或者通过SqlSessionTemplate配合Spring事务管理。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Mybatis与JDBC、Hibernate的对比选型
在技术选型时,我们通常会从以下几个维度进行对比:
| 特性 | JDBC | Hibernate | Mybatis |
|---|---|---|---|
| SQL控制力 | 完全控制 | 自动生成 | 完全控制 |
| 学习曲线 | 高 | 中 | 中 |
| 性能优化空间 | 极大 | 有限 | 较大 |
| 缓存机制 | 需手动实现 | 完善 | 二级缓存需配置 |
| 适合场景 | 极致性能需求 | 快速CRUD开发 | 复杂SQL项目 |
从我的项目经验来看,当遇到以下情况时会优先选择Mybatis:
- 需要执行复杂SQL(如多表关联、窗口函数等)
- 已有现成的SQL需要移植到Java项目
- 需要精细控制SQL性能优化
- 项目需要从JDBC逐步演进到ORM框架
3. Mybatis-Plus的核心增强功能
Mybatis-Plus在兼容原生Mybatis的基础上,提供了诸多开箱即用的功能:
3.1 条件构造器
通过QueryWrapper和LambdaQueryWrapper可以流畅地构建查询条件:
java复制// 查询年龄大于18且姓名包含"张"的记录
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.gt(User::getAge, 18)
.like(User::getName, "张");
List<User> users = userMapper.selectList(wrapper);
这种方式比Mybatis的动态SQL更符合Java开发者的编码习惯,而且编译时就能发现字段引用错误。
3.2 代码生成器
通过AutoGenerator可以快速生成Entity、Mapper、Service等层代码:
java复制AutoGenerator generator = new AutoGenerator();
generator.setDataSource(dataSourceConfig)
.setGlobalConfig(globalConfig)
.setPackageInfo(packageConfig)
.setStrategy(strategyConfig)
.execute();
我们团队将其集成在Maven插件中,每次修改表结构后运行mvn mybatis-plus:generate即可同步更新所有相关代码。
3.3 分页插件
配置简单且支持多种数据库:
java复制@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
使用时分页参数会自动注入到SQL中:
java复制Page<User> page = new Page<>(1, 10);
Page<User> result = userMapper.selectPage(page, null);
4. 生产环境中的实战经验
4.1 批量操作性能优化
Mybatis默认的foreach批量插入在数据量较大时性能较差。我们通过两种方案解决:
- 批处理模式:在JDBC URL中添加
rewriteBatchedStatements=true参数,配合SqlSession的批量执行方法:
java复制try(SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
UserMapper mapper = session.getMapper(UserMapper.class);
for(User user : userList) {
mapper.insert(user);
}
session.commit();
}
- 使用Mybatis-Plus的saveBatch:内部自动优化了批量提交策略:
java复制userService.saveBatch(userList, 1000); // 每1000条提交一次
4.2 多数据源配置
在微服务架构中,经常需要同时访问多个数据源。我们的配置方案:
java复制@Configuration
@MapperScan(basePackages = "com.primary.mapper",
sqlSessionFactoryRef = "primarySqlSessionFactory")
public class PrimaryDataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public SqlSessionFactory primarySqlSessionFactory(
@Qualifier("primaryDataSource") DataSource dataSource) throws Exception {
MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setMapperLocations(
new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/primary/*.xml"));
return factory.getObject();
}
}
注意:多数据源事务管理需要使用
@Transactional(transactionManager = "primaryTransactionManager")明确指定事务管理器
4.3 敏感数据加解密
通过TypeHandler实现字段级别的加解密:
java复制public class EncryptTypeHandler extends BaseTypeHandler<String> {
private final Encryptor encryptor = new AESEncryptor();
@Override
public void setNonNullParameter(PreparedStatement ps, int i,
String parameter, JdbcType jdbcType) {
ps.setString(i, encryptor.encrypt(parameter));
}
@Override
public String getNullableResult(ResultSet rs, String columnName) {
return encryptor.decrypt(rs.getString(columnName));
}
// 其他重载方法...
}
在Mapper XML中指定typeHandler:
xml复制<result column="phone" property="phone"
typeHandler="com.example.handler.EncryptTypeHandler"/>
5. 常见问题排查指南
5.1 参数绑定异常
错误现象:There is no getter for property named 'xxx' in 'class java.lang.String'
解决方案:
- 使用
@Param注解明确参数名:
java复制List<User> selectByName(@Param("name") String name);
- 在XML中正确引用参数:
xml复制<select id="selectByName" resultType="User">
SELECT * FROM user WHERE name = #{name}
</select>
5.2 缓存一致性问题
现象:更新数据库后查询结果仍是旧数据
排查步骤:
- 确认是否开启了二级缓存(
<cache/>) - 检查更新操作后是否执行了
sqlSession.clearCache() - 对于分布式环境,检查是否配置了集中式缓存(如Redis)
5.3 分页插件失效
可能原因及解决:
- 未正确配置拦截器(见3.3节)
- 自定义SQL未保持
${ew.customSqlSegment}格式 - 调用了
selectMapsPage但返回类型不匹配
6. 扩展功能开发实践
6.1 自定义SQL注入器
实现逻辑删除的扩展功能:
java复制public class LogicDeleteInjector extends AbstractMethod {
@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass,
Class<?> modelClass,
TableInfo tableInfo) {
String sql = "UPDATE %s SET deleted=1 WHERE %s=#{%s}";
String methodName = "logicDeleteById";
SqlSource sqlSource = languageDriver.createSqlSource(
configuration,
String.format(sql,
tableInfo.getTableName(),
tableInfo.getKeyColumn(),
tableInfo.getKeyProperty()),
modelClass);
return addDeleteMappedStatement(mapperClass, methodName, sqlSource);
}
}
注册到配置中:
java复制@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new LogicDeleteInjector());
return interceptor;
}
6.2 多租户实现方案
基于Mybatis-Plus的多租户插件:
java复制public class TenantInterceptor implements InnerInterceptor {
@Override
public void beforeQuery(Executor executor, MappedStatement ms,
Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, BoundSql boundSql) {
// 修改SQL添加租户条件
String tenantId = TenantContext.getCurrentTenant();
if(StringUtils.isNotBlank(tenantId)) {
BoundSql newBoundSql = new BoundSql(...);
newBoundSql.setAdditionalParameter("tenant_id", tenantId);
// 返回修改后的BoundSql
}
}
}
配置数据表时需包含tenant_id字段,并在拦截器中自动注入该条件。
7. 性能监控与调优
7.1 SQL执行监控
通过自定义插件记录慢查询:
java复制@Intercepts({
@Signature(type= Executor.class, method="update",
args={MappedStatement.class,Object.class}),
@Signature(type= Executor.class, method="query",
args={MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class})
})
public class PerformanceInterceptor implements Interceptor {
private static final long SLOW_QUERY_THRESHOLD = 1000; // 1秒
@Override
public Object intercept(Invocation invocation) throws Throwable {
long start = System.currentTimeMillis();
Object result = invocation.proceed();
long elapsed = System.currentTimeMillis() - start;
if(elapsed > SLOW_QUERY_THRESHOLD) {
MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
log.warn("Slow SQL detected: {} - {}ms",
ms.getId(), elapsed);
}
return result;
}
}
7.2 连接池配置建议
结合Druid连接池的最佳配置:
yaml复制spring:
datasource:
druid:
initial-size: 5
min-idle: 5
max-active: 20
max-wait: 60000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
validation-query: SELECT 1
test-while-idle: true
test-on-borrow: false
test-on-return: false
filters: stat,wall
这些配置在电商大促期间经过验证,能有效平衡连接开销和并发能力。
