Fescar - RM InsertExecutor介绍

简介: 开篇 这篇文章的目的是讲解RM Executor模块当中一些通用的方法,这些方法在各个Executor的父类当中实现的,各个子类Executor模块都会复用,因此抽取出来统一的进行讲解。 个人是认为抽取通用的内容放在一篇文章讲解完后可以针对每类Executor讲解特有的功能,这样能够有更好的理解。

开篇

 这篇文章的目的是讲解RM Executor模块当中一些通用的方法,这些方法在各个Executor的父类当中实现的,各个子类Executor模块都会复用,因此抽取出来统一的进行讲解。

 个人是认为抽取通用的内容放在一篇文章讲解完后可以针对每类Executor讲解特有的功能,这样能够有更好的理解。这篇文章讲解Executor的实现类InsertExecutor。


类依赖图


说明:

  • 着重讲解InsertExecutor实现类。


InsertExecutor方法介绍

public class InsertExecutor<T, S extends Statement> extends AbstractDMLBaseExecutor<T, S> {
 
   protected TableRecords beforeImage() throws SQLException {
        return TableRecords.empty(getTableMeta());
    }

}

public class TableRecords {

    public static TableRecords empty(TableMeta tableMeta) {
        return new TableRecords(tableMeta) {
            @Override
            public int size() {
                return 0;
            }

            @Override
            public List<Field> pkRows() {
                return new ArrayList<>();
            }

            @Override
            public void add(Row row) {
                throw new UnsupportedOperationException("xxx");
            }

            @Override
            public TableMeta getTableMeta() {
                throw new UnsupportedOperationException("xxx");
            }
        };
    }
}

说明:

  • Insert操作的执行前镜像顾名思义是空的,所以beforeImage()方法返回空记录。
  • TableRecords.empty返回的是一个没有记录的TableRecords对象。


public class InsertExecutor<T, S extends Statement> extends AbstractDMLBaseExecutor<T, S> {

    protected TableRecords afterImage(TableRecords beforeImage) throws SQLException {
        SQLInsertRecognizer recogizier = (SQLInsertRecognizer)sqlRecognizer;
        List<String> insertColumns = recogizier.getInsertColumns();
        TableMeta tmeta = getTableMeta();
        TableRecords afterImage = null;
        if (tmeta.containsPK(insertColumns)) {
            // 处理插入数据包含主键的情况
            List<Object> pkValues = null;
            String pk = tmeta.getPkName();
            for (int paramIdx = 0; paramIdx < insertColumns.size(); paramIdx++) {
                if (insertColumns.get(paramIdx).equalsIgnoreCase(pk)) {
                    // 处理PreparedStatement类型,从参数列表中提取
                    if (statementProxy instanceof PreparedStatementProxy) {
                        pkValues = ((PreparedStatementProxy) statementProxy).getParamsByIndex(paramIdx);
                    } else {
                        // 处理Statement类型,直接从SQL语句中提取
                        List<List<Object>> insertRows = recogizier.getInsertRows();
                        pkValues = new ArrayList<>(insertRows.size());
                        
                        for (List<Object> row : insertRows) {
                            pkValues.add(row.get(paramIdx));
                        }
                    }

                    //主键只有一个
                    break;
                }
            }
            if (pkValues == null) {
                throw new ShouldNeverHappenException();
            }
            afterImage = getTableRecords(pkValues);

        } else {
            // 处理主键自动生成的场景,也就是不包含主键的情况。
            Map<String, ColumnMeta> pkMetaMap = getTableMeta().getPrimaryKeyMap();
            if (pkMetaMap.size() != 1) {
                throw new NotSupportYetException();
            }

            // 获取primary key的元数据信息
            ColumnMeta pkMeta = pkMetaMap.values().iterator().next();
            if (!pkMeta.isAutoincrement()) {
                throw new ShouldNeverHappenException();
            }

            // 获取generatedKeys()获取生成的主键
            ResultSet genKeys = null;
            try {
                genKeys = statementProxy.getTargetStatement().getGeneratedKeys();
            } catch (SQLException e) {
               
                if ("S1009".equalsIgnoreCase(e.getSQLState())) {
                    genKeys = statementProxy.getTargetStatement().executeQuery("SELECT LAST_INSERT_ID()");
                } else {
                    throw e;
                }
            }
            List<Object> pkValues = new ArrayList<>();
            while (genKeys.next()) {
                Object v = genKeys.getObject(1);
                pkValues.add(v);
            }

            // 生成执行后镜像
            afterImage = getTableRecords(pkValues);

        }

        if (afterImage == null) {
            throw new SQLException("Failed to build after-image for insert");
        }

        return afterImage;
    }
}

