Le développement secondaire DataX prend en charge les données de mise à jour Oracle

Revue précédente :
"Explication détaillée de l'installation et de l'utilisation de DataX et DataX-Web"
"Débogage et empaquetage du code source DataX"
"Débogage et empaquetage du code source DataX-Web"

À l'heure actuelle, de nombreuses bases de données courantes prennent en charge le mode de mise à jour de clé en double (mise à jour des données lorsque la clé primaire est en conflit), et DataX prend également en charge la configuration du mode d'écriture en configurant writeMode. Mais actuellement, Oracle ne prend en charge que les éléments de configuration d'insertion .

Comment s'adapter à la base de données Oracle en mode de mise à jour de clé en double, aujourd'hui Daxiechao vous emmènera pour le développement secondaire.


1. Principe

La couche inférieure des éléments de configuration d'insertion, de remplacement et de mise à jour de writeMode utilise INSERT INTOREPLACE INTO/INSERT INTO … ON DUPLICATE KEY UPDATEl'instruction :

Parmi eux, insert into n'écrira pas les lignes en conflit lorsque la clé primaire/index unique est en conflit ; les deux derniers ont le même comportement que insert into lorsqu'il n'y a pas de conflit clé primaire/index unique, et remplaceront la ligne d'origine par une nouvelle ligne en cas de conflit tous les champs.

oracle ne prend pas en charge MySQL-like REPLACE INTOand INSERT … ON DUPLICATE KEY UPDATE, donc seuls les éléments de configuration d'insertion sont pris en charge. Pour réaliser cette fonction, vous devez utiliser l'instruction de fusion d'Oracle, examinons d'abord la syntaxe de fusion.

MERGE INTO [target-table] A USING [source-table sql] B 
ON([conditional expression] and [...]...) 
WHEN MATCHED THEN
 [UPDATE sql] 
WHEN NOT MATCHED THEN 
 [INSERT sql]

La syntaxe de fusion est en fait mise à jour si elle existe et insérée si elle n'existe pas.

Exemple:

MERGE INTO USERS A USING ( SELECT 18 AS "ID",'chaodev' AS "USER_ID" FROM DUAL ) TMP 
ON (TMP."ID" = A."ID" AND TMP."USER_ID" = A."USER_ID" ) 
WHEN MATCHED THEN 
UPDATE SET "USER_NAME" = '大佬超',"USER_PHONE" = '18000000000',"LASTUPDATETIME" = SYSDATE 
WHEN NOT MATCHED THEN 
INSERT ("ID","USER_ID","USER_NAME","USER_PHONE","LASTUPDATETIME") VALUES(18,'chaodev','大佬超','18000000000',SYSDATE)

Ainsi, le principe d'implémentation final est le suivant : modifiez le code source oraclewriter de datax et réalisez la sémantique UPSERT via la fusion dans l'instruction.


2. Modification du code source

Les classes et les méthodes impliquées dans la modification sont les suivantes :

paquet oraclewriter :

com.alibaba.datax.plugin.writer.oraclewriter.OracleWriter: Modifié pour permettre à l'utilisateur de configurer writeMode.

paquet plugin-dbms-util :

com.alibaba.datax.plugin.rdbms.writer.util.WriterUtil: Augmenter le code logique d'oralce.

com.alibaba.datax.plugin.rdbms.writer.CommonRdbmsWriter: La classe CommonRdbmsWriter.Task remplace les méthodes startWriteWithConnection(), doBatchInsert() et fillPreparedStatement().


2.1 Restrictions d'annotation OracleWriter sur writeMode

insérez la description de l'image ici


2.2 WriterUtil, ajouter la logique oracle

insérez la description de l'image ici

Ajoutez le code logique d'Oracle, comme suit

