springmvc配置allowMultiQueries批量执行多个SQL,解决multi-statement not allow问题

今天尝试使用springmvc使用mybatis一次执行多个SQL,首先在jdbc的url中配置了allowMultiQueries=true,但是报错multi-statement not allow。废话不多说,直接上配置

<!-- 1. 主库数据源 : DriverManagerDataSource -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
        init-method="init" destroy-method="close">
        <property name="url" value="${jdbc.main.url}" />
        <property name="username" value="${jdbc.main.username}" />
        <property name="password" value="${jdbc.main.password}" />
        <property name="driverClassName" value="${jdbc.main.driver}" />
        <property name="defaultAutoCommit" value="false" />
        <property name="filters" value="stat,slf4j" />
        <property name="maxActive" value="50" />
        <property name="initialSize" value="10" />
        <property name="maxWait" value="60000" />
        <property name="timeBetweenEvictionRunsMillis" value="3000" />
        <property name="minEvictableIdleTimeMillis" value="300000" />
        <property name="validationQuery" value="SELECT 1 from dual" />
        <property name="testWhileIdle" value="true" />
        <property name="testOnBorrow" value="false" />
        <property name="testOnReturn" value="false" />
        <property name="poolPreparedStatements" value="false" />
        <property name="maxPoolPreparedStatementPerConnectionSize"
            value="100" />
        <!-- 超过时间限制是否回收,这块也能测试出未关闭连接的时候报错位置 -->
        <property name="removeAbandoned" value="true" />
        <!-- 超时时间;单位为秒。180秒=3分钟 -->
        <property name="removeAbandonedTimeout" value="1800" />
        <!-- 关闭abanded连接时输出错误日志 -->
        <property name="logAbandoned" value="true" />
        <!-- 若需要mybatis的批量sql需配置 不配置则报错:nested exception is java.sql.SQLException: 
            sql injection violation, multi-statement not allow -->
        <property name="proxyFilters" ref="wall-filter" />
    </bean>
 
    <!-- 若需要mybatis的批量sql需配置 -->
    <bean id="wall-filter" class="com.alibaba.druid.wall.WallFilter">
        <property name="config" ref="wall-config" />
    </bean>
    <bean id="wall-config" class="com.alibaba.druid.wall.WallConfig">
        <property name="multiStatementAllow" value="true" />
    </bean>
以上的配置中需要着重注意两点:

1、dataSource下的filters中必须要去掉wall,否则spring会自动生成一个wall不会使用下面自定义的wall。

2、dataSource增加proxyFilters用来配置wall

springboot解决multi-statement not allow(已部署生产)

最近有一个需求,要在短时间内大量更新数据库数据,那肯定得上批量更新了。
springboot支持批量更新的配置很简单。如下:
//注意,allowMultiQueries要放在第一个参数,否则可能不生效
jdbc:mysql://MYSQL_IP:MYSQL_PORT/MYSQL_DATABASE?allowMultiQueries=true

1、问题复现
本地测试没有问题,部署到测试环境后,就会报错:

org.springframework.jdbc.UncategorizedSQLException: 
### Error updating database.  Cause: java.sql.SQLException: sql injection violation, dbType mysql, druid-version 1.2.16, multi-statement not allow : 
            UPDATE order SET express = ?,company = ? WHERE code = ?
         ; 
            UPDATE order SET express = ?,company = ? WHERE code = ?
         ; 
            UPDATE order SET express = ?,company = ? WHERE code = ?
         ; 
            UPDATE order SET express = ?,company = ? WHERE code = ?
            at com.alibaba.druid.wall.WallFilter.checkInternal(WallFilter.java:835)
    at com.alibaba.druid.wall.WallFilter.connection_prepareStatement(WallFilter.java:270)
    at com.alibaba.druid.filter.FilterChainImpl.connection_prepareStatement(FilterChainImpl.java:531)
    at com.alibaba.druid.filter.FilterAdapter.connection_prepareStatement(FilterAdapter.java:908)
    at com.alibaba.druid.filter.FilterEventAdapter.connection_prepareStatement(FilterEventAdapter.java:116)

报错很明显就是,druid数据源默认不允许这种批量更新。看报错的堆栈,是一个叫做WallFilter的过滤器中报出来的。因为我们在springboot的配置文件中添加了filters:stat,wall。就是这个wall的配置引起的。

2、解决方案
解决方案有两种。
第一:很简单,既然是wall引起的,直接把它去掉不就行了。这种方案是可以的。
第二:如果你确实用到了这个wall呢?那就得想办法,带着wall的同时,还要支持批量更新。
第一种不说了,删代码就行了。
直接说第二种方案,我对druid的源码也不熟悉,碰到了这个问题,也是google,
网上有一大堆解决方案,基本都是说要给WallFilter设置上一个属性:
multiStatementAllow = true。
首先来说,解决思路没有问题,就是将这个属性multiStatementAllow的值设置为true,关键是怎么做是有效的?反正我试了网上的思路,不管用,还是报错,要不然就是不报错了,但是会覆盖掉配置文件中数据源相关的配置。

3、解决方案对应的代码
直接贴代码,已在生产环境验证。
解决思路很简单,首先将我们已经生成的数据源注入到代码中,然后添加MultiStatementAllow=true的配置。

@SpringBootApplication()
public class Application implements CommandLineRunner {
    //获取到当前项目中的数据源
    @Autowired
    private DataSource dataSource;

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
        log.info("启动成功。。。");
    }

    @Override
    public void run(String... args) {
        try {
            DruidDataSource druidDataSource = (DruidDataSource) dataSource;
            List<Filter> proxyFilters = druidDataSource.getProxyFilters();
            for (Filter filter : proxyFilters) {
                if (filter instanceof WallFilter) {
                    //允许批量sql执行
                    ((WallFilter) filter).getProvider().getConfig().setMultiStatementAllow(true);
                    //允许DDL执行
                    ((WallFilter) filter).getProvider().getConfig().setNoneBaseStatementAllow(true);
                }
            }
            log.info("druid配置信息:{}",druidDataSource);
        } catch (Exception e) {
            log.error("给druid数据源设置过滤器异常",e);
        }
    }
}

以上就是如何解决multi-statement not allow的方案

4、多数据源问题解决方案
我们可能在项目中引入了多数据源,DynamicRoutingDataSource。这个时候,如果按照上面的方式,从DataSource直接强转DynamicRoutingDataSource会失败。我们需要从DynamicRoutingDataSource获取到DruidDataSource,然后再进行操作。从DynamicRoutingDataSource获取DruidDataSource的方式如下:

DynamicRoutingDataSource dynamicRoutingDataSource = (DynamicRoutingDataSource) dataSource;
            Map<String, DataSource> dataSources = dynamicRoutingDataSource.getDataSources();
            if (MapUtil.isEmpty(dataSources)) {
                return;
            }
            for (DataSource tempDataSource : dataSources.values()) {
                ItemDataSource itemDataSource  = (ItemDataSource) tempDataSource;
                DruidDataSource druidDataSource = (DruidDataSource) itemDataSource.getRealDataSource();
                resetFilter(druidDataSource);
            }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值