扩展MybatisPlus批量插入(忽略唯一索引冲突行)

这里是引用
实现参考了 https://my.oschina.net/teddyIH/blog/4548046
由于版本不同,InsertIgnoreBatchAllColumn和CustomerSqlInjector实现不同,添加注释完善了说明

一、背景

  1. 在数据同步或者幂等场景下,常常需要设置唯一索引来避免重复请求,select and update效率低,且并发时还是会报错,并不友好,那么可以用Mysql的Insert ignore语法来优化。
  2. MybatisPlus官方并没有针此处场景进行支持

二、环境

<dependency>
	<groupId>com.baomidou</groupId>
	<artifactId>mybatis-plus-boot-starter</artifactId>
	<version>3.1.0</version>
	<exclusions>
		<exclusion>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-generator</artifactId>
		</exclusion>
	</exclusions>
</dependency>

三、注入自定义批量插入sql

因为只需要改造insertBatchSomeColumn方法,那直接CV就好

insertBatchSomeColumn方法属于mybatis plus官方扩展包中

  1. sql模板
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.toolkit.Assert;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.sql.SqlScriptUtils;
import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator;
import org.apache.ibatis.executor.keygen.KeyGenerator;
import org.apache.ibatis.executor.keygen.NoKeyGenerator;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;

import java.util.function.Predicate;

/**
 * @author: EnumaElish
 * @Date: 2021/6/1 14:08
 * @Description: 参考InsertBatchSomeColumn实现,就替换了sqlTemplate
 * <p> 不同的数据库支持度不一样!!!  只在 mysql 下测试过!!!  只在 mysql 下测试过!!!  只在 mysql 下测试过!!! </p>
 * <p> 除了主键是 <strong> 数据库自增的未测试 </strong> 外理论上都可以使用!!! </p>
 * <p> 如果你使用自增有报错或主键值无法回写到entity,就不要跑来问为什么了,因为我也不知道!!! </p>
 * <p>
 * 自己的通用 mapper 如下使用:
 * <pre>
 * int fastSaveIgnoreBatch(List<T> entityList);
 * </pre>
 * </p>
 *
 * <li> 注意1: 不要加任何注解 !! </li>
 * <li> 注意2: 自选字段 insert !!,如果个别字段在 entity 里为 null 但是数据库中有配置默认值, insert 后数据库字段是为 null 而不是默认值 </li>
 *
 * <p>
 * 常用的构造入参:
 * </p>
 *
 * <li> 例1: new InsertIgnoreBatchAllColumn(t -> true) , 表示用于全字段 </li>
 * <li> 例2: new InsertIgnoreBatchAllColumn(t -> !t.isLogicDelete()) , 表示非逻辑删除字段外全字段 </li>
 * <li> 例3: new InsertIgnoreBatchAllColumn(t -> t.getFieldFill() != FieldFill.UPDATE) , 表示填充策略为 UPDATE 外的全字段 </li>
 *
 */
public class InsertIgnoreBatchAllColumn extends AbstractMethod {

	/**
	 * mapper 对应的方法名
	 */
	private static final String MAPPER_METHOD = "insertIgnoreBatchAllColumn";

	private Predicate<TableFieldInfo> predicate;

	public InsertIgnoreBatchAllColumn(Predicate<TableFieldInfo> predicate) {
		Assert.notNull(predicate, "this predicate can not be null !");
		this.predicate = predicate;
	}

	@SuppressWarnings("Duplicates")
	@Override
	public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
		KeyGenerator keyGenerator = new NoKeyGenerator();
		SqlMethod sqlMethod = SqlMethod.INSERT_ONE;
		String sqlTemplate = "<script>\nINSERT IGNORE INTO %s %s VALUES %s\n</script>";

