通用 Mapper 的批量插入实现

发布时间 2023-05-22 09:52:25作者: FatalFlower

具体的 SQL 模板实现如下所示:

import org.apache.ibatis.mapping.MappedStatement;
import tk.mybatis.mapper.MapperException;
import tk.mybatis.mapper.entity.EntityColumn;
import tk.mybatis.mapper.mapperhelper.EntityHelper;
import tk.mybatis.mapper.mapperhelper.MapperHelper;
import tk.mybatis.mapper.mapperhelper.SelectKeyHelper;
import tk.mybatis.mapper.mapperhelper.SqlHelper;
import tk.mybatis.mapper.provider.base.BaseInsertProvider;

import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author lxh
 */
public class GoodsInsertProvider extends BaseInsertProvider {

    public GoodsInsertProvider(Class<?> mapperClass, MapperHelper mapperHelper) {
        super(mapperClass, mapperHelper);
    }

    protected final static String VAR_REGEX = "#\\{(.+)}";

    public String saveAll(MappedStatement ms) {
        Class<?> entityClass = getEntityClass(ms);
        StringBuilder sql = new StringBuilder();
        //获取全部列
        Set<EntityColumn> columnList = EntityHelper.getColumns(entityClass);
        EntityColumn logicDeleteColumn = SqlHelper.getLogicDeleteColumn(entityClass);
        sql.append(SqlHelper.insertIntoTable(entityClass, tableName(entityClass)));
        sql.append(SqlHelper.insertColumns(entityClass, false, false, false));
        sql.append("<trim prefix=\"VALUES \" suffixOverrides=\",\">");
        sql.append("<foreach collection=\"collection\" item=\"item\" separator=\",\" >");
        processKey(sql, entityClass, ms, columnList);
        sql.append("<trim prefix=\"(\" suffix=\")\" suffixOverrides=\",\">");
        for (EntityColumn column : columnList) {
            if (!column.isInsertable()) {
                continue;
            }
            if (logicDeleteColumn != null && logicDeleteColumn == column) {
                sql.append(SqlHelper.getLogicDeletedValue(column, false)).append(",");
                continue;
            }
            String tmp;
            //优先使用传入的属性值,当原属性property!=null时,用原属性
            //自增的情况下,如果默认有值,就会备份到property_cache中,所以这里需要先判断备份的值是否存在
            //其他情况值仍然存在原property中
            if (column.isIdentity()) {
                tmp = SqlHelper.getIfCacheNotNull(column,
                        column.getColumnHolder(null, "_cache", ","));
                sql.append(tmp);
            } else {
                //其他情况值仍然存在原property中
                tmp = SqlHelper.getIfNotNull(column,
                        column.getColumnHolder(null, null, ","), isNotEmpty());
                sql.append(replaceByRegex(tmp, VAR_REGEX, "item.", true));
            }
            // tmp = SqlHelper.getIfNotNull(column,
            //        column.getColumnHolder(null, null, ","), isNotEmpty());

            //当属性为null时,如果存在主键策略,会自动获取值,如果不存在,则使用null
            //当null的时候,如果不指定jdbcType,oracle可能会报异常,指定VARCHAR不影响其他
            if (column.isIdentity()) {
                tmp = SqlHelper.getIfCacheIsNull(column, column.getColumnHolder() + ",");
                sql.append(replaceByRegex(tmp, "#\\{(.+})", "item.", true));
            } else {
                //当null的时候,如果不指定jdbcType,oracle可能会报异常,指定VARCHAR不影响其他
                tmp = SqlHelper.getIfIsNull(column,
                        column.getColumnHolder(null, null, ","), isNotEmpty());
                sql.append(replaceByRegex(tmp, VAR_REGEX, "item.", true));
            }
            // tmp = SqlHelper.getIfIsNull(column,
            //        column.getColumnHolder(null, null, ","), isNotEmpty());
        }
        sql.append("</trim>");
        sql.append("</foreach>");
        sql.append("</trim>");
        return sql.toString();
    }

    protected void processKey(StringBuilder sql, Class<?> entityClass,
                              MappedStatement ms, Set<EntityColumn> columnList) {
        //Identity列只能有一个
        boolean hasIdentityKey = false;
        //先处理cache或bind节点
        for (EntityColumn column : columnList) {
            if (column.isIdentity()) {
                //这种情况下,如果原先的字段有值,需要先缓存起来,否则就一定会使用自动增长
                //这是一个bind节点
                String tmp = SqlHelper.getBindCache(column);
                sql.append(replaceByRegex(tmp, "value=\"(.+\")", "item.", true));
                //如果是Identity列,就需要插入selectKey
                //如果已经存在Identity列,抛出异常
                if (hasIdentityKey) {
                    //jdbc类型只需要添加一次
                    if (column.getGenerator() != null && "JDBC".equals(column.getGenerator())) {
                        continue;
                    }
                    throw new MapperException(ms.getId() + "对应的实体类"
                            + entityClass.getName() + "中包含多个MySql的自动增长列,最多只能有一个!");
                }
                //插入selectKey
                SelectKeyHelper.newSelectKeyMappedStatement(ms, column, entityClass, isBEFORE(), getIDENTITY(column));
                hasIdentityKey = true;
            } else if (column.getGenIdClass() != null) {
                sql.append("<bind name=\"").append(column.getColumn())
                        .append("GenIdBind\" value=\"@tk.mybatis.mapper.genid.GenIdUtil@genId(");
                sql.append("item").append(", '").append(column.getProperty()).append("'");
                sql.append(", @").append(column.getGenIdClass().getName()).append("@class");
                sql.append(", '").append(tableName(entityClass)).append("'");
                sql.append(", '").append(column.getColumn()).append("')");
                sql.append("\"/>");
            }
        }
    }

    public static String replaceByRegex(String rawStr, String regex,
                                        String content, boolean et) {
        if (rawStr == null || rawStr.isEmpty()) {
            return rawStr;
        }
        if (!et) {
            return rawStr.replaceAll(regex, content);
        }

        Pattern pat = Pattern.compile(regex);
        Matcher matcher = pat.matcher(rawStr);
        while (matcher.find()) {
            if (matcher.groupCount() >= 1) {
                String group = matcher.group(1);
                rawStr = rawStr.replace(group, content + group);
            }
        }
        return rawStr;
    }
}