Mybatis: 兼容Oracle数据库批量插入语句

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 当前项目需要在不同环境下部署,不同环境下有不同的数据库,有pg、oracle、mysql等,项目中的所有sql均为pg数据库相关的sql,由于oracle数据库比较特殊所以需要兼容相关的sql。批量插入的语句,pg和oracle有着较大的差别,不能同一条语句兼顾两种数据库:,所以需要查找方案来解决。

背景

当前项目需要在不同环境下部署,不同环境下有不同的数据库,有pg、oracle、mysql等,项目中的所有sql均为pg数据库相关的sql,由于oracle数据库比较特殊所以需要兼容相关的sql。批量插入的语句,pg和oracle有着较大的差别,不能同一条语句兼顾两种数据库:,所以需要查找方案来解决。

pg/mysql的批量插入sql的Mybatis写法【平常的mysql写法】:

INSERT INTO
        table1
        (
        item1,item2
        )
        VALUES
        <foreach collection="list" item="item" index="index" separator=",">  
        (
        #{item.item1},#{item.item2}
        )
       </foreach>

oracle批量插入sql的Mybat写法【需要 select from dual 来编写, 使用 union 分割 】:

INSERT  INTO
    table1
    (
    item1,item2
    )
    <foreach  collection="list" item="item" index="index" separator="union">  
        select
        #{item.item1},#{item.item2}
        from dual
   </foreach>

方法1

网上教程:https://blog.csdn.net/qq_35981283/article/details/79503571

Mybatis标签中有个databaseId可以指定对应的数据库,可以兼容多种数据库,但是需要写两条sql来兼容,目前项目中有太多太多的批量插入语句了,手动复制粘贴的话然后再加入对应的语句的话,太耗人力了,说不定复制个三天三夜都复制不完,而且会出错,到时候哭天抢地就来不及了。所以重复性的工作为何不交给程序来解决呢。

方法2

暗自思考(菜鸡的想法就是这么单纯):

1.项目都是用的Mybatis来搭建的,而Mybatis能兼容oracle、mysql等数据库,这么强大的一个东西,总能有些扩展或者插件啥的吧

2.假设能扩展吧,是不是能够拦截到对应的sql然后修改对应的sql然后再让Mybatis按照我修改之后的sql来执行了呢?赶紧百度起来吧。

emmm,百度之后,果然有这个东西,Mybatis提供了一个Interceptor的东西让使用者进行一系列的操作,类似于Spring的Aop,哦豁,赶紧动起来!

这时得回想起我们的目的啦,我们的目的是把批量的插入的语句在oracle的数据库下转为对应oracle的插入语句,我们不妨设为以下的步骤

1.判断是不是oracle环境,不是的话自然而然啥也不用操作啦

2.获取到对应的sql,判断是不是插入的sql,不是插入sql同样啥也不同操作

3.进行一系列的操作,获取到对应的oracle批量插入的sql然后给Mybatis执行

4.执行后续的操作

如果需要直接看代码的话,直接点左边的目录栏即可

话不多说,先上主体代码

SqlInterceptor【增加了各个语句的兼容】