/**
* 新增oracle update模块
* @author 程序员大佬超
* @date 20221202
* @param columnHolders
* @param valueHolders
* @param writeMode
* @param dataBaseType
* @param forceUseUpdate
* @return
*/
public static String getWriteTemplate(List<String> columnHolders, List<String> valueHolders,
                                      String writeMode, DataBaseType dataBaseType, boolean forceUseUpdate)
{
    
    
    String mode = writeMode.trim().toLowerCase();
    boolean isWriteModeLegal = mode.startsWith("insert") || mode.startsWith("replace") || mode.startsWith("update");

    if (!isWriteModeLegal) {
    
    
        throw DataXException.asDataXException(DBUtilErrorCode.ILLEGAL_VALUE,
                                              String.format("您所配置的 writeMode:%s 错误. 因为DataX 目前仅支持replace,update 或 insert 方式. 请检查您的配置并作出修改.", writeMode));
    }
    String writeDataSqlTemplate;
    if (forceUseUpdate || mode.startsWith("update")) {
    
    
        if (dataBaseType == DataBaseType.MySql || dataBaseType == DataBaseType.Tddl) {
    
    
            writeDataSqlTemplate = new StringBuilder()
                .append("INSERT INTO %s (").append(StringUtils.join(columnHolders, ","))
                .append(") VALUES(").append(StringUtils.join(valueHolders, ","))
                .append(")")
                .append(onDuplicateKeyUpdateString(columnHolders))
                .toString();
        }
        else if (dataBaseType == DataBaseType.Oracle) {
    
    
            writeDataSqlTemplate = new StringBuilder().append(onMergeIntoDoString(writeMode, columnHolders, valueHolders)).append("INSERT (")
                .append(StringUtils.join(columnHolders, ","))
                .append(") VALUES(").append(StringUtils.join(valueHolders, ","))
                .append(")").toString();
        }
        else {
    
    
            throw DataXException.asDataXException(DBUtilErrorCode.ILLEGAL_VALUE,
                                                  String.format("当前数据库不支持 writeMode:%s 模式.", writeMode));
        }
    }
    else {
    
    
        //这里是保护,如果其他错误的使用了update,需要更换为replace
        if (writeMode.trim().toLowerCase().startsWith("update")) {
    
    
            writeMode = "replace";
        }
        writeDataSqlTemplate = new StringBuilder().append(writeMode)
            .append(" INTO %s (").append(StringUtils.join(columnHolders, ","))
            .append(") VALUES(").append(StringUtils.join(valueHolders, ","))
            .append(")").toString();
    }

    return writeDataSqlTemplate;
}

La méthode d'appel est ajoutée comme suit, principalement pour coller l'instruction merge :

public static String onMergeIntoDoString(String merge, List<String> columnHolders, List<String> valueHolders) {
    
    
    String[] sArray = getStrings(merge);
    StringBuilder sb = new StringBuilder();
    sb.append("MERGE INTO %s A USING ( SELECT ");

    boolean first = true;
    boolean first1 = true;
    StringBuilder str = new StringBuilder();
    StringBuilder update = new StringBuilder();
    for (String columnHolder : columnHolders) {
    
    
        if (Arrays.asList(sArray).contains(columnHolder)) {
    
    
            if (!first) {
    
    
                sb.append(",");
                str.append(" AND ");
            } else {
    
    
                first = false;
            }
            str.append("TMP.").append(columnHolder);
            sb.append("?");
            str.append(" = ");
            sb.append(" AS ");
            str.append("A.").append(columnHolder);
            sb.append(columnHolder);
        }
    }

    for (String columnHolder : columnHolders) {
    
    
        if (!Arrays.asList(sArray).contains(columnHolder)) {
    
    
            if (!first1) {
    
    
                update.append(",");
            } else {
    
    
                first1 = false;
            }
            update.append(columnHolder);
            update.append(" = ");
            update.append("?");
        }
    }

    sb.append(" FROM DUAL ) TMP ON (");
    sb.append(str);
    sb.append(" ) WHEN MATCHED THEN UPDATE SET ");
    sb.append(update);
    sb.append(" WHEN NOT MATCHED THEN ");
    return sb.toString();
}

public static String[] getStrings(String merge) {
    
    
    merge = merge.replace("update", "");
    merge = merge.replace("(", "");
    merge = merge.replace(")", "");
    merge = merge.replace(" ", "");
    return merge.split(",");
}

2.3 Modification de CommonRdbmsWriter.Task

Modifier la méthode startWriteWithConnection()