说明:

  • afterImage准备执行后的镜像,针对Insert操作就是新增的记录。
  • afterImage处理分为两种场景,分为插入记录包含主键字段和不包含主键字段。
  • 记录包含主键字段场景下,区分preparedStatement和Statement两种场景,前者去传入参数中获取,后者从执行的SQL当中解析。
  • 不包含主键字段场景下,通过获取getGeneratedKeys()返回的值获取。
  • 获取主键的值以后pkValues,然后通过getTableRecords()生成执行后镜像。


public class InsertExecutor<T, S extends Statement> extends AbstractDMLBaseExecutor<T, S> {

    private TableRecords getTableRecords(List<Object> pkValues) throws SQLException {
        TableRecords afterImage;
        String pk = getTableMeta().getPkName();
        StringBuffer selectSQLAppender = new StringBuffer("SELECT * FROM " + getTableMeta().getTableName() + " WHERE ");
        for (int i = 1; i <= pkValues.size(); i++) {
            selectSQLAppender.append(pk + "=?");
            if (i < pkValues.size()) {
                selectSQLAppender.append(" OR ");
            }
        }
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = statementProxy.getConnection().prepareStatement(selectSQLAppender.toString());

            for (int i = 1; i <= pkValues.size(); i++) {
                ps.setObject(i, pkValues.get(i - 1));
            }

            rs = ps.executeQuery();
            afterImage = TableRecords.buildRecords(getTableMeta(), rs);

        } finally {
            if (rs != null) {
                rs.close();
            }
            if (ps != null) {
                ps.close();
            }
        }
        return afterImage;
    }
}

说明:

  • getTableRecords通过针对主键信息拼接SELECT的SQL语句拼接成根据主键查询字段信息的SQL。
  • 通过buildRecords()方法针对查询结果ResultSet进行处理。
  • 生成执行后镜像信息在buildRecords()内部实现。


public class TableRecords {

    public static TableRecords buildRecords(TableMeta tmeta, ResultSet resultSet) throws SQLException {
        TableRecords records = new TableRecords(tmeta);
        ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
        int columnCount = resultSetMetaData.getColumnCount();

        while (resultSet.next()) {
            List<Field> fields = new ArrayList<>(columnCount);
            for (int i = 1; i <= columnCount; i++) {
                String colName = resultSetMetaData.getColumnName(i);
                ColumnMeta col = tmeta.getColumnMeta(colName);
                Field field = new Field();
                field.setName(col.getColumnName());
                if (tmeta.getPkName().equals(field.getName())) {
                    field.setKeyType(KeyType.PrimaryKey);
                }
                field.setType(col.getDataType());
                field.setValue(resultSet.getObject(i));
                fields.add(field);
            }

            Row row = new Row();
            row.setFields(fields);

            records.add(row);
        }
        return records;
    }
}

说明:

  • buildRecords内部遍历查询的结果凭借Row对象。
  • 插入的数据可能有多条,外层while循环获取所有的行数,内层for循环遍历所有的列数。


期待

 InsertExecutor生成执行前后镜像的过程分析完后,会接着分析DeleteExecutor。额外的收货就是分析完对于SQL或者说对阿里开源的druid有了一些深入研究的兴趣。