@Component
@Slf4j
@Order(1)
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class SqlConvertInterceptor implements Interceptor {

  @Override
  public Object intercept(Invocation invocation) throws Throwable {
    //当前业务,兼容pg 和 oracle,需要兼容oracle的批量插入语句

    // 如果当前的数据库不是oracle,则直接放行
    // 【如果后面有其他数据库兼容,则加入到对应的枚举类中】
    SqlConvertEnum convertorEnum = SqlConvertEnum.findConvertorByType(DbUtil.getDatabaseType());
    if (ObjectUtils.isEmpty(convertorEnum)) {
      log.info("----当前是{}数据库,跳过sql转化----", DbUtil.getDatabaseType());
      return invocation.proceed();
    }

    //获取 StatementHandler 和 MappedStatement
    StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
    MetaObject metaObject = MetaObject
            .forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,
                    new DefaultReflectorFactory());

    MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");

    // 如果已经兼容了对应的数据库也不用转化
    // 如果不是插入语句则不需要转化
    // 【如果后续需要进行其他转化,再创建自己的sql类型枚举和接口方法,这里只有批量插入语句需要转化】
    if (convertorEnum.getDataBaseId().equals(mappedStatement.getDatabaseId())) {
      return invocation.proceed();
    }

    //获取BoundSql (Mybatis转化过后的Sql在这里)
    BoundSql boundSql = statementHandler.getBoundSql();
    //设置兼容后的插入语句【使用反射进行设置值】
    Field declaredField = boundSql.getClass().getDeclaredField("sql");
    declaredField.setAccessible(true);
    declaredField.set(boundSql, getConvertSql(boundSql.getSql(), convertorEnum.getConverterClass(), mappedStatement.getSqlCommandType()));

    return invocation.proceed();
  }

  /**
   * 获取转化sql
   * @param sql
   * @param covertCLass
   * @param sqlCommandType
   * @return
   */
  private String getConvertSql(String sql, Class<? extends SqlConverter> covertCLass, SqlCommandType sqlCommandType) {
    SqlConverter sqlConverter = SpringUtil.getBean(covertCLass);
    switch (sqlCommandType) {
      case INSERT :
        return sqlConverter.convertInsert(sql);
      case DELETE:
        return sqlConverter.convertDelete(sql);
      case UPDATE:
        return sqlConverter.convertUpdate(sql);
      case SELECT:
        return sqlConverter.convertSelect(sql);
      default:
        return sql;
    }


  }


}

Intercepts注解

既然类似于Aop,那总得有切点切面吧,所以在网上的文章里看到这个注解,指定了对应的Class,方法名,方法的入参,对反射熟悉的同学应该就知道这个就是方法的签名,而注解的命名也很形象地命名为了签名,当前这个拦截器的先拦截的是RoutingStatementHandler。

@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})

这个拦截器注解能按顺序拦到的是 newExecutor -> StatementHandler -> ParameterHandler -> ResultSetHandler -> StatementHandler,这里就不展开讲了,想了解可以阅读文章:https://www.cnblogs.com/FraserYu/p/11044062.html

1.判断是不是oracle数据库

判断是否是oracle数据,使用项目中的一个工具类判断是不是oracle,

如果是单数据源的话也可以用jdbc的url前缀来判断是否是oracle数据库

SqlInterceptorEnum 这个枚举是用于日后的扩展使用的,里面添加不同的数据库类型,便于不同数据库的兼容

    // --1
    // 如果当前的数据库不是oracle,则直接放行
    // 【如果后面有其他数据库兼容,则加入到对应的枚举类中】
    SqlConvertEnum convertorEnum = SqlConvertEnum.findConvertorByType(DbUtil.getDatabaseType());
    if (ObjectUtils.isEmpty(convertorEnum)) {
      log.info("----当前是{}数据库,跳过sql转化----", DbUtil.getDatabaseType());
      return invocation.proceed();
    }

SqlConvertEnum

【如果是自行实现数据库类型的话,可以把databaseType设为jdbc的前缀】

public enum SqlConvertEnum {
    /**
     * Oracle 数据库
     */
    ORACLE(DatabaseType.ORACLE, OracleSqlConverter.class);
    /**
     * 数据库类型
     */
    private DatabaseType databaseType;

    /**
     * sql转换class
     */
    private Class<? extends SqlConverter> converterClass;
    
    /**
    * databaseId 用于判断sql的执行数据库
    */
    private String databaseId;

    SqlConvertEnum(DatabaseType dataBaseType, Class<? extends SqlConverter> converterClass) {
        this.databaseType = dataBaseType;
        this.converterClass = converterClass;
    }

    public Class<? extends SqlConverter> getConverterClass() {
        return this.converterClass;
    }

    /**
     * 根据对应的数据库类型获取对应的枚举
     * @param databaseType 数据库类型
     * @return 转化枚举
     */
    public static SqlConvertEnum findConvertorByType(DatabaseType databaseType) {
        for (SqlConvertEnum sqlConvertEnum : SqlConvertEnum.values()) {
            if (sqlConvertEnum.databaseType.equals(databaseType)) {
                return sqlConvertEnum;
            }
        }
        return null;
    }


}

2.获取到对应的sql

判断是不是插入的sql,不是插入sql同样啥也不同操作