/**
* 更改适配oracle update
* @author 程序员大佬超
* @date 20221202
* @param recordReceiver
* @param taskPluginCollector
* @param connection
*/
public void startWriteWithConnection(RecordReceiver recordReceiver, TaskPluginCollector taskPluginCollector, Connection connection)
{
    
    
    this.taskPluginCollector = taskPluginCollector;
    List<String> mergeColumns = new ArrayList<>();

    if (this.dataBaseType == DataBaseType.Oracle && !"insert".equalsIgnoreCase(this.writeMode)) {
    
    
        LOG.info("write oracle using {} mode", this.writeMode);
        List<String> columnsOne = new ArrayList<>();
        List<String> columnsTwo = new ArrayList<>();
        String merge = this.writeMode;
        String[] sArray = WriterUtil.getStrings(merge);
        for (String s : this.columns) {
    
    
            if (Arrays.asList(sArray).contains(s)) {
    
    
                columnsOne.add(s);
            }
        }
        for (String s : this.columns) {
    
    
            if (!Arrays.asList(sArray).contains(s)) {
    
    
                columnsTwo.add(s);
            }
        }
        int i = 0;
        for (String column : columnsOne) {
    
    
            mergeColumns.add(i++, column);
        }
        for (String column : columnsTwo) {
    
    
            mergeColumns.add(i++, column);
        }
    }
    mergeColumns.addAll(this.columns);

    // 用于写入数据的时候的类型根据目的表字段类型转换
    this.resultSetMetaData = DBUtil.getColumnMetaData(connection,
                                                      this.table, StringUtils.join(mergeColumns, ","));
    // 写数据库的SQL语句
    calcWriteRecordSql();

    List<Record> writeBuffer = new ArrayList<>(this.batchSize);
    int bufferBytes = 0;
    try {
    
    
        Record record;
        while ((record = recordReceiver.getFromReader()) != null) {
    
    
            if (record.getColumnNumber() != this.columnNumber) {
    
    
                // 源头读取字段列数与目的表字段写入列数不相等,直接报错
                throw DataXException
                    .asDataXException(
                    DBUtilErrorCode.CONF_ERROR,
                    String.format(
                        "列配置信息有错误. 因为您配置的任务中,源头读取字段数:%s 与 目的表要写入的字段数:%s 不相等. 请检查您的配置并作出修改.",
                        record.getColumnNumber(),
                        this.columnNumber));
            }

            writeBuffer.add(record);
            bufferBytes += record.getMemorySize();

            if (writeBuffer.size() >= batchSize || bufferBytes >= batchByteSize) {
    
    
                doBatchInsert(connection, writeBuffer);
                writeBuffer.clear();
                bufferBytes = 0;
            }
        }
        if (!writeBuffer.isEmpty()) {
    
    
            doBatchInsert(connection, writeBuffer);
            writeBuffer.clear();
        }
    }
    catch (Exception e) {
    
    
        throw DataXException.asDataXException(
            DBUtilErrorCode.WRITE_DATA_ERROR, e);
    }
    finally {
    
    
        writeBuffer.clear();
        DBUtil.closeDBResources(null, null, connection);
    }
}

Modifier la méthode doBatchInsert()

/**
* 更改适配oracle update
* @author 程序员大佬超
* @date 20221202
* @param connection
* @param buffer
* @throws SQLException
*/
protected void doBatchInsert(Connection connection, List<Record> buffer)
    throws SQLException
{
    
    
    PreparedStatement preparedStatement = null;
    try {
    
    
        connection.setAutoCommit(false);
        preparedStatement = connection
            .prepareStatement(this.writeRecordSql);
        if (this.dataBaseType == DataBaseType.Oracle && !"insert".equalsIgnoreCase(this.writeMode)) {
    
    
            String merge = this.writeMode;
            String[] sArray = WriterUtil.getStrings(merge);
            for (Record record : buffer) {
    
    
                List<Column> recordOne = new ArrayList<>();
                for (int j = 0; j < this.columns.size(); j++) {
    
    
                    if (Arrays.asList(sArray).contains(this.columns.get(j))) {
    
    
                        recordOne.add(record.getColumn(j));
                    }
                }
                for (int j = 0; j < this.columns.size(); j++) {
    
    
                    if (!Arrays.asList(sArray).contains(this.columns.get(j))) {
    
    
                        recordOne.add(record.getColumn(j));
                    }
                }
                for (int j = 0; j < this.columns.size(); j++) {
    
    
                    recordOne.add(record.getColumn(j));
                }
                for (int j = 0; j < recordOne.size(); j++) {
    
    
                    record.setColumn(j, recordOne.get(j));
                }
                preparedStatement = fillPreparedStatement(
                    preparedStatement, record);
                preparedStatement.addBatch();
            }
        }
        else {
    
    
            for (Record record : buffer) {
    
    
                preparedStatement = fillPreparedStatement(
                    preparedStatement, record);
                preparedStatement.addBatch();
            }
        }
        preparedStatement.executeBatch();
        connection.commit();
    }
    catch (SQLException e) {
    
    
        LOG.warn("回滚此次写入, 采用每次写入一行方式提交. 因为: {}", e.getMessage());
        connection.rollback();
        doOneInsert(connection, buffer);
    }
    catch (Exception e) {
    
    
        throw DataXException.asDataXException(
            DBUtilErrorCode.WRITE_DATA_ERROR, e);
    }
    finally {
    
    
        DBUtil.closeDBResources(preparedStatement, null);
    }
}