		String insertSqlColumn = tableInfo.getSomeInsertSqlColumn(predicate);
		String columnScript = LEFT_BRACKET + insertSqlColumn.substring(0, insertSqlColumn.length() - 1) + RIGHT_BRACKET;
		String insertSqlProperty = tableInfo.getSomeInsertSqlProperty(ENTITY_DOT, predicate);
		insertSqlProperty = LEFT_BRACKET + insertSqlProperty.substring(0, insertSqlProperty.length() - 1) + RIGHT_BRACKET;
		String valuesScript = SqlScriptUtils.convertForeach(insertSqlProperty, "list", null, ENTITY, COMMA);
		String keyProperty = null;
		String keyColumn = null;
		// 表包含主键处理逻辑,如果不包含主键当普通字段处理
		if (StringUtils.isNotEmpty(tableInfo.getKeyProperty())) {
			if (tableInfo.getIdType() == IdType.AUTO) {
				/** 自增主键 */
				keyGenerator = new Jdbc3KeyGenerator();
				keyProperty = tableInfo.getKeyProperty();
				keyColumn = tableInfo.getKeyColumn();
			} else {
				if (null != tableInfo.getKeySequence()) {
					keyGenerator = TableInfoHelper.genKeyGenerator(tableInfo, builderAssistant, sqlMethod.getMethod(), languageDriver);
					keyProperty = tableInfo.getKeyProperty();
					keyColumn = tableInfo.getKeyColumn();
				}
			}
		}
		String sql = String.format(sqlTemplate, tableInfo.getTableName(), columnScript, valuesScript);
		SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
		return this.addInsertMappedStatement(mapperClass, modelClass, MAPPER_METHOD, sqlSource, keyGenerator, keyProperty, keyColumn);
	}
}
  1. 注入sql
mport com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.injector.AbstractSqlInjector;
import com.baomidou.mybatisplus.core.injector.methods.Insert;
import com.baomidou.mybatisplus.extension.injector.methods.*;

import java.util.List;
import java.util.stream.Stream;

import static java.util.stream.Collectors.toList;

/**
 * @author: EnumaElish
 * @Date: 2021/6/1 14:22
 * @Description: LogicSqlInjector 实现上加InsertIgnoreBatchAllColumn
 */
public class CustomerSqlInjector extends AbstractSqlInjector {

	@Override
	public List<AbstractMethod> getMethodList() {
		return Stream.of(
				new Insert(),
				new LogicDelete(),
				new LogicDeleteByMap(),
				new LogicDeleteById(),
				new LogicDeleteBatchByIds(),
				new LogicUpdate(),
				new LogicUpdateById(),
				new LogicSelectById(),
				new LogicSelectBatchByIds(),
				new LogicSelectByMap(),
				new LogicSelectOne(),
				new LogicSelectCount(),
				new LogicSelectMaps(),
				new LogicSelectMapsPage(),
				new LogicSelectObjs(),
				new LogicSelectList(),
				new LogicSelectPage(),
				// 批量新增
				new InsertIgnoreBatchAllColumn(t->!t.isLogicDelete())
		).collect(toList());
	}

}
  1. 配置DataSourceConfig,调整GlobalConfig中的setSqlInjector
@Configuration
@MapperScan(basePackages = "XXX",sqlSessionFactoryRef="sqlSessionFactory")
public class DataSourceConfig {
	
	// 忽略DataSource

	@Bean
	public GlobalConfig globalConfiguration() {
		GlobalConfig conf = new GlobalConfig();
		conf.setBanner(true)// 是否打印
//				.setSqlInjector(new LogicSqlInjector()) // 逻辑注入sql
				// 逻辑注入sql
				.setSqlInjector(new CustomerSqlInjector())
				.setDbConfig(new GlobalConfig.DbConfig()
						.setLogicDeleteValue("1")
						.setLogicNotDeleteValue("0")
						.setIdType(IdType.AUTO));// 使用数据库生成方式
		return conf;
	}
	/**
	 * 数据源sqlSessionFactory配置
	 * 更多参数设置参考https://mp.baomidou.com/config/#%E8%BF%9B%E9%98%B6%E9%85%8D%E7%BD%AE
	 */
	@Bean
	@Primary
	public SqlSessionFactory sqlSessionFactory(DataSource dataSource, PaginationInterceptor paginationInterceptor, GlobalConfig globalConfiguration) throws Exception {
		MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
		/* 数据源 */
		sqlSessionFactory.setDataSource(dataSource);
		/* 枚举扫描 */
		sqlSessionFactory.setTypeEnumsPackage("xxx");
//		/* xml路径配置 */
//		sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/*.xml"));
		/* entity扫描,mybatis的Alias功能 */
		sqlSessionFactory.setTypeAliasesPackage("xxx");
		/* entity扫描,mybatis的Alias功能 */
		MybatisConfiguration configuration = new MybatisConfiguration();
		configuration.setJdbcTypeForNull(JdbcType.NULL);
		/* 驼峰转下划线 */
		configuration.setMapUnderscoreToCamelCase(false);
		/* 分页插件 */
		configuration.addInterceptor(paginationInterceptor);
		/* 允许JDBC支持自动生成主键 */
		configuration.setUseGeneratedKeys(true);
		/* 乐观锁插件 */
		configuration.addInterceptor(new OptimisticLockerInterceptor());
		sqlSessionFactory.setConfiguration(configuration);
		sqlSessionFactory.setGlobalConfig(globalConfiguration);
		return sqlSessionFactory.getObject();
	}

}
  1. 通用mapper