MappedStatement 里面有对应的sqlCommandType可以用来判断是什么类型的SQL,SqlCommandType这个枚举类是Mybatis自带的

    // --2
    //获取 StatementHandler 和 MappedStatement
    StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
    MetaObject metaObject = MetaObject
            .forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,
                    new DefaultReflectorFactory());

    //先拦截到RoutingStatementHandler,里面有个StatementHandler类型的delegate变量,其实现类是BaseStatementHandler,然后就到BaseStatementHandler的成员变量mappedStatement
    MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");

    // 如果不是插入语句则不需要转化
    // 【如果后续需要进行其他转化,再创建自己的sql类型枚举和接口方法,这里只有批量插入语句需要转化】
    if (!SqlCommandType.INSERT.equals(mappedStatement.getSqlCommandType())) {
      return invocation.proceed();
    }

3.获取对应oracle的sql并交给Mybatis执行【逻辑编辑一下即可】

    //--3
    //获取BoundSql (Mybatis转化过后的Sql在这里)
    BoundSql boundSql = statementHandler.getBoundSql();
    String sql = boundSql.getSql();

    //设置兼容后的插入语句【使用反射进行设置值】
    Field declaredField = boundSql.getClass().getDeclaredField("sql");
    declaredField.setAccessible(true);
    SqlConverter convertor = SpringUtil.getBean(convertorEnum.getConverterClass());
    declaredField.set(boundSql, convertor.convertInsert(sql));

由于后面关于转化太多内容啦,这边先讲一下反射设置值的问题,因为sql这个属性是私有的而且并没有对外开放set方法,所以只能将其设为公开的declaredField.setAccessible(true);,然后再进行赋值。

这个转化是这次的重头戏啦,所以,不妨让我来分析下整个的处理过程,以及我写这段代码的一些想法。

起初想着,这个拦截拦截到的sql是实实在在Mybatis已经转化好的完整SQL,还想着怎么防止sql注入的问题,怎么拿到括号中的value值,要是括号中的value值有多个括号,或者多个单引号岂不是GG,这样越写越大的话岂不是就成了数据库的sql解析器了。

自己想总会出错,然后百度了一下BoundSql中拿到的是啥,发现里面是Mybatis用问号代替变量值的sql,用于Mybatis自己后续处理的,这岂不是美滋滋,基本上只要处理括号和问号的问题就好了呀,然后防止程序员自己写入的带括号的字符串常量就可,真的天助我也,不用处理sql注入的问题就已经很好了。

接下来的代码可能会有点长【已经只截方法了】,忍一下,看看呗,也不难。

这段代码主要是用来拼接sql的,基本上并不难,重点在于CommonSqlUtil.getValues(sql); 方法,拼接sql的解析请看下面分解

每日一个小技巧,如果在编码中出现大量的字符串拼接,就不要用+号了,直接使用StringBuilder吧,因为每次+号都会生成一个StringBuilder再append,到不如直接使用StringBuilder来得容易

    /**
     * ORACLE 批量插入需要的关键字拼接
     */
    private static final String SELECT_STR = "SELECT ";
    private static final String INSERT_END_STR = " FROM DUAL ";
    private static final String UNION_STR = "UNION ";

//转化insert语句
public String convertInsert(String sql) {
        //用oracle中的批量语句代替
        //查找values的位置,将后面全部括号里的东西取出,然后再用对应的数据进行封装
        //获取前面的sql,这段sql与Oracle的相同
        String prefix = CommonSqlUtil.getInsertPrefix(sql);

        //获取insert语句中要插入的值的列表
        List<String> valueList = CommonSqlUtil.getValues(sql);

        //如果只有一条值的,则返回原sql,不用拼接
        if (valueList.size() == 1) {
            return sql;
        }

        //拼接sql
        StringBuilder sqlBuilder = new StringBuilder().append(prefix);
        boolean start = true;
        for (String value : valueList) {
            if (!start) {
                sqlBuilder.append(UNION_STR);
            }
            else {
                start = false;
            }
            sqlBuilder.append(SELECT_STR).append(value).append(INSERT_END_STR);
        }

        return sqlBuilder.toString();
    }

接下来贴上CommonSqlUtil的代码,首先来分析一下,【自己也可以先把代码看完了在来看分析】

分析:

我们再把两种sql拿下来对比(Mybatis处理后的sql)

