batchSave()插入数据时没有ignore/replace的支持

批量保存时如果数据库某个字段设置唯一索引,在插入时会导致整个列表都插入失败,希望后边的版本添加支持;

目前临时解决方案,大部分是源码,有轻微改动:

public class DbProUtil {
    /**
     * 
     * @param db                数据库别名   
     * @param tableName         表名
     * @param recordList        数据集合
     * @param batchSize         一次插入数据数量
     * @return                  受影响行数
     */
    public int[] ignoreBatchSave(String db, String tableName, List<Record> recordList, int batchSize) {
        if (recordList == null || recordList.size() == 0) {
            return  new int[0];
        }

        Record record = recordList.get(0);
        Map<String, Object> cols = record.getColumns();
        int index = 0;
        StringBuilder columns = new StringBuilder();
        // the same as the iterator in Dialect.forDbSave() to ensure the order of the columns
        for (Map.Entry<String, Object> e : cols.entrySet()) {
            if (index++ > 0) {
                columns.append(',');
            }
            columns.append(e.getKey());
        }
        String[] pKeysNoUse = new String[0];
        StringBuilder sql = new StringBuilder();
        List<Object> parasNoUse = new ArrayList<Object>();
        forDbSave(tableName, pKeysNoUse, record, sql, parasNoUse);
        return Db.use(db).batch(sql.toString(), columns.toString(), recordList, batchSize);
    }

    /**
     * Do not delete the String[] pKeys parameter, the element of pKeys needs to trim()
     */
    private void forDbSave(String tableName, String[] pKeys, Record record, StringBuilder sql, List<Object> paras) {
        tableName = tableName.trim();
        trimPrimaryKeys(pKeys);    // important

        sql.append("insert ignore into `");
        sql.append(tableName).append("`(");
        StringBuilder temp = new StringBuilder();
        temp.append(") values(");

        for (Map.Entry<String, Object> e: record.getColumns().entrySet()) {
            if (paras.size() > 0) {
                sql.append(", ");
                temp.append(", ");
            }
            sql.append('`').append(e.getKey()).append('`');
            temp.append('?');
            paras.add(e.getValue());
        }
        sql.append(temp.toString()).append(')');
    }

    /**
     * 一、forDbXxx 系列方法中若有如下两种情况之一,则需要调用此方法对 pKeys 数组进行 trim():
     * 1:方法中调用了 isPrimaryKey(...):为了防止在主键相同情况下,由于前后空串造成 isPrimaryKey 返回 false
     * 2:为了防止 tableName、colName 与数据库保留字冲突的,添加了包裹字符的:为了防止串包裹区内存在空串
     *   如 mysql 使用的 "`" 字符以及 PostgreSql 使用的 "\"" 字符
     * 不满足以上两个条件之一的 forDbXxx 系列方法也可以使用 trimPrimaryKeys(...) 方法让 sql 更加美观,但不是必须
     *
     * 二、forModelXxx 由于在映射时已经trim(),故不再需要调用此方法
     */
    private void trimPrimaryKeys(String[] pKeys) {
        for (int i=0; i<pKeys.length; i++) {
            pKeys[i] = pKeys[i].trim();
        }
    }
}


评论区

chcode

2019-09-17 19:02

胡说 我replace into 用的好好的

Fcmmy

2019-09-18 10:50

@chcode 像下边这样自己拼接sql批量执行时可以的,但没有提供一个类似batchSave(String tableName, List recordList, int batchSize)不需要自己拼接sql,直接传List的支持;
StringBuffer sb = new StringBuffer(" insert ignore into " + tableName + "(");
StringBuffer val = new StringBuffer();
StringBuffer col = new StringBuffer();
for (int i = 0; i < columns.length; i++) {
if (i == columns.length - 1) {
col.append(columns[i]);
val.append("?");
} else {
col.append(columns[i]).append(",");
val.append("?,");
}
}
sb.append(col).append(") values(").append(val).append(" ) ");

int[] count = Db.use(db).batch(sb.toString(), col.toString(), newsrcList, 5000);

chcode

2019-09-18 13:19

Fcmmy

2019-09-18 15:05

@chcode 求教,具体是哪个方法

chcode

2020-11-19 09:44

@Fcmmy batch 带sql 的方法,自己写replace into 的sql 语句