用过mybatis都清楚tk-mapper
tk-mapper 批量保存操作不知道用过没有 感觉很鸡肋就是动态拼接批量插入语句
效率低就算了 可是还硬性规定 接口限制实体包含id
属性并且必须为自增列
并且字段为null也会插入 想想心都累了 有的字段有默认值 你插入null 数据库统统报错
搞了很久终于把字段为null这一诟病给治好了
利用注解和反射给字段标注默认值
1. 定义默认值注解
package ;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 定义实体类字段注解
*
* @author
* @since 2021-07-31
*/
public interface TableField {
/**
* String类型字段默认值(数据库默认值,批量插入数据时,该值必填)
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ColumnDefaultStringValue {
String value();
}
/**
* int类型字段默认值(数据库默认值,批量插入数据时,该值必填)
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ColumnDefaultIntValue {
int value();
}
/**
* double类型字段默认值(数据库默认值,批量插入数据时,该值必填)
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ColumnDefaultDoubleValue {
double value();
}
/**
* boolean类型字段默认值(数据库默认值,批量插入数据时,该值必填)
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ColumnDefaultBooleanValue {
boolean value();
}
/**
* char类型字段默认值(数据库默认值,批量插入数据时,该值必填)
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ColumnDefaultCharValue {
char value();
}
/**
* Date类型字段默认值(数据库默认值,批量插入数据时,该值默认系统时间)
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ColumnDefaultDateValue {
String value() default "";
}
}
2. 重写框架自带的批量插入方法
package ;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import com.google.common.collect.Lists;
import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ReflectUtil;
import lombok.experimental.Accessors;
import tk.mybatis.mapper.common.MySqlMapper;
/**
* 批量插入,支持批量插入的数据库可以使用,另外该接口限制实体包含`id`属性并且必须为自增列 </br>
* 条件:限制实体包含`id`属性并且必须为自增列</br>
*
* @author
* @since 2021-07-31
* @param <T>
*/
@SuppressWarnings("unchecked")
public interface BatchInsertListMapper<T> extends MySqlMapper<T> {
/**
* 批量插入数据 规定1000条执行一次批量插入,null的属性也会保存,如果数据库有默认值,会覆盖null值</br>
* 实体类字段请使用 {@link cn.com.insurance.admin.internal.annotation.TableField} 注解标注默认值</br>
*/
default int batchInsertList(List<? extends T> recordList) {
// 填充字段默认值
new BatchInsertListMapper.Inner<T>().fillFieldDefaultValue(recordList);
List<?> partition = Lists.partition(recordList, Inner.BATCH_SIZE);
int effectRow = 0;
for (Object item : partition) {
effectRow += insertList((List<? extends T>) item);
}
return effectRow;
};
@Accessors(chain = true)
class Inner<T> {
/**
* 注解ID标识
*/
private static final String PRIMARY_ID = "id";
/**
* 批量插入数据大小
*/
private static final int BATCH_SIZE = 1000;
/**
* 注解默认属性名
*/
private static final String ANNOTATION_DEFAULT_ATTRIBUTE = "value";
/**
* 字段默认值缓存
*/
private static final Map<String, Object> FIELD_DEFAULT_VALUE_MAP = new ConcurrentHashMap<>();
/**
* TableField注解类所在包名
*/
private static final String TABLE_FIELD_ANNOTATION_BASE_PACKAGE = TableField.class.getPackage().getName();
/**
* 扫描所有填充字段默认值注解的类
*/
private static final Set<Class<?>> ANNOTATION_CLASSES = ClassUtil.scanPackage(
TABLE_FIELD_ANNOTATION_BASE_PACKAGE,
clazz -> clazz != null && clazz.isAnnotation() && clazz.getName().contains(TableField.class.getName()));
/**
* 填充字段默认值
*
* @param recordList
*/
private void fillFieldDefaultValue(List<? extends T> recordList) {
try {
for (T item : recordList) {
// 获取实体类fields
Field[] fields = ReflectUtil.getFields(item.getClass());
for (Field field : fields) {
// 字段值为空或者不是主键就赋默认值
if (ReflectUtil.getFieldValue(item, field) == null
|| !Objects.equals(field.getName(), PRIMARY_ID)) {
// 从缓存取
Object object = FIELD_DEFAULT_VALUE_MAP.get(field.toGenericString());
if (object != null) {
ReflectUtil.setFieldValue(item, field, object);
} else {
// 手动查找
searchFieldDefaultValue(item, field);
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 手动查找字段默认值
*
* @param item
* @param field
*/
private void searchFieldDefaultValue(T item, Field field) {
for (Class<?> clazz : ANNOTATION_CLASSES) {
Map<String, Object> annotationValueMap = AnnotationUtil.getAnnotationValueMap(field,
(Class<? extends Annotation>) clazz);
if (MapUtil.isNotEmpty(annotationValueMap)) {
// 获取注解值即填充字段值
Object fillValue = annotationValueMap.get(ANNOTATION_DEFAULT_ATTRIBUTE);
// 普通注解
if ((fillValue.getClass() == Double.class
&& field.getType() == BigDecimal.class) ? true : field.getType() == fillValue.getClass()) {
ReflectUtil.setFieldValue(item, field, fillValue);
FIELD_DEFAULT_VALUE_MAP.put(field.toGenericString(), fillValue);
}
// 时间注解
if (field.isAnnotationPresent(ColumnDefaultDateValue.class)) {
ReflectUtil.setFieldValue(item, field, new Date());
}
}
}
}
}
}
3. 声明定义的批量插入接口
package ;
import tk.mybatis.mapper.annotation.RegisterMapper;
import tk.mybatis.mapper.common.Mapper;
import tk.mybatis.mapper.common.Marker;
/**
* Mapper 继承该接口后,无需编写 mapper.xml 文件,即可获得CRUD功能
*
* @author
* @since 2021-5-21
*/
@RegisterMapper
public interface CoreMapper<T> extends Mapper<T>, BatchInsertListMapper<T>, Marker {
}
至此项目中只要 dao 层继承 CoreMapper 接口即可获取批量插入功能并且会插入字段默认值(字段本身有值不会覆盖)