mybatis之mysql中的xml文件理论上不支持批量更新
通过源码截图看出。
com.alibaba.druid.wall.WallProvider#checkInternal方法中的如下代码。
if (statementList.size() > 1 && !config.isMultiStatementAllow()) { violations.add(new IllegalSQLObjectViolation(ErrorCode.MULTI_STATEMENT, "multi-statement not allow", sql)); }
2.作为码农还是要想办法。
网上找了两篇帖子。。
第一篇帖子-------------------------------start--------------------------------------------------
multi-statement not allow 没有设置批处理,解决方案网上很多,大部分基于以下 (本人基于springBoot开发)
(1)设置WallConfig里面的multiStatementAllow 为true
(2)DB URL追加&allowMultiQueries=true
以上详细内网,网上大把不做赘述。但是对于本人没用。
druid 的配置是基于Filter的,在设置wallConfig的时候事先查看wallConfig在druidDataSource 里面的Filtter是否存在,如果已经存在那么MySqlWallProvider 这个类在初始化的时候就会有可能初始化成另外一个没有配置multiStatementAllow 为true 的Filter,然后放在缓存里面,后面就算你的wallConfig 再次被执行init函数也不再放到缓存里面去.
下面是我在初始化wallConfig的时候打的断点,明显实现就已经存在了WallConfig,如果盲目的在设置一个WallConfig都是白搭,这就是我为什么采用了网上配置之后都是没用的原因
下面这个是取druidDataSource的内容进行初始化
解决方法:在设置之前先判断是都已经存在WallConfig,如果有,直接将现有的替换掉
DB的URL的参数还是得加上&allowMultiQueries=true , 原因网上很多,自行百度,欢迎提问
————————————————
第二篇帖子-------------------------------start--------------------------------------------------
有两个原因,一个数据库层面的限制,通过在url地址中添加allowMultiQueries=true可以解决
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test_account?characterEncoding=utf-8&useSSL=false&allowMultiQueries=true
另外一个原因,与Druid的过滤器有关,其本质错误是Druid的防火墙配置(WallConfig)中变量multiStatementAllow默认为false
————————————————
解决方案就是把multiStatementAllow设为true,例如
<bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="proxyFilters">
<list>
<ref bean="stat-filter" />
<ref bean="wall-filter"/>
</list>
</property>
<!--<property name="filters" value="stat,wall"/>-->
<!-- <property name="filters" value="mergeStat"/> -->
</bean>
<bean id="stat-filter" class="com.alibaba.druid.filter.stat.StatFilter"/>
<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>
或者通过代码注入新的datasource
import com.alibaba.druid.filter.Filter;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.wall.WallConfig;
import com.alibaba.druid.wall.WallFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.List;
@Configuration
public class MybatisConfig {
@Autowired
WallFilter wallFilter;
@Bean(name = "dataSource", destroyMethod = "close") //声明其为Bean实例
@Primary //在同样的DataSource中,首先使用被标注的DataSource
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource(){
DruidDataSource datasource = new DruidDataSource();
// filter
List<Filter> filters = new ArrayList<>();
filters.add(wallFilter);
datasource.setProxyFilters(filters);
return datasource;
}
@Bean(name = "wallFilter")
@DependsOn("wallConfig")
public WallFilter wallFilter(WallConfig wallConfig){
WallFilter wallFilter = new WallFilter();
wallFilter.setConfig(wallConfig);
return wallFilter;
}
@Bean(name = "wallConfig")
public WallConfig wallConfig(){
WallConfig wallConfig = new WallConfig();
wallConfig.setMultiStatementAllow(true);//允许一次执行多条语句
wallConfig.setNoneBaseStatementAllow(true);//允许一次执行多条语句
return wallConfig;
}
}
本人的解决方案依据如下:
之前写的代码,批量更新的操作都是使用for循环语句进行集合的遍历,一次次调用单条更新的接口,代码既不美观,性能也不好,就想使用MyBatis在一个方法里批量更新数据,我一共想到两种思路,分享一下:
第一种就是使用MyBatis的 <foreach> 去循环创建多条完整的 update 语句,然后交由Mysql数据库一条一条去执行(这个和之前的性能差不多,都是一条一条去更新)
第二种就是使用数据库支持的case when函数,根据不同的条件去更新对应符合条件的数据
我们先来看看第一种方法吧:
1.使用 <foreach> 循环创建多条完整update语句
此处是将需要修改的参数放入一个 List 中,然后遍历list集合,创建多条 update 语句,最终的结果如下:
1.1 MyBatis 的XML语句
<!--批量更新 -->
<update id="updateListByHouseId" parameterType="java.util.ArrayList">
<foreach close=";" collection="list" index="index" item="item" open="" separator=";">
update rba_house_status
<trim prefix="set" suffixOverrides=",">
last_push_time = now(),
<if test="item.pushStatus != null">push_status = #{item.pushStatus},</if>
<if test="item.createStatus != null">create_status = #{item.createStatus},</if>
<if test="item.houseCreateTime != null">house_create_time = #{item.houseCreateTime},</if>
<if test="item.updateStatus != null">update_status = #{item.updateStatus},</if>
update_time = now(),
<if test="item.auditStatus != null">audit_status = #{item.auditStatus},</if>
<if test="item.auditDesc != null">audit_desc = #{item.auditDesc},</if>
<if test="item.hotelId != null">hotel_id = #{item.hotelId},</if>
</trim>
<where>house_id = #{item.houseId}</where>
</foreach>
</update>
————————————————
MyBatis最终生成的SQL
update rba_house_status set last_push_time = now(), push_status = ?, create_status = ?, house_create_time = ?, update_status = ?, update_time = now(), audit_status = ?, audit_desc = ?, hotel_id = ? WHERE house_id = ? ;
update rba_house_status set last_push_time = now(), push_status = ?, create_status = ?, house_create_time = ?, update_status = ?, update_time = now(), audit_status = ?, audit_desc = ?, hotel_id = ? WHERE house_id = ? ;
上脚本中的 “?” 对应参数对象中各个对应字段的值
写到这里,本以为第一种方法就可以执行了,直接去进行测试了,就在这时,服务器跟我闹别扭,扭扭捏捏不执行,一直报一个错误: multi-statement not allow
一脸懵逼为啥还不让执行了?
查了半天资料发现,原来MyBatis想要一次执行多条语句是需要进行额外的配置,配置在数据库链接串里面,就是在链接串最后面加一个: &allowMultiQueries=true ,如下所示:
jdbc:mysql://127.0.0.1:3306/test_db?characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai&allowMultiQueries=true
加完以后信誓旦旦开始了第二次测试执行,按理来说应该到这里就可以,结果执行还是报错,难道是我执行的姿势不对?清除之前编译的文件,再试,还是不行好吧,继续google and 百度…
最后历经千辛万苦,还是没结果,看来还是只能自己找答案了,最后偶然间发现在数据源的配置文件里有一个 com.alibaba.druid.wall.WallFilter 的配置,这个filter里面有个一配置项 com.alibaba.druid.wall.WallConfig ,这个配置项里面有个一 multiStatementAllow ,这个配置默认是false,果断改掉配置,添加一个 multiStatementAllow 的配置,如下:
<bean id="wall-filter-config" class="com.alibaba.druid.wall.WallConfig" init-method="init">
<property name="dir" value="META-INF/druid/wall/mysql" />
<property name="selectWhereAlwayTrueCheck" value="false" />
<property name="selectHavingAlwayTrueCheck" value="false" />
<property name="multiStatementAllow" value="true" />
</bean>
————————————————
配置完成,再次执行测试代码!