pg / mysql:

INSERT INTO
        table1
        (
        item1,item2,item3
        )
        VALUES
        (
        ?,?,'00A'
        )
        (
        ?,?,'00A'
        )
        

oracle

INSERT  INTO
    table1
    (
    item1,item2,item3
    )
    select
    ?,?,'00A'
    from dual
    
    union
    
    select
    ?,?,'00A'
    from dual
  

根据一轮的分析后,发现,只要拿到pg的sql里的values中的值【也就是 ?,?,'00A'】,然后再用select from dual拼接起来,然后再用union分隔即可,所以上面拼接的代码就是把 select , , from dual, union 按Oracle的语法拼接起来 。

所以重点来了,怎么取到对应的values中值呢 ?

遇到这种规律sql的字符串,第一时间想到正则表达式,找了一圈,貌似没有什么结果,都说这个正则表达式很难写,要兼容很多种情况,所以,放弃了这种想法。

随后,只能自己写个算法来解决这个问题啦。

首先呢,得先排除table那边的括号,我们需要的是values关键字后面的括号,,所以只需要匹配第二个左括号即可

整个语句中,其实就只是问号,括号,逗号,常量值。问号,和常量值可以归为一类,是我们需要拿到的值,那么剩下的就是括号,用作分隔用的逗号是没用的。

所以就想到了用括号匹配的算法来实现【leetcode上面有这道题】,用一个栈来保存左括号【如果字符为左括号时则进栈】,当遇到右括号的时候将左括号弹出匹配,当栈为空时就完成了一个完整括号的匹配,则可记录一条关于value的String放入到对应的List中。

当然,根据sql的正确性,这个算法是相对简单的,但是目前这个算法还没有兼容 类似这种情况的常量 '(‘,就是常量中包含左括号,右括号的情况,所以如果需要兼容这种情况的话,需要程序员自己添加变量来添加,即用Mybatis中的来代替常量。

CommonSqlUtil

public final class CommonSqlUtil {
    private CommonSqlUtil() {

    }

    /**
     * 获取insert语句前缀,只获取到insert到最后一个括号
     * @param sql 当前sql
     * @return 获取insert语句前缀
     */
    public static String getInsertPrefix(String sql) {
        return sql.substring(0, sql.indexOf(")") + 1);
    }

    /**
     * 使用栈实现获取value中括号的值【当前用于oracle】
     * 【目前只处理了Mybatis中处理好sql中的问号以及字符串常量中没有括号的情况(如果需要有括号,自行用变量代替)】
     * 例子:(?,?,?,'00A') -> ?,?,?,'00A'
     *
     * @param sql 整条insert语句
     * @return value中括号的值
     */
    public static List<String> getValues(String sql) {
        //从第二个括号开始取值
        String subSql = sql.substring(sql.indexOf(")"));
        String valueSql = subSql.substring(subSql.indexOf("("));

        //获取value关键字后面括号中的值组成一个Stirng的list
        List<String> values = new ArrayList<>();
        Stack<Character> brackets = new Stack<>();
        StringBuilder splitValue = new StringBuilder();
        for (Character c : valueSql.toCharArray()) {
            //左括号进栈
            if ('(' == c) {
                brackets.push(c);
            }
            else if (')' == c) {
                //右括号则将左括号出栈,清空builder
                brackets.pop();
                values.add(splitValue.toString());
                splitValue.delete(0, splitValue.length());
            }
            else if (!brackets.empty()) {
                //只有进入括号中才将值放入,排除括号外的逗号
                splitValue.append(c);
            }
        }
        return values;

    }

}

4.执行后续的操作

这一步就是让Mybatis继续走自己调用的责任链啦,到这里就处理完成了。

// --4
    return invocation.proceed();

相关设计

为了符合设计模式的开闭模式,这里使用了工厂模式以及单例模式【配合Spring的Bean工厂使用】,这边使用SqlConvertEnum来管理对应的数据库类型以及转化服务的Class,以下展示对应的抽象接口类,以及对应的实现类【完整代码】。

SqlConverter

//Sql转化抽象类,提供了对应的抽象转化接口,可以转化select,update、insert、delete语句
public interface SqlConverter {
    /**
     * 转化select语句
     * @param sql 需要转化的sql
     * @return 转化后的sql
     */
    String convertSelect(String sql);

