当前位置: 首页 > 工具软件 > common-mapper > 使用案例 >

跟我一起定制tk-mapper

姚浩歌
2023-12-01

用过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>
	 * 实体类字段请使用&nbsp{@link cn.com.insurance.admin.internal.annotation.TableField}&nbsp注解标注默认值</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 接口即可获取批量插入功能并且会插入字段默认值(字段本身有值不会覆盖)

 类似资料: