06-EaEngine调度器

06-EaEngine:549行的调度器

EaEngine 是元数据引擎的总调度------初始化链、缓存解析、六种API入口、事务的success-flag模式、postProcess加密脱敏全在这一个类里。这篇按"启动→查询→更新→异常"的顺序把549行拆完。

文章目录

源码:browise-metadata/src/main/java/com/browise/ea/core/engine/EaEngine.java


一、它持有的八个协作者

java 复制代码
public class EaEngine implements EaLifecycle {
    private EaConfig config;              // 配置(密钥/阈值/自定义策略)
    private EaConnectionProvider provider;// 连接提供者(Spring DataSource桥接)
    private EaMetadataCache cache;        // 元数据缓存(ConcurrentHashMap)
    private MetadataLoader metadataLoader;// 五表装载器
    private SqlBuilder sqlBuilder;        // SQL生成器(持有Dialect)
    private Dialect dialect;              // 数据库方言(探测后注入)
    private SlowSqlMonitor slowSqlMonitor;// 慢SQL监控
    private EncryptStrategy encryptStrategy; // SM4(可空)
    private SignStrategy signStrategy;       // SM2(可空)
}

EaEngine 自己不拼SQL、不连数据库、不做加密------它是纯粹的调度者,每个动作都委托给专职组件。唯一内联实现的是 toJson/escapeJson/mask 这类不值得抽出去的小工具。


二、init():六步启动链

java 复制代码
public void init(EaConfig config, EaConnectionProvider provider) {
    this.config = config;
    this.provider = provider;
    this.cache = new EaMetadataCache();
    this.metadataLoader = new MetadataLoader();
    this.slowSqlMonitor = new SlowSqlMonitor(config);

    initEncryptStrategy();   // ①自定义策略优先,否则SM4默认
    initSignStrategy();      // ②同上,SM2

    DdlExecutor ddlExecutor = new DdlExecutor();
    ddlExecutor.executeAutoCreate(provider, config);   // ③五表不存在则建
    ddlExecutor.executeAutoUpgrade(provider, config);  // ④列缺失则补

    try (Connection conn = provider.getConnection()) {
        DatabaseType dbType = DatabaseType.detect(conn, config.isKingBasePgMode());
        this.dialect = dbType.createDialect();          // ⑤探测方言
    }

    this.sqlBuilder = new SqlBuilder(dialect);          // ⑥方言注入SQL生成器
    metadataLoader.loadAll(provider, cache);            //    五表全量装载
}

三个细节:

顺序不能乱------③④必须在⑤前:DdlExecutor自己按DatabaseType建表,而方言探测需要连接(连接要活着才能detect)。⑥必须在⑤后:SqlBuilder构造时就要方言。

双策略初始化的自定义优先

java 复制代码
private void initEncryptStrategy() {
    EncryptStrategy custom = config.customEncryptStrategy();
    if (custom != null) { this.encryptStrategy = custom; return; }  // SPI注入优先
    if (config.getEncryptKey() != null) {
        this.encryptStrategy = new Sm4EncryptStrategy(key, iv, mode); // 否则默认SM4
    }
}

不配密钥 → encryptStrategy为null → postProcess跳过解密。加密能力默认关闭,配置了才生效。

refresh() 是synchronized的------元数据热更新(管理界面改了EA03字段)不能和正在进行的loadAll并发:

java 复制代码
@Override
public synchronized void refresh() {
    metadataLoader.loadAll(provider, cache);
}

三、describe():手拼JSON的元数据输出

describe不拼SQL------它把缓存里的元数据序列化成前端能消费的JSON:

java 复制代码
public String describe(String eaCode) {
    EaSqlMeta sqlMeta = resolveSqlMeta(eaCode);
    List<EaFieldMeta> fields = cache.getFieldMetas(eaCode);
    List<EaConditionMeta> conditions = cache.getConditionMetas(eaCode);
    // StringBuilder逐字段append,输出:
    // { "sqlCode":"SYS_USER_s", "title":"用户查询",
    //   "columns":[ {name,label,dataType,displayType,codeName,
    //                encrypted,masked,pattern,maskRule...} ],
    //   "conditions":[ {paramName,label,operator,required...} ] }
}

为什么手拼而不用Jackson? metadata模块刻意不依赖任何JSON库------它是纯JDK实现的引擎,springboot装配层(EaAutoConfiguration)才接触Spring。这样引擎可以脱离Spring独立测试(metadata模块108个单测全部纯JVM跑)。


四、query():两条通道的分岔

java 复制代码
public String query(String eaCode, Map<String, String> params) {
    EaSqlMeta sqlMeta = resolveSqlMeta(eaCode);
    List<EaFieldMeta> fields = cache.getFieldMetas(eaCode);
    List<EaConditionMeta> conditions = cache.getConditionMetas(eaCode);

    Integer minRow = parseIntOrNull(params.get("minRow"));
    Integer maxRow = parseIntOrNull(params.get("maxRow"));
    boolean paginate = minRow != null && maxRow != null;

    SqlBuilder.BuiltSql builtSql;
    if (sqlMeta.isHasRawSql()) {
        builtSql = sqlBuilder.buildFromRawSql(...);   // rawSql通道
    } else {
        builtSql = sqlBuilder.buildSelect(...);       // 元数据通道
    }
    return executeQuery(builtSql, fields);
}

hasRawSql 是分岔点------复杂SQL整条存EA01.EAE800,走占位符替换通道;标准查询走五表组装通道。两条通道最终都汇入同一个executeQuery。


五、resolveSqlMeta():缓存miss的synchronized双检

java 复制代码
private EaSqlMeta resolveSqlMeta(String eaCode) {
    if (!cache.contains(eaCode)) {
        synchronized (this) {
            if (!cache.contains(eaCode)) {
                metadataLoader.loadAll(provider, cache);  // 全量重载
            }
        }
    }
    EaSqlMeta meta = cache.getSqlMeta(eaCode);
    if (meta == null) {
        throw new EaException(EaExceptionType.METADATA_ERROR, "未找到SQL编号: " + eaCode);
    }
    return meta;
}

启动时loadAll过了,为什么运行时还要查?------运行中通过管理界面新增了SQL定义,缓存里没有。双检锁(double-checked locking)避免并发请求触发多次全量重载。找不到时抛METADATA_ERROR而不是返回null------让上层GlobalExceptionHandler统一转成前端错误。


六、success-flag事务模式

executeQuery/executeUpdate共用同一套事务模板(这段修过bug,BROWISE-STATUS记录了修复过程):

java 复制代码
private String executeQuery(SqlBuilder.BuiltSql builtSql, List<EaFieldMeta> fields) {
    long start = System.currentTimeMillis();
    boolean myTrans = !provider.hasTransaction();  // ①外层没事务才自己管
    boolean success = false;
    if (myTrans) provider.beginTrans();
    Connection conn = null;
    try {
        conn = provider.getConnection();
        try (PreparedStatement ps = conn.prepareStatement(builtSql.getSql())) {
            bindParams(ps, builtSql.getParams());       // ②占位符按序绑定
            try (ResultSet rs = ps.executeQuery()) {
                List<Map<String, Object>> rows = resultSetToList(rs, fields);
                slowSqlMonitor.check(...);               // ③慢SQL计时
                success = true;                          // ④成功标志
                return toJson(rows);
            }
        }
    } catch (Exception e) {
        throw new EaException(SQL_ERROR, builtSql.getSql(), e);
    } finally {
        if (myTrans) {
            if (success) provider.commit();    // ⑤按标志提交/回滚
            else provider.rollback();
        } else if (conn != null) {
            conn.close();                      // ⑥外层事务只还连接
        }
    }
}

关键在①的判断 :如果调用方(如commonSave多store提交)已开启事务,EA引擎不自己commit------它只执行SQL、还连接,事务边界交给外层。这就是"多store一个事务"的实现基础。

修复前的bug:连接管理没有按myTrans分支------外层事务下引擎把连接close了,导致Spring事务提交时连接已失效。success-flag取代了"catch里rollback"的写法,因为catch块本身抛新异常时容易吞掉原始回滚路径。


七、executeUpdate():_t参数分发

java 复制代码
public int executeUpdate(String eaCode, Map whereParams, Map valueParams) {
    String type = valueParams.get("_t");
    if (type == null) type = whereParams.get("_t");
    if (type == null) type = sqlMeta.getSqlMode();   // 兜底用元数据定义的模式

    if ("2".equals(type))      builtSql = sqlBuilder.buildUpdate(...);
    else if ("4".equals(type)) builtSql = sqlBuilder.buildInsert(...);
    else if ("3".equals(type)) builtSql = sqlBuilder.buildDelete(...);
    else if ("1".equals(type) && sqlMeta.isHasRawSql())
                                builtSql = sqlBuilder.buildFromRawSql(...);
    else if ("0".equals(type))
        throw new EaException("综合类型需通过 _t 参数指定操作");
}

_t 从行协议泄漏到了执行接口------前端collect('dirty')收集的行自带 _t=1/3/4,commonSave把行连同_t传到这里,引擎按行状态选DML类型。协议字段一杆子插到了SQL生成层。


八、resultSetToList 与 postProcess

java 复制代码
while (rs.next()) {
    Map<String, Object> row = new LinkedHashMap<>();   // 保序
    for (int i = 1; i <= colCount; i++) {
        String key = meta.getColumnLabel(i).toLowerCase();
        Object value;
        if (Types.BLOB == meta.getColumnType(i)) {
            // BLOB→UTF-8字符串(政务系统里BLOB存的是文本)
            Blob blob = rs.getBlob(i);
            value = blob != null ? new String(blob.getBytes(1,(int)blob.length()),"UTF-8") : "";
        } else {
            value = rs.getObject(i);
            if (value != null && "null".equals(value.toString())) value = "";  // Oracle的"null"字符串
        }
        row.put(key, value);
    }
    postProcess(row, fields);   // 每行立刻后处理
}

postProcess两步:isEncrypted先解密、isMasked再脱敏------先解密再脱敏的顺序不能反(脱敏后的星号解不开)。


✅ 亮点:把549行调度器拆成启动链/两条SQL通道/success-flag事务/_t分发四块,重点讲myTrans判断如何支撑多store单事务、BLOB与Oracle"null"字符串的现实处理。适合写引擎类中间件的参考。扩展方向:第07篇拆SqlBuilder六法、第16篇拆SlowSqlMonitor。

相关推荐
卓怡学长1 小时前
w181springboot机场乘客服务系统
java·数据库·spring boot·spring·intellij-idea
萧瑟余晖2 小时前
Java深入解析篇三十九之集成测试
java·开发语言·集成测试
天涯明月19932 小时前
Ray 架构解析——以动态任务图统一 AI 计算的分布式框架
大数据·人工智能·分布式·架构·ray
IT大白鼠2 小时前
Wazuh开源安全平台技术架构与功能特性深度解析
架构·开源xdr/siem
码域空间2 小时前
锁住了检查,锁不住延迟——MySQL 读写分离架构下双重检查模式失效实录
数据库·mysql·架构
吴声子夜歌3 小时前
Java面试——数据结构(一)
java·数据结构·面试
罗超驿3 小时前
SpringBoot 快速入门
java·spring boot·后端
疯狂打码的少年3 小时前
【数据结构】选择类排序:简单选择与堆排序
java·数据结构·笔记·算法