    /**
     * 转化update语句
     * @param sql 需要转化的sql
     * @return 转化后的sql
     */
    String convertUpdate(String sql);

    /**
     * 转化delete语句
     * @param sql 需要转化的sql
     * @return 转化后的sql
     */
    String convertDelete(String sql);

    /**
     * 转化insert语句
     * @param sql 需要转化的sql
     * @return 转化后的sql
     */
    String convertInsert(String sql);
}

OracleSqlConverter

//转化为oracle相关sql的实现类
@Service
public class OracleSqlConverter implements SqlConverter {
    /**
     * ORACLE 批量插入需要的关键字拼接
     */
    private static final String SELECT_STR = "SELECT ";
    private static final String INSERT_END_STR = " FROM DUAL ";
    private static final String UNION_STR = "UNION ";

    @Override
    public String convertSelect(String sql) {
        return sql;
    }

    @Override
    public String convertUpdate(String sql) {
        return sql;
    }

    @Override
    public String convertDelete(String sql) {
        return sql;
    }

    @Override
    public String convertInsert(String sql) {
        //用oracle中的批量语句代替
        //查找values的位置,将后面全部括号里的东西取出,然后再用对应的数据进行封装
        //获取前面的sql,这段sql与Oracle的相同
        String prefix = CommonSqlUtil.getInsertPrefix(sql);

        //获取insert语句中要插入的值的列表
        List<String> valueList = CommonSqlUtil.getValues(sql);

        //如果只有一条值的,则返回原sql,不用拼接
        if (valueList.size() == 1) {
            return sql;
        }

        //拼接sql
        StringBuilder sqlBuilder = new StringBuilder().append(prefix);
        boolean start = true;
        for (String value : valueList) {
            if (!start) {
                sqlBuilder.append(UNION_STR);
            }
            else {
                start = false;
            }
            sqlBuilder.append(SELECT_STR).append(value).append(INSERT_END_STR);
        }

        return sqlBuilder.toString();
    }
}

设计过程以及代码整理过程

其实好的代码都是一步步改过来的,就像写作文一般,需要不断地修改以及改进才能出现一篇好的作文。

刚开始的时候,我是将全部代码写在一个类里面,用main函数一步步调试转化函数,然后再连接上oracle数据库进行调试。

其实写完也就200行左右的代码,也对不同操作进行了函数分割【以下为没有调整过的一个拦截器,代码其实功能都符合业务需求,觉得没有必要看的可以不看那段代码,因为实在是烂代码,哈哈哈】。但是看着总觉得不对劲,总感觉这个类承担了他不应该承担的事,又进行sql的分析,又进行sql的转化,他的功能应该就是简简单单的四步才对,怎么干了那么多事呢?而且要是日后又要加select、update、delete的转化怎么办?万一又有别的数据库需要转化怎么办,那这代码岂不是越来越冗长,加的功能越来越多?想想,还是别留那么大的坑给后面的人吧,自己辛苦点做点可扩展啥的,也算是一种锻炼,所以做了以下的分析:

1.原本sql的分析,可以抽出来一个工具类,因为有可能其他的服务也需要

2.不同数据库的不同语句的转化,可以抽象成一个接口类,可以有不同数据库不同语句的实现,拦截器调用时也能抽象地调用接口即可,不用关注实现。

3.一些常量的设置,当前只是判断了oracle的数据库,而且是用jdbc的前缀来判断的,可以使用枚举类来做到扩展数据库类型时不必修改拦截器代码,同时也可以映射对应实现类,让一个枚举该做到的都做了,可以减少代码的增加以及修改。

4.可以使用源码中有的常量,比如sql的类型在mybatis中肯定会有判断,所以,可以使用SqlCommandType来判断语句的类型

5.寻找Value关键字部分可以使用一个List 来管理需要查找的值,循环查找即可,这样也可以避免一直if-else下去。

总结:工厂模式+单例模式+反射 可以解决大部分的可扩展以及多态问题,可以很好地让代码符合开闭原则,用起来!

OracleSqlInterceptor【原始未修改整理过的代码】

