这里是引用
实现参考了 https://my.oschina.net/teddyIH/blog/4548046
由于版本不同,InsertIgnoreBatchAllColumn和CustomerSqlInjector实现不同,添加注释完善了说明
一、背景
- 在数据同步或者幂等场景下,常常需要设置唯一索引来避免重复请求,select and update效率低,且并发时还是会报错,并不友好,那么可以用Mysql的Insert ignore语法来优化。
- 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官方扩展包中
- 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);
}
}
- 注入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());
}
}
- 配置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();
}
}
- 通用mapper
public interface CommonMapper<T> extends BaseMapper<T> {
/**
* 全量插入,等价于insert,忽略唯一索引冲突的行
* {@link InsertIgnoreBatchAllColumn}
*
* @param entityList
* @return
*/
int insertIgnoreBatchAllColumn(List<T> entityList);
}
- 通用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);
}
}