天天看點

Fescar - RM InsertExecutor介紹

開篇

 這篇文章的目的是講解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有了一些深入研究的興趣。