1. MyBatis-Plus核心架构解析
MyBatis-Plus作为MyBatis的增强工具包,其核心设计理念可概括为"简化开发、提升效率"。框架通过三个层次实现这一目标:
-
基础层:对MyBatis原生功能进行无侵入式增强,包括:
- 自动化SQL生成引擎(BaseMapper)
- 动态表名处理器
- 性能分析拦截器
-
中间层:提供企业开发常用组件:
java复制// 条件构造器示例 QueryWrapper<User> queryWrapper = new QueryWrapper<>(); queryWrapper.lambda() .eq(User::getDepartmentId, 5) .between(User::getCreateTime, startDate, endDate); -
扩展层:支持插件化扩展:
- 多租户SQL解析器
- 乐观锁插件
- 动态数据源支持
重要设计原则:所有增强功能都通过拦截器机制实现,确保与原生MyBatis的兼容性。这种设计使得开发者可以按需启用特定功能模块。
2. 生产环境CRUD优化实战
2.1 高性能批量操作方案
常规循环插入的性能瓶颈主要体现在:
- 每次操作都需要建立/释放数据库连接
- SQL语句重复解析
- 事务开销累积
优化方案对比:
| 方案 | 10万条数据耗时 | 内存占用 | 适用场景 |
|---|---|---|---|
| 循环单条插入 | 182s | 低 | 小批量数据 |
| BatchExecutor | 28s | 中 | 通用场景 |
| 拼接批量SQL | 9s | 高 | 大数据量 |
推荐实现:
java复制// 使用BatchExecutor
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
for (int i = 0; i < dataList.size(); i++) {
mapper.insert(dataList.get(i));
if (i % 1000 == 0) {
sqlSession.flushStatements();
}
}
sqlSession.commit();
2.2 智能字段填充策略
审计字段自动化处理方案:
- 实现MetaObjectHandler:
java复制public class AuditMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createBy", String.class, getCurrentUser());
this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateBy", String.class, getCurrentUser());
this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
}
}
- 配置生效:
yaml复制mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted
logic-not-delete-value: 0
logic-delete-value: 1
meta-object-handler: com.example.AuditMetaObjectHandler
3. 企业级高级特性剖析
3.1 多租户架构实现
SAAS系统多租户方案选型:
-
独立数据库方案:
java复制public class TenantDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return TenantContext.getCurrentTenant(); } } -
共享数据库独立Schema:
sql复制-- 动态修改schema SET search_path TO tenant1; -
数据隔离方案对比:
| 维度 | 独立数据库 | 共享DB独立Schema | 共享DB共享Schema |
|---|---|---|---|
| 隔离性 | 高 | 中 | 低 |
| 成本 | 高 | 中 | 低 |
| 运维复杂度 | 高 | 中 | 低 |
| 扩展性 | 低 | 中 | 高 |
3.2 分布式ID生成策略
雪花算法优化实践:
java复制public class CustomIdGenerator implements IdentifierGenerator {
private final long workerId;
private long sequence = 0L;
private long lastTimestamp = -1L;
public CustomIdGenerator(@Value("${cluster.node-id}") long workerId) {
this.workerId = workerId & 0x1F;
}
@Override
public Number nextId(Object entity) {
long timestamp = timeGen();
if (timestamp < lastTimestamp) {
throw new RuntimeException("时钟回拨异常");
}
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & 0xFFF;
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
return ((timestamp - 1288834974657L) << 22)
| (workerId << 17)
| sequence;
}
}
4. 性能调优深度指南
4.1 SQL执行监控方案
执行分析拦截器配置:
java复制@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// SQL分析插件
interceptor.addInnerInterceptor(new PerformanceInnerInterceptor() {
@Override
protected void outputSlowLog(Statement statement, long time) {
if (time > 1000) { // 超过1秒视为慢查询
logger.warn("Slow SQL detected: {}", statement.toString());
}
}
});
return interceptor;
}
4.2 二级缓存优化策略
Redis缓存集成方案:
java复制public class RedisCache implements Cache {
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final String id;
private final RedisTemplate<String, Object> redisTemplate;
public RedisCache(String id) {
this.id = id;
this.redisTemplate = ApplicationContextHolder.getBean("redisTemplate");
}
@Override
public Object getObject(Object key) {
try {
lock.readLock().lock();
return redisTemplate.opsForValue().get(key.toString());
} finally {
lock.readLock().unlock();
}
}
@Override
public void putObject(Object key, Object value) {
try {
lock.writeLock().lock();
redisTemplate.opsForValue().set(key.toString(), value, 30, TimeUnit.MINUTES);
} finally {
lock.writeLock().unlock();
}
}
}
5. 复杂查询解决方案
5.1 动态SQL构建技巧
多条件动态查询实现:
java复制public Page<User> queryUsers(UserQuery query, Pageable pageable) {
return lambdaQuery()
.eq(query.getDeptId() != null, User::getDeptId, query.getDeptId())
.like(StringUtils.isNotBlank(query.getName()), User::getName, query.getName())
.in(query.getRoleIds() != null, User::getRoleId, query.getRoleIds())
.between(query.getStartTime() != null && query.getEndTime() != null,
User::getCreateTime, query.getStartTime(), query.getEndTime())
.orderByAsc(User::getSort)
.page(new Page<>(pageable.getPageNumber(), pageable.getPageSize()));
}
5.2 联表查询优化方案
- 注解方式:
java复制@Select("SELECT u.*, d.name as deptName FROM user u LEFT JOIN department d ON u.dept_id = d.id")
@Results({
@Result(column = "deptName", property = "deptName")
})
List<UserVO> selectUserWithDept();
- XML方式性能对比:
xml复制<select id="selectComplexUsers" resultMap="userDeptMap">
SELECT
u.id, u.name,
d.id as dept_id, d.name as dept_name,
r.id as role_id, r.name as role_name
FROM user u
LEFT JOIN department d ON u.dept_id = d.id
LEFT JOIN user_role ur ON u.id = ur.user_id
LEFT JOIN role r ON ur.role_id = r.id
WHERE u.status = 1
</select>
6. 扩展开发实践
6.1 自定义数据权限实现
基于部门的数据权限过滤:
java复制public class DataPermissionInterceptor implements InnerInterceptor {
@Override
public void beforeQuery(Executor executor, MappedStatement ms,
Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
BoundSql boundSql) {
if (!needDataFilter(ms)) {
return;
}
String originalSql = boundSql.getSql();
String whereClause = buildDataPermissionWhere();
String newSql = originalSql.replace("where", "where " + whereClause + " and ");
resetSql(ms, boundSql, newSql);
}
private String buildDataPermissionWhere() {
User currentUser = SecurityUtils.getCurrentUser();
if (currentUser.isAdmin()) {
return "1=1";
}
return "dept_id in (" + StringUtils.join(currentUser.getAccessDepts(), ",") + ")";
}
}
6.2 分布式事务集成
Seata整合配置要点:
yaml复制# application.yml
seata:
enabled: true
application-id: ${spring.application.name}
tx-service-group: my_tx_group
service:
vgroup-mapping:
my_tx_group: default
grouplist:
default: 127.0.0.1:8091
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace:
group: SEATA_GROUP
registry:
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
namespace:
group: SEATA_GROUP
7. 生产环境问题排查
7.1 常见异常处理指南
-
主键冲突问题:
- 现象:Duplicate entry 'xxx' for key 'PRIMARY'
- 解决方案:
java复制@TableId(type = IdType.ASSIGN_ID) // 使用雪花ID private Long id;
-
乐观锁冲突:
- 现象:Optimistic lock failed
- 处理流程:
java复制@Transactional public void updateWithRetry(User user) { int retry = 0; while (retry < 3) { try { userMapper.updateById(user); break; } catch (OptimisticLockException e) { user = userMapper.selectById(user.getId()); retry++; } } }
7.2 性能瓶颈分析
慢查询定位步骤:
-
启用SQL监控:
yaml复制mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl -
使用Explain分析:
sql复制EXPLAIN SELECT * FROM large_table WHERE status = 1 ORDER BY create_time DESC; -
索引优化建议:
- 避免在索引列上使用函数
- 遵循最左前缀原则
- 控制索引数量(单表建议不超过5个)
8. 最佳实践总结
-
代码生成器配置建议:
java复制FastAutoGenerator.create(dataSourceConfig) .globalConfig(builder -> builder .author("developer") .enableSwagger() .outputDir("src/main/java")) .packageConfig(builder -> builder .parent("com.example") .moduleName("system")) .strategyConfig(builder -> builder .addInclude("tbl_user","tbl_dept") .entityBuilder() .enableLombok() .versionColumnName("version") .logicDeleteColumnName("deleted")) .execute(); -
项目结构规范:
code复制src/main/java ├── com.example │ ├── common # 公共模块 │ ├── config # 配置类 │ ├── controller # 控制层 │ ├── entity # 实体类 │ ├── enums # 枚举类 │ ├── mapper # Mapper接口 │ ├── service # 服务层 │ └── utils # 工具类 -
监控指标采集:
java复制@Bean public MeterBinder mybatisMetrics(MybatisPlusInterceptor interceptor) { return registry -> { interceptor.getInterceptors().stream() .filter(i -> i instanceof PerformanceInnerInterceptor) .findFirst() .ifPresent(i -> { PerformanceInnerInterceptor pii = (PerformanceInnerInterceptor)i; Gauge.builder("mybatis.plus.query.count", pii::getQueryCount) .register(registry); Gauge.builder("mybatis.plus.query.time", pii::getTotalTime) .register(registry); }); }; }