【设计】设计一个web版的数据库管理平台后端(之五) --借鉴mybatis

前情回顾

之前我们利用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 拦截器链 非侵入式扩展:主流程永葆纯洁
相关推荐
这个DBA有点耶25 分钟前
InnoDB页结构深入:页分裂、页合并、填充因子——B+树底层机制全解析
数据库·mysql·架构
山岚的运维笔记1 小时前
mysql 专业笔记 -- 第 36 章:MySQL 管理
运维·数据库·笔记·后端·mysql·oracle·dba
阳光宅男@李光熠2 小时前
【电子通识】一起学习TDK的EMC基础——电池兼容设计方法概述
java·前端·数据库
知识的搬运工旺仔2 小时前
唯一索引与 NULL 值:PostgreSQL 主键约束与 NULLS NOT DISTINCT
数据库·后端·sql·postgresql
我的xiaodoujiao2 小时前
Django 基础知识详细图文教程 9-Django 模板引擎 2
开发语言·数据库·后端·django
蓝速科技2 小时前
商用复杂环境翻译机耐用性选型指南丨蓝速科技
大数据·运维·数据库·人工智能·科技
starzy19903 小时前
Flink Sliding Window 详解及代码实现:从窗口重叠到状态爆炸防控
运维·数据库·flink
jnrjian3 小时前
DRG-50857 ORA-30576 drixmd.PurgeKGL CTXSYS
数据库·oracle
志栋智能4 小时前
集成是关键:让巡检超自动化融入现有工具链
运维·服务器·数据库·架构·自动化
IvorySQL4 小时前
PostgreSQL 日报|逻辑解码竞态条件修复(9 月 20 日)
数据库·人工智能·postgresql