Modifier la méthode fillPreparedStatement()

/**
* 更改适配oracle update
* @author 程序员大佬超
* @date 20221202
* @param preparedStatement
* @param record
* @return
* @throws SQLException
*/
protected PreparedStatement fillPreparedStatement(PreparedStatement preparedStatement, Record record)
    throws SQLException
{
    
    
    for (int i = 0; i < record.getColumnNumber(); i++) {
    
    
        int columnSqltype = this.resultSetMetaData.getMiddle().get(i);
        preparedStatement = fillPreparedStatementColumnType(preparedStatement, i,
                                                            columnSqltype, record.getColumn(i));
    }
    return preparedStatement;
}

2.4 Tests

Après le reconditionnement, testez le travail

{
    
    
  "job": {
    
    
    "setting": {
    
    
      "speed": {
    
    
        "channel": 3,
        "byte": 1048576
      },
      "errorLimit": {
    
    
        "record": 0,
        "percentage": 0.02
      }
    },
    "content": [
      {
    
    
        "reader": {
    
    
          "name": "mysqlreader",
          "parameter": {
    
    
            "username": "root",
            "password": "123456",
            "column": [
              "`id`",
              "`user_id`",
              "`user_password`",
              "`user_name`",
              "`user_phone`",
              "`email`",
              "`nick_name`",
              "`head_url`",
              "`sex`",
              "`state`",
              "`create_time`",
              "`create_user`",
              "`lastUpdateTime`"
            ],
            "splitPk": "",
            "connection": [
              {
    
    
                "table": [
                  "users"
                ],
                "jdbcUrl": [
                  "jdbc:mysql://127.0.0.1:3306/im"
                ]
              }
            ]
          }
        },
        "writer": {
    
    
          "name": "oraclewriter",
          "parameter": {
    
    
            "username": "yxc",
            "password": "123456",
            "column": [
              "\"ID\"",
              "\"USER_ID\"",
              "\"USER_PASSWORD\"",
              "\"USER_NAME\"",
              "\"USER_PHONE\"",
              "\"EMAIL\"",
              "\"NICK_NAME\"",
              "\"HEAD_URL\"",
              "\"SEX\"",
              "\"STATE\"",
              "\"CREATE_TIME\"",
              "\"CREATE_USER\"",
              "\"LASTUPDATETIME\""
            ],
            "writeMode": "update(\"ID\",\"USER_ID\")",
            "connection": [
              {
    
    
                "table": [
                  "USERS"
                ],
                "jdbcUrl": "jdbc:oracle:thin:@//192.168.157.142:1521/orcl"
              }
            ]
          }
        }
      }
    ]
  }
}

Remarque : Les champs dans les parenthèses de mise à jour de writeMode doivent être ajoutés\"

En regardant le journal en cours d'exécution, vous pouvez voir que l'instruction MERGE a été correctement épissée :
insérez la description de l'image ici

insérez la description de l'image ici

Tâche exécutée avec succès. Basculer entre l'inspection du mode d'insertion et de mise à jour, aucune anomalie.



Pour les produits secs plus techniques, veuillez continuer à faire attention au programmeur Dachao.
L'originalité n'est pas facile, veuillez indiquer la source pour la réimpression.

Je suppose que tu aimes

Origine blog.csdn.net/xch_yang/article/details/128250190
conseillé
Classement