@Component
@Slf4j
@Order(1)
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class OracleSqlInterceptor implements Interceptor {

  @Value("${jdbc.url}")
  private String jdbcType;

  @Override
  public Object intercept(Invocation invocation) throws Throwable {
    //当前业务,兼容pg 和 oracle,需要兼容oracle的批量插入语句
    StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
    MetaObject metaObject = MetaObject
            .forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,
                    new DefaultReflectorFactory());

    MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
    //如果当前的数据库不是oracle,则直接放行

    if (!jdbcType.startsWith("jdbc:oracle:")) {
        log.info("----当前不是oracle数据库,跳过转化----");
      return invocation.proceed();
    }

    BoundSql boundSql = statementHandler.getBoundSql();
    String sql = boundSql.getSql();
    
    //如果不是插入语句则放行【仅支持全大写或全小写的情况】
    if (!sql.startsWith("insert") || !sql.startsWith("INSERT")) {
      return invocation.proceed();
    }

    //开始兼容批量插入语句,并设置boundsql
    Field declaredField = boundSql.getClass().getDeclaredField("sql");
    declaredField.setAccessible(true);
    declaredField.set(boundSql, convertOracleInsertSql(sql));
    log.info("---转换后的sql为:{}", boundSql.getSql());

    return invocation.proceed();
  }

  /**
   * Oracle Insert语句转化
   *
   * @param sql 传入的pg的sql
   * @return 转化后的sql
   */
  public String convertOracleInsertSql(String sql) {
    //用oracle中的批量语句代替
    //查找values的位置,将后面全部括号里的东西取出,然后再用对应的数据进行封装
    //获取前面的sql,这段sql与Oracle的相同
    String prefix = sql.substring(0, getKeywordValueIndex(sql));
    //排除table中的括号,取后面的括号
    String subSql = sql.substring(getKeywordValueIndex(sql));
    String valueSql = subSql.substring(subSql.indexOf("("));
    List<String> valueList = getValues(valueSql);

    //拼接sql
    StringBuilder sqlBuilder = new StringBuilder().append(prefix);
    String selectValue = "SELECT ";
    String endValue = " FROM DUAL ";
    String unionValue = "UNION ";
    boolean start = true;
    for (String value : valueList) {
      if (!start) {
        sqlBuilder.append(unionValue);
      }
      else {
        start = false;
      }
      sqlBuilder.append(selectValue).append(value).append(endValue);
    }

    return sqlBuilder.toString();
  }

//  public static void main(String[] args) {
//    String sql = "insert into table(id, name, age) values(?,?,?),(?,?,?),(?,?,?)";
//    System.out.println(convertOracleInsertSql(sql));
//  }

  /**
   * 使用栈实现获取value中括号的值
   *
   * @param sql
   * @return
   */
  public List<String> getValues(String sql) {
    List<String> values = new ArrayList<>();
    Stack<Character> brackets = new Stack<>();
    StringBuilder splitValue = new StringBuilder();
    for (Character c : sql.toCharArray()) {
      if ('(' == c) {
        //左括号进栈
        brackets.push(c);
      }
      else if (')' == c) {
        //右括号则将左括号出栈,清空builder
        brackets.pop();
        values.add(splitValue.toString());
        splitValue.delete(0, splitValue.length());
      }
      else if (!brackets.empty()) {
        //只有进入括号中才将值放入,排除括号外的逗号
        splitValue.append(c);
      }
    }
    return values;

  }

  /**
   * 查找关键字value的位置
   * @param sql
   * @return
   */
  public  int getKeywordValueIndex(String sql) {
    //先找values,再找value
    if (sql.indexOf("values") != -1) {
      return sql.indexOf("values");
    }
    else if (sql.indexOf("VALUES") != -1) {
      return sql.indexOf("VALUES");
    }
    else if (sql.indexOf("value") != -1) {
      return sql.indexOf("value");
    }
    else {
      return sql.indexOf("VALUE");
    }
  }

}

相关深入阅读以及参考

【Mybatis拦截器执行过程】:https://www.cnblogs.com/FraserYu/p/11044062.html

【Mybatis拦截器修改语句】:https://blog.csdn.net/qq_22200097/article/details/82942908

【Oracle批量插入】:https://www.cnblogs.com/hjm0928/p/10254894.html

【设计模式】:http://c.biancheng.net/design_pattern/

