问题:
在对接 sqlserver数据库的时候 主子表 保存的时候
子表批量保存 使用的 mybatis-plus提供的 saveOrUpdateBatch saveBatch 这两个方法
但是 报错
报错内容为 :
com.microsoft.sqlserver.jdbc.SQLServerException: 必须执行该语句才能获得结果。
框架版本
sprin boot 2.0 +
mybatis-plus 3.3.1
mybatis-plus 代码生成器 mybatis-plus-generator: 3.3.2
SQL Server JDBC: mssql-jdbc 版本 :8.4.1.jre8
经过排查后
猜测 应该是 mssql-jdbc 和 mybatis-plus 不兼容把
mssql-jdbc 和 mybatis-plus 都调整的了版本 还是不行
最后我的结论 应该是 mybatis-plus在处理 sqlserver 批处理的时候
没有考虑这种情况
最后也没找到合适的解决方案
以前写过一篇博客 解决这个问题
https://blog.csdn.net/Drug_/article/details/129336556
我当时采用的是 博客中方案一 的处理方法
异常捕获一下
当时测试是没有问题的
但是 经过使用 发现还是有问题 会丢数据
以前的博客中也提供了方式二 就是写 xml文件 原生sql 执行
经过测试 方式二 是非常好用的
但是 我接触java的时候 就开始使用 mybatis-plus
习惯了调方法 ,就很烦写xml
而且我在学习 以前java web框架 发现以前的web框架 都是在编写xml
我有点感叹 这一代的java 程序员好幸福 , 基本不用写 xml 文件
我也有点怀念php框架的 操作数据库的方法
所以为了 批量处理数据 不写大量的 xml文件
既然 框架没办法解决这个问题
经过 两天的摸索 又看了底层的实现方式
于是我就 在 mybatis-plus 依赖的基础上 封装了一个 自定义的 saveOrUpdateBatch 方法
遇到批量处理数据 就调用自己的
以下是封装的代码
我们使用 mybatis-plus 自动生成文件
他默认 继承 ServiceImpl 和 BaseMapper 这两个类
我就 又封装了两个文件
CommonService 和 CommonMapper
分别 继承ServiceImpl 和 BaseMapper
然后 让各个表 的Mapper和Service 都来继承 CommonService 和 CommonMapper
CommonMapper 代码
package com.erp.yt.common.init.appService;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
import java.util.List;
import java.util.Map;
/**
* User: Json
* <p>
* Date: 2023/3/30
**/
public interface CommonMapper<T> extends BaseMapper<T> {
@Insert({
"<script>" +
"INSERT INTO ${table} (${tableFieldList}) VALUES " +
"<foreach collection=\"tableFieldValueList\" item=\"item\" separator=\",\">" +
" ${item} " +
"</foreach>" +
" ;" +
"</script>"})
int insertAll(@Param("table") String table,
@Param("tableFieldList") String tableFieldList,
@Param("tableFieldValueList") List<Object> tableFieldValueList);
//目前不会 返回 主键id
//针对insert有效,当有关联表操作的时候,可以先插入主表,然后根据主表返回的主键id去落库详情表
//如果需要
// 加入 第四个参数 传入一个对象 然后 keyProperty="对象的主键id",
// 则会返回id
//@Options(useGeneratedKeys=true,keyProperty="EtMaintainsub.id",keyColumn="id")
//useGeneratedKeys 是否返回生成的主键
//keyProperty 传入对象中的对象名
//keyColumn 数据库中的字段名
@Update({
"<script>" +
"<foreach collection=\"fieldValueList\" item=\"item\" separator=\";\">" +
" UPDATE ${table} SET ${item.fieldValue} WHERE ${item.where}" +
"</foreach>" +
"</script>"})
int updateAll(@Param("table") String table, @Param("fieldValueList") List<Map<String,Object>> fieldValueList);
}
CommonService 代码
package com.erp.yt.common.init.appService;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.erp.yt.common.init.utils.DateUtil;
import com.erp.yt.common.init.utils.RequestUtils;
import com.erp.yt.common.init.utils.WhlUtil;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* User: Json
* <p>
* T 实体类
* Rq 可以传实体类 也可以传 请求类 这个泛型 在这 个代码里没有用 也可以删掉
* 方法没有抽取封装 可自行优化使用
* Date: 2022/6/30
**/
public class CommonService<M extends CommonMapper<T>, T, Rq> extends ServiceImpl<M, T> {
//没有控制 批量操作条数 调用mybatisplus框架方法的话 框架默认是 1000条
@Transactional(rollbackFor = Exception.class)
public boolean saveOrUpdateBatchZdy(Collection<T> entityList) {
TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClass);
String keyProperty = tableInfo.getKeyProperty();
//新增字段
List<String> insertTableFieldList=new ArrayList<>();
//新增字段值
List<Object> insertTableFieldValueList=new ArrayList<>();
//更新数据
List<Map<String,Object>> updateFieldValueList=new ArrayList<>();
for (T entity : entityList) {
// System.out.println(entity);
Object idVal = ReflectionKit.getFieldValue(entity, keyProperty);
if (com.baomidou.mybatisplus.core.toolkit.StringUtils.checkValNull(idVal) || Objects.isNull(getById((Serializable) idVal))) {
// System.out.println("新增");
List<Object> tableFieldValue=new ArrayList<>();
for (TableFieldInfo fieldInfo :tableInfo.getFieldList()){
Object field = ReflectionKit.getFieldValue(entity, fieldInfo.getProperty());
// System.out.println("sql字段:"+fieldInfo.getColumn());
// System.out.println("实体字段类型:"+fieldInfo.getPropertyType());
// System.out.println("实体类字段:"+fieldInfo.getProperty());
// System.out.println("实体类字段值:"+field);
if(!insertTableFieldList.contains(fieldInfo.getColumn())){
insertTableFieldList.add(fieldInfo.getColumn());
}
//如果前端传个空字符串"" 应该会被执行 没有判断 "" 空字符串的情况,
// 如果需要判断
// 可根据实体类上的 mybatisplus 上的 注解
// 多个判断 应该就可以了
if(ObjectUtils.isEmpty(field)){
//自动填充 根据 mybatisplus 的注解 判断 填充就好
if(FieldFill.INSERT_UPDATE.equals(fieldInfo.getFieldFill())){
// 目前自动填充 只有 更新人 更新时间 创建人 和创建时间
// 所以只用判断数据类型就好 不用针对某个字段进行判断
if(fieldInfo.getPropertyType().equals(LocalDateTime.class)){
Date strDate= DateUtil.localDateToDateTime(LocalDateTime.now());
tableFieldValue.add("'"+DateUtil.format(strDate,"yyyy-MM-dd HH:mm:ss")+"'");
}
if(fieldInfo.getPropertyType().equals(String.class)){
tableFieldValue.add("'"+RequestUtils.getUsername()+"'");
}
}else if(FieldFill.INSERT.equals(fieldInfo.getFieldFill())){
if(fieldInfo.getPropertyType().equals(LocalDateTime.class)){
Date strDate= DateUtil.localDateToDateTime(LocalDateTime.now());
tableFieldValue.add("'"+DateUtil.format(strDate,"yyyy-MM-dd HH:mm:ss")+"'");
}
if(fieldInfo.getPropertyType().equals(String.class)){
tableFieldValue.add("'"+RequestUtils.getUsername()+"'");
}
}else{
tableFieldValue.add(null);
}
}else{
//所有的字段值都转成字符串 插入
// 时间 要特殊处理
if(fieldInfo.getPropertyType().equals(LocalDateTime.class)){
Date strDate= DateUtil.localDateToDateTime((LocalDateTime) field);
tableFieldValue.add("'"+DateUtil.format(strDate,"yyyy-MM-dd HH:mm:ss")+"'");
}else{
tableFieldValue.add("'"+field+"'");
}
}
}
insertTableFieldValueList.add("( " +tableFieldValue.stream().map(String::valueOf).collect(Collectors.joining(","))+")");
} else {
//System.out.println("更新");
Map<String,Object> updateFieldValueMap=new HashMap<>();
//目前这个方法 只根据 主键id更新
updateFieldValueMap.put("where",keyProperty+"="+idVal);
List<String> updateFieldValue=new ArrayList<>();
for (TableFieldInfo fieldInfoUpdate :tableInfo.getFieldList()){
Object field = ReflectionKit.getFieldValue(entity, fieldInfoUpdate.getProperty());
// System.out.println("sql字段:"+fieldInfoUpdate.getColumn());
// System.out.println("实体字段类型:"+fieldInfoUpdate.getPropertyType());
// System.out.println("实体类字段:"+fieldInfoUpdate.getProperty());
// System.out.println("实体类字段值:"+field);
// 如果用mybatisplus框架的方法
// 他会根据实体类的注解@TableField(updateStrategy = FieldStrategy.IGNORED )
// 来判断 到底允许不允许更新 空字符串
//如果 需要这种 空字符串的处理 这里需要根据 实体字段上面的注解 多做一个判断 应该就可以了
if(ObjectUtils.isEmpty(field)){
//自动填充 根据 mybatisplus 的注解 判断 填充就好
if(FieldFill.INSERT_UPDATE.equals(fieldInfoUpdate.getFieldFill())){
// 目前自动填充 只有 更新人 更新时间 创建人 和创建时间
// 所以只用判断数据类型就好 不用针对某个字段进行判断
if(fieldInfoUpdate.getPropertyType().equals(LocalDateTime.class)){
Date strDate= DateUtil.localDateToDateTime(LocalDateTime.now());
updateFieldValue.add(fieldInfoUpdate.getColumn()+"="+"'"+DateUtil.format(strDate,"yyyy-MM-dd HH:mm:ss")+"'");
}
if(fieldInfoUpdate.getPropertyType().equals(String.class)){
updateFieldValue.add(fieldInfoUpdate.getColumn()+"="+"'"+RequestUtils.getUsername()+"'");
}
}else if(FieldFill.UPDATE.equals(fieldInfoUpdate.getFieldFill())){
if(fieldInfoUpdate.getPropertyType().equals(LocalDateTime.class)){
Date strDate= DateUtil.localDateToDateTime(LocalDateTime.now());
updateFieldValue.add(fieldInfoUpdate.getColumn()+"="+"'"+DateUtil.format(strDate,"yyyy-MM-dd HH:mm:ss")+"'");
}
if(fieldInfoUpdate.getPropertyType().equals(String.class)){
updateFieldValue.add(fieldInfoUpdate.getColumn()+"="+"'"+RequestUtils.getUsername()+"'");
}
}else{
updateFieldValue.add(fieldInfoUpdate.getColumn()+"="+null);
}
}else{
if(fieldInfoUpdate.getPropertyType().equals(LocalDateTime.class)){
Date strDate= DateUtil.localDateToDateTime((LocalDateTime) field);
updateFieldValue.add(fieldInfoUpdate.getColumn()+"="+"'"+DateUtil.format(strDate,"yyyy-MM-dd HH:mm:ss")+"'");
}else{
updateFieldValue.add(fieldInfoUpdate.getColumn()+"="+"'"+field+"'");
}
}
updateFieldValueMap.put("fieldValue",updateFieldValue.stream().map(String::valueOf).collect(Collectors.joining(",")));
}
updateFieldValueList.add(updateFieldValueMap);
}
}
if(!CollectionUtils.isEmpty(insertTableFieldList) && !CollectionUtils.isEmpty(insertTableFieldValueList)){
// System.out.println("最终字段:"+insertTableFieldList);
// System.out.println("最终字段值:"+insertTableFieldValueList);
baseMapper.insertAll(WhlUtil.getSqlTableName(entityClass),
insertTableFieldList.stream().map(String::valueOf).collect(Collectors.joining(",")),
insertTableFieldValueList);
}
if(!CollectionUtils.isEmpty(updateFieldValueList)){
// System.out.println("最终更新语句: "+updateFieldValueList);
baseMapper.updateAll(WhlUtil.getSqlTableName(entityClass),
updateFieldValueList);
}
return true;
}
}
工具类:
WhlUtil.getSqlTableName(EtMaintainsub.class) 这个工具类 主要是去拿实体类上的 表名
/**
* @param clazz 实体类.class
* @return 物理表名
* **/
public static String getSqlTableName(Class<?> clazz){
if(clazz.isAnnotationPresent(TableName.class)){
TableName table = clazz.getAnnotation(TableName.class);
String tableName = table.value();
if(StringUtils.isEmpty(tableName)){
throw new ErpRuntimeException("@TableName注解value不存在,无法获取表名");
}
return tableName;
}else{
throw new ErpRuntimeException("@TableName注解不存在,无法获取表名");
}
}
//调用 测试
saveOrUpdateBatchZdy(etMaintain.getEtMaintainsubList());
//以上就是封装的公共的方法 主要利用 泛型 封装成 公共的操作类
下面再分享一个 写死实体类的方法
下面方法没有 实现自动填充 自行补充就好
//没有控制 批量操作条数 调用mybatisplus框架方法的话 框架默认是 1000条
@Transactional(rollbackFor = Exception.class)
public boolean saveOrUpdateBatchZdy(Collection<EtMaintainsub> entityList) {
TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClass);
String keyProperty = tableInfo.getKeyProperty();
//新增字段
List<String> insertTableFieldList=new ArrayList<>();
//新增字段值
List<Object> insertTableFieldValueList=new ArrayList<>();
//更新数据
List<Map<String,Object>> updateFieldValueList=new ArrayList<>();
for (EtMaintainsub entity : entityList) {
// System.out.println(entity);
Object idVal = ReflectionKit.getFieldValue(entity, keyProperty);
if (StringUtils.checkValNull(idVal) || Objects.isNull(getById((Serializable) idVal))) {
// System.out.println("新增");
List<Object> tableFieldValue=new ArrayList<>();
for (TableFieldInfo fieldInfo :tableInfo.getFieldList()){
Object field = ReflectionKit.getFieldValue(entity, fieldInfo.getProperty());
// System.out.println("sql字段:"+fieldInfo.getColumn());
// System.out.println("实体字段类型:"+fieldInfo.getPropertyType());
// System.out.println("实体类字段:"+fieldInfo.getProperty());
// System.out.println("实体类字段值:"+field);
if(!insertTableFieldList.contains(fieldInfo.getColumn())){
insertTableFieldList.add(fieldInfo.getColumn());
}
//如果前端传个空字符串"" 应该会被执行 没有判断 "" 空字符串的情况,如果需要多个判断实体字段类型应该就可以了
if(ObjectUtils.isEmpty(field)){
tableFieldValue.add(field);
}else{
tableFieldValue.add("'"+field+"'");
}
}
insertTableFieldValueList.add("( " +tableFieldValue.stream().map(String::valueOf).collect(Collectors.joining(","))+")");
} else {
System.out.println("更新");
Map<String,Object> updateFieldValueMap=new HashMap<>();
//目前这个方法 只根据 主键id更新
updateFieldValueMap.put("where",keyProperty+"="+idVal);
List<String> updateFieldValue=new ArrayList<>();
for (TableFieldInfo fieldInfoUpdate :tableInfo.getFieldList()){
Object field = ReflectionKit.getFieldValue(entity, fieldInfoUpdate.getProperty());
// System.out.println("sql字段:"+fieldInfoUpdate.getColumn());
// System.out.println("实体字段类型:"+fieldInfoUpdate.getPropertyType());
// System.out.println("实体类字段:"+fieldInfoUpdate.getProperty());
// System.out.println("实体类字段值:"+field);
// 如果用mybatisplus框架的方法
// 他会根据实体类的注解@TableField(updateStrategy = FieldStrategy.IGNORED )
// 来判断 到底允许不允许更新 空字符串
//如果 需要这种 空字符串的处理 这里需要根据 实体字段类型 多做一个判断 应该就可以了
if(ObjectUtils.isEmpty(field)){
updateFieldValue.add(fieldInfoUpdate.getColumn()+"="+field);
}else{
updateFieldValue.add(fieldInfoUpdate.getColumn()+"="+"'"+field+"'");
}
updateFieldValueMap.put("fieldValue",updateFieldValue.stream().map(String::valueOf).collect(Collectors.joining(",")));
}
updateFieldValueList.add(updateFieldValueMap);
}
}
if(!CollectionUtils.isEmpty(insertTableFieldList) && !CollectionUtils.isEmpty(insertTableFieldValueList)){
// System.out.println("最终字段:"+insertTableFieldList);
// System.out.println("最终字段值:"+insertTableFieldValueList);
baseMapper.insertAll(WhlUtil.getSqlTableName(EtMaintainsub.class),
insertTableFieldList.stream().map(String::valueOf).collect(Collectors.joining(",")),
insertTableFieldValueList);
}
if(!CollectionUtils.isEmpty(updateFieldValueList)){
// System.out.println("最终更新语句: "+updateFieldValueList);
baseMapper.updateAll(WhlUtil.getSqlTableName(EtMaintainsub.class),
updateFieldValueList);
}
return true;
}