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

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 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
目录
相关文章
|
23天前
|
存储 自然语言处理 Oracle
Oracle数据库字符集概述及修改方式
【8月更文挑战第15天】Oracle 数据库字符集定义了数据的编码方案,决定可存储的字符类型及其表示方式。主要作用包括数据存储、检索及跨系统传输时的正确表示。常见字符集如 AL32UTF8 支持多语言,而 WE8MSWIN1252 主用于西欧语言。修改字符集风险高,可能导致数据问题,需事先备份并评估兼容性。可通过 ALTER DATABASE 语句直接修改或采用导出-导入数据的方式进行。完成后应验证数据完整性。此操作复杂,须谨慎处理。
|
19天前
|
数据采集 Oracle 关系型数据库
实时计算 Flink版产品使用问题之怎么实现从Oracle数据库读取多个表并将数据写入到Iceberg表
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
|
7天前
|
存储 Oracle 关系型数据库
Oracle同一台服务器创建多个数据库
【8月更文挑战第30天】在 Oracle 中,可在同一服务器上创建多个数据库。首先确保已安装 Oracle 软件并具有足够资源,然后使用 DBCA 工具按步骤创建,包括选择模板、配置存储及字符集等。重复此过程可创建多个数据库,需确保名称、SID 和存储位置唯一。创建后,可通过 Oracle Enterprise Manager 进行管理,注意服务器资源分配与规划。
18 10
|
15天前
|
存储 Oracle 关系型数据库
分享几个Oracle数据库日常维护中常见的问题
分享几个Oracle数据库日常维护中常见的问题
55 1
|
20天前
|
SQL 关系型数据库 MySQL
解决:Mybatis-plus向数据库插入数据的时候 报You have an error in your SQL syntax
该博客文章讨论了在使用Mybatis-Plus向数据库插入数据时遇到的一个常见问题:SQL语法错误。作者发现错误是由于数据库字段中使用了MySQL的关键字,导致SQL语句执行失败。解决方法是将这些关键字替换为其他字段名称,以避免语法错误。文章通过截图展示了具体的操作步骤。
|
9天前
|
SQL Oracle 关系型数据库
实时计算 Flink版产品使用问题之Oracle数据库是集群部署的,怎么进行数据同步
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
|
15天前
|
Oracle 关系型数据库 数据库
Oracle数据库备份脚本分享-Python
Oracle数据库备份脚本分享-Python
13 0
|
9天前
|
Java 数据库连接 测试技术
SpringBoot 3.3.2 + ShardingSphere 5.5 + Mybatis-plus:轻松搞定数据加解密,支持字段级!
【8月更文挑战第30天】在数据驱动的时代,数据的安全性显得尤为重要。特别是在涉及用户隐私或敏感信息的应用中,如何确保数据在存储和传输过程中的安全性成为了开发者必须面对的问题。今天,我们将围绕SpringBoot 3.3.2、ShardingSphere 5.5以及Mybatis-plus的组合,探讨如何轻松实现数据的字段级加解密,为数据安全保驾护航。
30 1
|
17天前
|
Web App开发 前端开发 关系型数据库
基于SpringBoot+Vue+Redis+Mybatis的商城购物系统 【系统实现+系统源码+答辩PPT】
这篇文章介绍了一个基于SpringBoot+Vue+Redis+Mybatis技术栈开发的商城购物系统,包括系统功能、页面展示、前后端项目结构和核心代码,以及如何获取系统源码和答辩PPT的方法。
|
22天前
|
Java 关系型数据库 MySQL
1、Mybatis-Plus 创建SpringBoot项目
这篇文章是关于如何创建一个SpringBoot项目,包括在`pom.xml`文件中引入依赖、在`application.yml`文件中配置数据库连接,以及加入日志功能的详细步骤和示例代码。

推荐镜像

更多
下一篇
DDNS