摘录

后记 ---【Mybatis拦截器修改语句】

当我们需要改变sql的时候,显然我们要在预编译SQL(prepare方法前加入修改的逻辑)。

当我们需要修改参数的时候我们可以在调用parameterize方法前修改逻辑。或者使用ParameterHandler来改造设置参数。

我们需要控制组装结果集的时候,也可以在query方法前后加入逻辑,或者使用ResultHandler来改造组装结果。

分页插件可以拦截Executor的方法进行。

最后分享高中化学老师教的一句话:读书是先将书读薄,然后再将书读厚。也就是先取其精华,再将精华扩展成不同的东西,说着有点像java的抽象编程喔。

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
1月前
|
SQL Java 数据库连接
深入 MyBatis-Plus 插件:解锁高级数据库功能
Mybatis-Plus 提供了丰富的插件机制,这些插件可以帮助开发者更方便地扩展 Mybatis 的功能,提升开发效率、优化性能和实现一些常用的功能。
201 26
深入 MyBatis-Plus 插件:解锁高级数据库功能
|
10天前
|
存储 Oracle 关系型数据库
数据库数据恢复—ORACLE常见故障的数据恢复方案
Oracle数据库常见故障表现: 1、ORACLE数据库无法启动或无法正常工作。 2、ORACLE ASM存储破坏。 3、ORACLE数据文件丢失。 4、ORACLE数据文件部分损坏。 5、ORACLE DUMP文件损坏。
46 11
|
23天前
|
Oracle 关系型数据库 数据库
Oracle数据恢复—Oracle数据库文件有坏快损坏的数据恢复案例
一台Oracle数据库打开报错,报错信息: “system01.dbf需要更多的恢复来保持一致性,数据库无法打开”。管理员联系我们数据恢复中心寻求帮助,并提供了Oracle_Home目录的所有文件。用户方要求恢复zxfg用户下的数据。 由于数据库没有备份,无法通过备份去恢复数据库。
|
29天前
|
存储 Oracle 关系型数据库
oracle数据恢复—Oracle数据库文件大小变为0kb的数据恢复案例
存储掉盘超过上限,lun无法识别。管理员重组存储的位图信息并导出lun,发现linux操作系统上部署的oracle数据库中有上百个数据文件的大小变为0kb。数据库的大小缩水了80%以上。 取出&并分析oracle数据库的控制文件。重组存储位图信息,重新导出控制文件中记录的数据文件,发现这些文件的大小依然为0kb。
|
16天前
|
存储 Oracle 关系型数据库
服务器数据恢复—华为S5300存储Oracle数据库恢复案例
服务器存储数据恢复环境: 华为S5300存储中有12块FC硬盘,其中11块硬盘作为数据盘组建了一组RAID5阵列,剩下的1块硬盘作为热备盘使用。基于RAID的LUN分配给linux操作系统使用,存放的数据主要是Oracle数据库。 服务器存储故障: RAID5阵列中1块硬盘出现故障离线,热备盘自动激活开始同步数据,在同步数据的过程中又一块硬盘离线,RAID5阵列瘫痪,上层LUN无法使用。
|
1月前
|
SQL Oracle 关系型数据库
Oracle数据库优化方法
【10月更文挑战第25天】Oracle数据库优化方法
48 7
|
1月前
|
Oracle 关系型数据库 数据库
oracle数据库技巧
【10月更文挑战第25天】oracle数据库技巧
31 6
|
1月前
|
存储 Oracle 关系型数据库
Oracle数据库优化策略
【10月更文挑战第25天】Oracle数据库优化策略
29 5
|
10天前
|
关系型数据库 MySQL 数据库
Python处理数据库:MySQL与SQLite详解 | python小知识
本文详细介绍了如何使用Python操作MySQL和SQLite数据库,包括安装必要的库、连接数据库、执行增删改查等基本操作,适合初学者快速上手。
79 15
|
4天前
|
SQL 关系型数据库 MySQL
数据库数据恢复—Mysql数据库表记录丢失的数据恢复方案
Mysql数据库故障: Mysql数据库表记录丢失。 Mysql数据库故障表现: 1、Mysql数据库表中无任何数据或只有部分数据。 2、客户端无法查询到完整的信息。

推荐镜像

更多