目录
相关文章
|
8月前
|
机器学习/深度学习 人工智能 算法
更大的上下文窗口为什么让RAG变得更重要而非更多余
大上下文窗口(如1M tokens)并未淘汰RAG,反而凸显其价值:LLM注意力易被噪声稀释,“迷失在中间”效应导致性能下降。实验证明,相关性筛选比单纯扩容更关键。RAG+大上下文协同——先精准检索重排序,再注入高密度片段——才是生产级AI的可靠范式。
706 0
|
7月前
|
传感器 人工智能 监控
数字孪生项目的开发流程
数字孪生开发是融合物理感知、三维建模、WebGL渲染与IoT数据的跨学科工程:涵盖业务分析、轻量化建模(glTF/BIM)、Three.js/Cesium集成、实时数据驱动、AI仿真分析及多端部署,实现从监控到预测的闭环演进。(239字)
|
消息中间件 存储 Cloud Native
云消息队列 Kafka 版 V3 系列荣获信通院“云原生技术创新标杆案例”
2024 年 12 月 24 日,由中国信息通信研究院(以下简称“中国信通院”)主办的“2025 中国信通院深度观察报告会:算力互联网分论坛”,在北京隆重召开。本次论坛以“算力互联网 新质生产力”为主题,全面展示中国信通院在算力互联网产业领域的研究、实践与业界共识,与产业先行者共同探索算力互联网产业未来发展的方向。会议公布了“2024 年度云原生与应用现代化标杆案例”评选结果,“云消息队列 Kafka 版 V3 系列”荣获“云原生技术创新标杆案例”。
735 115
|
机器学习/深度学习 人工智能 自然语言处理
Transformer架构深度解析:重新定义序列建模的革命
Transformer是一种基于自注意力机制的神经网络架构,2017年由Google提出,彻底摒弃了RNN的循环结构,实现并行化处理序列数据。其核心通过QKV机制捕捉长距离依赖,以“圆桌会议”式交互提升效率与性能,成为大模型时代的基石。
|
NoSQL Java 区块链
Springboot应用开发:配置类整理
在使用SpringBoot进行应用开发中,通常需要编写很多的配置类,这些配置类在很大程度上提高了应用的开发速度.其中就包括线程池、数据库连接池、缓存的相关配置类。在这里我将经常用到的配置类进行整理和总结。
553 1
|
人工智能 运维 安全
函数计算支持热门 MCP Server 一键部署
MCP(Model Context Protocol)自2024年发布以来,逐渐成为AI开发领域的实施标准。OpenAI宣布其Agent SDK支持MCP协议,进一步推动了其普及。然而,本地部署的MCP Server因效率低、扩展性差等问题,难以满足复杂生产需求。云上托管成为趋势,函数计算(FC)作为Serverless算力代表,提供一键托管开源MCP Server的能力,解决传统托管痛点,如成本高、弹性差、扩展复杂等。通过CAP平台,用户可快速部署多种热门MCP Server,体验高效灵活的AI应用开发与交互方式。
4385 10
|
Linux API
Linux下载工具wget与curl
`wget` 是一个用于从网络下载文件的命令行工具,支持HTTP、HTTPS和FTP协议。它能自动处理下载中断,并支持递归下载网站内容。基本用法:`wget URL`,可指定文件名(`-O`)、保存目录(`-P`),还支持断点续传(`-c`)、限速(`--limit-rate`)和递归下载(`-r`)。相比之下,`curl` 更侧重于发送各种HTTP请求(如GET、POST),并支持文件上传、自定义请求头和cookie等功能。
1043 10
|
机器学习/深度学习 数据库 索引
Transformer 学习笔记 | Encoder
本文记录了学习Transformer模型过程中对Encoder部分的理解,包括多头自注意力机制(Multi-Head Self-Attention)和前馈网络(Feed-Forward Network)的工作原理。每个Encoder Layer包含残差连接(Residual Connection)和层归一化(Layer Normalization),以缓解梯度消失问题并稳定训练过程。文中详细解释了Q、K、V的含义及缩放点积注意力机制(Scaled Dot-Product Attention),并通过图解展示了各组件的工作流程。欢迎指正。
|
存储 缓存
本地缓存和分布式缓存区别
【2月更文挑战第16天】
1169 2
本地缓存和分布式缓存区别
|
Ubuntu Unix Linux
【GO基础】1. Go语言环境搭建
【GO基础】1. Go语言环境搭建
1181 2