public interface CommonMapper<T> extends BaseMapper<T> {

    /**
     * 全量插入,等价于insert,忽略唯一索引冲突的行
     * {@link InsertIgnoreBatchAllColumn}
     *
     * @param entityList
     * @return
     */
    int insertIgnoreBatchAllColumn(List<T> entityList);

}

  1. 通用Service
public class CommonServiceImpl<M extends CommonMapper<T>, T> extends ServiceImpl<M, T> {
	/**
	 * 根据业务情况调整最大每次批量数,mysql是有语句长度限制的,max_allow_packet是MySQL控制网络包大小的参数,有的版本默认1M,有的版本默认4M
	 */
	public static final int BATCH_SIZE = 500;

	/**
	 * 加事务是参考mybatis-plus saveBatch也加,防止非数据库异常需要回滚,如果要极限性能可自行去掉,自己做补偿重试
	 * @param list
	 * @param batchSize
	 * @return
	 */
	@Transactional(rollbackFor = Exception.class)
	public boolean fastSaveIgnoreBatch(List<T> list, int batchSize) {
		if(CollectionUtils.isEmpty(list)) {
			return true;
		}
		batchSize = batchSize < 1 ? BATCH_SIZE : batchSize;

		if(list.size() <= batchSize) {
			return retBool(baseMapper.insertIgnoreBatchAllColumn(list));
		}
		for (int fromIdx = 0 , endIdx = batchSize ; ; fromIdx += batchSize, endIdx += batchSize) {
			if(endIdx > list.size()) {
				endIdx = list.size();
			}
			baseMapper.insertIgnoreBatchAllColumn(list.subList(fromIdx, endIdx));
			if(endIdx == list.size()) {
				return true;
			}
		}
	}

	/**
	 * 加事务是参考mybatis-plus saveBatch也加,防止非数据库异常需要回滚,果要极限性能可自行去掉,自己做补偿重试
	 * @param list
	 * @return
	 */
	@Transactional(rollbackFor = Exception.class)
	public boolean fastSaveIgnoreBatch(List<T> list) {
		return fastSaveIgnoreBatch(list, BATCH_SIZE);
	}

}
  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MyBatis-Plus中实现批量删除的方法是通过在Mapper中定义一个方法来实现。在引用中的代码示例中,可以看到Mapper中的deleteBatchById方法用于批量删除超高数据。该方法接受一个参数deleteIds,即要删除的id列表,返回一个int类型的结果表示删除的记录数。 具体的删除操作是在Mapper的xml文件中定义的。引用中的xml代码片段展示了具体的删除操作。在delete标签中,通过foreach标签循环遍历deleteIds列表,将其中的每个id拼接到SQL语句中的IN条件中,实现批量删除。 总结来说,使用MyBatis-Plus进批量删除操作的步骤如下: 1. 在Mapper接口中定义一个批量删除方法,接受一个id列表作为参数。 2. 在对应的xml文件中编写删除操作的SQL语句,通过foreach标签遍历id列表,将其拼接到SQL语句中的IN条件中。 参考资料: <span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [mybatisPlus中 批量删除](https://blog.csdn.net/fortunate_leixin/article/details/123641954)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [MyBatis-Plus 删除操作](https://blog.csdn.net/weixin_43452424/article/details/118750771)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值