一、场景
- Mybatis Plus默认提供了
insertBatchSomeColumn
选装件 - 当批量插入的PO对象是NULL值,且数据库字段是NotNull且有默认值时就会报Value Not Null异常
二、解决思路
- 在代码生成器时对PO对象赋予默认值
- 在BaseServiceImpl实现类中对PO对象值为Null,数据库字段NotNull且有默认的值字段自动设置默认值
三、实现
3.1 代码生成器解决方法
- 重写AutoGenerator
@Data
@Accessors(chain = true)
public class AutoGeneratorHelper {
private static final Logger logger = LoggerFactory.getLogger(AutoGeneratorHelper.class);
protected ConfigBuilder config;
@Getter(AccessLevel.NONE)
@Setter(AccessLevel.NONE)
protected InjectionConfig injectionConfig;
private DataSourceConfig dataSource;
private StrategyConfig strategy;
private PackageConfig packageInfo;
private TemplateConfig template;
private GlobalConfig globalConfig;
private AbstractTemplateEngine templateEngine;
public void execute() {
logger.debug("==========================准备生成文件...==========================");
if (null == config) {
config = new ConfigBuilder(packageInfo, dataSource, strategy, template, globalConfig);
if (null != injectionConfig) {
injectionConfig.setConfig(config);
}
}
processTableFieldDefaultValue();
if (null == templateEngine) {
templateEngine = new VelocityTemplateEngine();
}
templateEngine.init(this.pretreatmentConfigBuilder(config)).mkdirs().batchOutput().open();
logger.debug("==========================文件生成完成!!!==========================");
}
private void processTableFieldDefaultValue() {
for (TableInfo tableInfo : config.getTableInfoList()) {
for (TableField field : tableInfo.getFields()) {
Map<String, Object> customMap = field.getCustomMap();
DbColumnType columnType = (DbColumnType) field.getColumnType();
String defaultValue = (String) customMap.get("Default");
if (Objects.isNull(defaultValue)) {
continue;
}
Object val = null;
switch (columnType) {
case BYTE:
case SHORT:
case CHARACTER:
case INTEGER:
val = defaultValue;
break;
case LONG:
val = defaultValue + "L";
break;
case FLOAT:
val = defaultValue + "F";
break;
case DOUBLE:
val = defaultValue + "D";
break;
case BOOLEAN:
val = "Boolean.valueOf(\"" + defaultValue + "\")";
break;
case STRING:
val = "\"" + defaultValue + "\"";
break;
case LOCAL_DATE:
if ("CURRENT_TIMESTAMP".equals(defaultValue)) {
val = "LocalDate.now()";
break;
}
val = "LocalDateTime.parse(\""+ defaultValue + "\", DateTimeFormatter.ofPattern("yyyy-MM-dd")).toLocalDate()";
break;
case LOCAL_TIME:
if ("CURRENT_TIMESTAMP".equals(defaultValue)) {
val = "LocalTime.now()";
break;
}
val = "LocalDateTime.parse(\""+ defaultValue + "\", DateTimeFormatter.ofPattern("HH:mm:ss")).toLocalTime()";
break;
case LOCAL_DATE_TIME:
if ("CURRENT_TIMESTAMP".equals(defaultValue)) {
val = "LocalDateTime.now()";
break;
}
val = "LocalDateTime.parse(\""+ defaultValue + "\", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))";
break;
case DATE:
val = "new Date()";
break;
case BIG_INTEGER:
val = "new BigInteger(\"" + defaultValue + "\")";
break;
case BIG_DECIMAL:
val = "new BigDecimal(\"" + defaultValue + "\")";
break;
}
customMap.put("Default", val);
customMap.put("DefaultValueFieldName", "DEFAULT_" + CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, field.getPropertyName()));
}
}
}
protected List<TableInfo> getAllTableInfoList(ConfigBuilder config) {
return config.getTableInfoList();
}
protected ConfigBuilder pretreatmentConfigBuilder(ConfigBuilder config) {
if (null != injectionConfig) {
injectionConfig.initMap();
config.setInjectionConfig(injectionConfig);
}
List<TableInfo> tableList = this.getAllTableInfoList(config);
for (TableInfo tableInfo : tableList) {
if (config.getGlobalConfig().isActiveRecord()) {
tableInfo.setImportPackages(Model.class.getCanonicalName());
}
if (tableInfo.isConvert()) {
tableInfo.setImportPackages(TableName.class.getCanonicalName());
}
if (config.getStrategyConfig().getLogicDeleteFieldName() != null && tableInfo.isLogicDelete(config.getStrategyConfig().getLogicDeleteFieldName())) {
tableInfo.setImportPackages(TableLogic.class.getCanonicalName());
}
if (StringUtils.isNotBlank(config.getStrategyConfig().getVersionFieldName())) {
tableInfo.setImportPackages(Version.class.getCanonicalName());
}
boolean importSerializable = true;
if (StringUtils.isNotBlank(config.getSuperEntityClass())) {
tableInfo.setImportPackages(config.getSuperEntityClass());
importSerializable = false;
}
if (config.getGlobalConfig().isActiveRecord()) {
importSerializable = true;
}
if (importSerializable) {
tableInfo.setImportPackages(Serializable.class.getCanonicalName());
}
if (config.getStrategyConfig().isEntityBooleanColumnRemoveIsPrefix()
&& CollectionUtils.isNotEmpty(tableInfo.getFields())) {
List<TableField> tableFields = tableInfo.getFields().stream().filter(field -> "boolean".equalsIgnoreCase(field.getPropertyType()))
.filter(field -> field.getPropertyName().startsWith("is")).collect(Collectors.toList());
tableFields.forEach(field -> {
if (field.isKeyFlag()) {
tableInfo.setImportPackages(TableId.class.getCanonicalName());
} else {
tableInfo.setImportPackages(com.baomidou.mybatisplus.annotation.TableField.class.getCanonicalName());
}
field.setConvert(true);
field.setPropertyName(StringUtils.removePrefixAfterPrefixToLower(field.getPropertyName(), 2));
});
}
}
return config.setTableInfoList(tableList);
}
public InjectionConfig getCfg() {
return injectionConfig;
}
public AutoGeneratorHelper setCfg(InjectionConfig injectionConfig) {
this.injectionConfig = injectionConfig;
return this;
}
}
- 修改配置
AutoGeneratorHelper autoGenerator = new AutoGeneratorHelper();
DataSourceConfig dataSourceConfig = getDataSourceConfig(generateDTO);
dataSourceConfig.setDbQuery(new MySqlQuery() {
@Override
public String[] fieldCustom() {
return new String[]{"Default"};
}
});
autoGenerator.setDataSource(dataSourceConfig);
- 编写Entity模板
#if(${field.customMap.Default})
#if(!${field.fill})
@TableField(fill = FieldFill.INSERT)
#end
private ${field.propertyType} ${field.propertyName};
public static final ${field.propertyType} ${field.customMap.DefaultValueFieldName} = ${field.customMap.Default};
#end
#if(!${field.customMap.Default})
private ${field.propertyType} ${field.propertyName};
#end
- DAO层
public interface CommonMapper<T> extends BaseMapper<T> {
int insertBatchSomeColumn(List<T> entityList);
}
- BaseServiceImpl对Null值处理
public class CommonServiceImpl<M extends CommonMapper<T>, T> extends ServiceImpl<M, T> {
private static ConcurrentMap<String, List<Field>> defaultValueFields = new ConcurrentHashMap<>();
private static ConcurrentMap<Field, Object> defaultFieldValue = new ConcurrentHashMap<>();
private static final int BATCH_SIZE = 1000;
private static final String DEFAULT_VALUE_PREFIX = "DEFAULT_";
@Transactional(rollbackFor = Exception.class)
public boolean fastSaveBatch(List<T> list, int batchSize) {
if(CollectionUtils.isEmpty(list)) {
return true;
}
processDefaultValue(list);
batchSize = batchSize < 1 ? BATCH_SIZE : batchSize;
if(list.size() <= batchSize) {
return retBool(baseMapper.insertBatchSomeColumn(list));
}
for (int fromIdx = 0 , endIdx = batchSize ; ; fromIdx += batchSize, endIdx += batchSize) {
if(endIdx > list.size()) {
endIdx = list.size();
}
baseMapper.insertBatchSomeColumn(list.subList(fromIdx, endIdx));
if(endIdx == list.size()) {
return true;
}
}
}
@Transactional(rollbackFor = Exception.class)
public boolean fastSaveBatch(List<T> list) {
return fastSaveBatch(list, BATCH_SIZE);
}
private void processDefaultValue(List<T> list) {
try {
Class<T> clz = (Class<T>) list.get(0).getClass();
List<Field> fields = getFilDefaultFields(clz);
for (T t1 : list) {
for (Field field : fields) {
ReflectionUtils.makeAccessible(field);
Object value = ReflectionUtils.getField(field, t1);
if (Objects.isNull(value)) {
Object fieldDefaultValue = getFieldDefaultValue(clz, field);
if (Objects.nonNull(fieldDefaultValue)) {
ReflectionUtils.setField(field, t1, fieldDefaultValue);
}
}
}
}
}catch (Exception e) {
log.error(e.getMessage(), e);
throw new RuntimeException(e);
}
}
private List<Field> getFilDefaultFields(Class<T> clz) {
List<Field> fields = defaultValueFields.get(clz.getName());
if (Objects.isNull(fields)) {
fields = Stream.of(clz.getDeclaredFields())
.filter(o -> {
TableField annotation = o.getAnnotation(TableField.class);
return Objects.nonNull(annotation) && FieldFill.INSERT == annotation.fill();
}).collect(Collectors.toList());
defaultValueFields.putIfAbsent(clz.getName(), fields);
}
return fields;
}
private Object getFieldDefaultValue(Class<T> clz, Field field) throws IllegalAccessException, InstantiationException {
Object value = defaultFieldValue.get(field);
if (Objects.isNull(value)) {
T t = clz.newInstance();
String name = DEFAULT_VALUE_PREFIX + CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, field.getName());
Field defaultValueField = ReflectionUtils.findField(clz, name);
if (Objects.isNull(defaultValueField)) {
throw new RuntimeException("can not find field:" + field.getName() + "default value");
}
value = ReflectionUtils.getField(defaultValueField, t);
if (Objects.nonNull(value)) {
defaultFieldValue.putIfAbsent(field, value);
}
}
return value;
}
}
四、优化
- 针对反射的赋值取值操作可以用并行流、多线程等方式提升性能、或者可以参考
Mapstruct
生成检测空值和赋值操作