前情回顾
之前我们利用4期将了设计一个数据库管理平台的代码精要。我们还讲了mybatis中最重要的几个对象。
现在,我们利用mybatis中的一些思路来优化我们的数据库管理平台。
优化点一:用"注册表(Registry)"替代硬编码的"Switch-Case"(解决"开闭原则")
问题:
每次新增数据库(如达梦、TDSQL)或新增SQL关键词(如UPSERT、MERGE),你都必须修改Configuration类和枚举类的源码。这违反了开闭原则(对扩展开放,对修改关闭)。
借鉴MyBatis(TypeAliasRegistry/MapperRegistry)
java
// 1. 定义一个 DialectRegistry(注册表)
public class DialectRegistry {
private final Map<String, Supplier<Dialect>> registry = new ConcurrentHashMap<>();
public void register(String dbType, Supplier<Dialect> dialectSupplier) {
registry.put(dbType.toLowerCase(), dialectSupplier);
}
public Dialect getDialect(String dbType) {
Supplier<Dialect> supplier = registry.get(dbType.toLowerCase());
if (supplier == null) {
throw new UnsupportedOperationException("Unsupported DB: " + dbType);
}
return supplier.get();
}
}
// 2. Configuration 只需持有注册表,并在启动时初始化
public class Configuration {
private final DialectRegistry dialectRegistry = new DialectRegistry();
public Configuration(DataSource dataSource) {
// 启动时注册所有已知方言(甚至可以从配置文件动态加载)
dialectRegistry.register("mysql", MySQLDialect::new);
dialectRegistry.register("oracle", OracleDialect::new);
dialectRegistry.register("sqlserver", SQLServerDialect::new);
// 用户想新增达梦?直接调用 register("dameng", DamengDialect::new) 即可!
}
public Dialect getDialect() {
return dialectRegistry.getDialect(this.databaseType);
}
}
把"变化的数据库类型"变成了"注册表中的一项"。从此,主流程代码(Executor、StatementHandler)一行不改,完美支持无限扩展。
优化点二:用"工厂模式(Factory)"解耦"对象创建"(解决"依赖倒置")
SimpleExecutor 直接 new SimpleStatementHandler(configuration)
SimpleStatementHandler 直接 new MapResultSetHandler()
问题:
Executor 和 StatementHandler 深度绑定了具体实现类。如果想做SQL日志增强、换成批量执行器、或换成自定义结果集处理器(如带加密解密的SecureResultSetHandler),必须修改 SimpleExecutor 的源码。
借鉴MyBatis(DefaultSqlSessionFactory、MapperProxyFactory):
java
// 1. 定义工厂接口
public interface StatementHandlerFactory {
StatementHandler create(Configuration configuration);
}
// 2. 默认工厂生产默认处理器
public class DefaultStatementHandlerFactory implements StatementHandlerFactory {
@Override
public StatementHandler create(Configuration configuration) {
return new SimpleStatementHandler(configuration);
}
}
// 3. 改造 Executor,依赖工厂而非具体类
public class SimpleExecutor implements Executor {
private final StatementHandlerFactory statementHandlerFactory;
public SimpleExecutor(Configuration configuration) {
this.statementHandlerFactory = configuration.getStatementHandlerFactory(); // 从配置拿
}
@Override
public List<Map<String, Object>> query(String sql, Object... parameters) {
StatementHandler handler = statementHandlerFactory.create(configuration);
return handler.query(sql, parameters);
}
}
现在如果接入Druid SQL监控,只需写一个 DruidStatementHandlerFactory 并注入Configuration,业务代码一行都不用动。
优化点三:借鉴"MapperMethod"思想,封装"SQL指令"而非散落的"if-else"
SqlSession 暴露了三个独立方法:selectList()、executeDML()、executeDDL()。用户(上层Controller)需要自己判断用哪个。
问题:
如果未来引入"存储过程调用(CALL)"或"批量提交(Batch)",SqlSession接口会无限膨胀(加executeProcedure、executeBatch),变成"大杂烩"。
借鉴MyBatis(MapperMethod 封装命令):
MyBatis的MapperMethod把"方法签名+SQL类型+参数映射"封装成一个对象,execute()内部根据类型走不同分支。
java
// 1. 封装一个 SqlCommand,包含类型和SQL
public class SqlCommand {
private final SqlCommandType type; // SELECT, DML, DDL, PROCEDURE
private final String sql;
public Object execute(SqlSession session, Object[] params) {
switch (type) {
case SELECT: return session.selectList(sql, params);
case DML: return session.executeDML(sql, params);
case DDL: return session.executeDDL(sql, params);
// 新增CALL时,只改这里,不改SqlSession接口
}
}
}
// 2. SqlSession只保留一个通用入口
public class SqlSession {
public Object execute(SqlCommand command, Object... parameters) {
return command.execute(this, parameters);
}
}
将"变化的SQL类型"转化为"可枚举的命令对象",业务侧只需要构造SqlCommand传给SqlSession,完全无需感知底层是查还是改。
优化点四:引入"拦截器(Interceptor)"机制,抽离"横切关注点"
当前代码:
SimpleStatementHandler.query()里,直接包含stmt.setQueryTimeout()和stmt.setMaxRows(),日志打印、性能监控、SQL审计全部要硬编码进去。
问题:
随着需求增加(慢SQL报警、SQL黑名单拦截、数据脱敏),StatementHandler会越来越臃肿,变成"上帝类"。
借鉴MyBatis(Interceptor链 + 动态代理):
虽然平台不需要像MyBatis那样用动态代理那么重,但可以引入"责任链模式"预处理/后处理SQL。
java
// 1. 定义拦截器接口
public interface SqlInterceptor {
void beforeExecute(String sql, Object[] params);
void afterExecute(String sql, Object result, long costMs);
}
// 2. SimpleStatementHandler 注入拦截器列表
public class SimpleStatementHandler {
private List<SqlInterceptor> interceptors;
@Override
public List<Map<String, Object>> query(String sql, Object... parameters) {
for (SqlInterceptor interceptor : interceptors) {
interceptor.beforeExecute(sql, parameters);
}
long start = System.currentTimeMillis();
// ... 执行逻辑 ...
long cost = System.currentTimeMillis() - start;
for (SqlInterceptor interceptor : interceptors) {
interceptor.afterExecute(sql, result, cost);
}
return result;
}
}
现在,慢SQL日志、权限校验、数据脱敏全部以"插件"形式挂在拦截器链上。主流程SimpleStatementHandler永远只负责执行SQL,不关心任何旁路逻辑。新增审计需求时,只需要新增一个拦截器类并在启动时注册即可。
总结
| 当前代码 | 抽象目标(MyBatis方式) | 架构收益 |
|---|---|---|
| switch硬编码创建方言 | DialectRegistry 注册表 | 开闭原则:新增DB零修改 |
| new SimpleStatementHandler() | StatementHandlerFactory 工厂 | 依赖倒置:可随意替换执行策略 |
| SqlSession有多个executeXxx()方法 | SqlCommand统一命令封装 | 单一职责:接口永不膨胀 |
| 日志/监控与执行逻辑混杂 | SqlInterceptor 拦截器链 | 非侵入式扩展:主流程永葆纯洁 |