背景
在使用Mybatis-plus进行项目开发的过程中由于有很多地方需要批量插入,而且普通的saveBatch是循环单条插入的,是对大数据量插入极不友好的一种插入方式,看到其官方文档中有提到一个insertBatchSomeColumn的方法,试用之后发现不太符合自己的预期效果,遂对其进行改造🔧
以下文档基于该版本进行改造,有其他问题可以查阅官方文档
<mybatis-plus.version>3.5.3.2</mybatis-plus.version>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-core</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-annotation</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
基础配置
@Configuration
public class MybatisPlusConfiguration {
// 按需注入自己需要的配置信息,我这里需要对租户配置做一些自定义的处理
@Bean
public BatchInsertSqlInjector easySqlInjector(@Autowired TenantFilterProperties tenantFilterProperties) {
return new BatchInsertSqlInjector(tenantFilterProperties);
}
}
@RequiredArgsConstructor
public class BatchInsertSqlInjector extends DefaultSqlInjector {
private final TenantFilterProperties tenantFilterProperties;
@Override
public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {
List<AbstractMethod> methodList = super.getMethodList(mapperClass, tableInfo);
// 批量插入
methodList.add(new CustomInsertBatchSomeColumnMethod(i -> i.getFieldFill() != FieldFill.UPDATE, tenantFilterProperties));
return methodList;
}
}
重写自定义插入方法逻辑
import com.baomidou.mybatisplus.annotation.IdType;
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.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.sql.SqlInjectionUtils;
import com.baomidou.mybatisplus.core.toolkit.sql.SqlScriptUtils;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import lombok.Setter;
import lombok.experimental.Accessors;
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;
public class CustomInsertBatchSomeColumnMethod extends AbstractMethod {
private static final String INSERT_SQL = "INSERT INTO %s %s VALUES %s";
private static final String CREATE_TIME_FIELD = "createTime";
private static final String CREATE_BY_FIELD = "createBy";
private static final String INSERT_BY_FIELD = "insertBy";
/**
* 字段筛选条件
*/
@Setter
@Accessors(chain = true)
private Predicate<TableFieldInfo> predicate;
private final TenantFilterProperties tenantFilterProperties;
/**
* 默认方法名
*
* @param predicate 字段筛选条件
*/
public CustomInsertBatchSomeColumnMethod(Predicate<TableFieldInfo> predicate, TenantFilterProperties tenantFilterProperties) {
// 此处定义的就是一个方法名,后续会在mapper上使用该方法
super("customInsertBatchSomeColumn");
this.predicate = predicate;
this.tenantFilterProperties = tenantFilterProperties;
}
@Override
// 这里的方法相当于写XML,这里只有启动的时候会扫描,并返回MappedStatement对象
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
KeyGenerator keyGenerator = NoKeyGenerator.INSTANCE;
// 自定义业务逻辑需求
boolean noTenant = (CollectionUtils.isEmpty(tenantFilterProperties.getTables()) || !tenantFilterProperties.getTables().contains(tableInfo.getTableName()));
String keyProperty = null;
String keyColumn = null;
// 表包含主键处理逻辑,如果不包含主键当普通字段处理
if (tableInfo.havePK()) {
if (tableInfo.getIdType() == IdType.AUTO) {
/* 自增主键生成方法,原先使用的Jdbc3KeyGenerator有点坑 */
// keyGenerator = Jdbc3KeyGenerator.INSTANCE;
keyGenerator = new CustomerKeyGenerator(tableInfo);
keyProperty = tableInfo.getKeyProperty();
// 去除转义符
keyColumn = SqlInjectionUtils.removeEscapeCharacter(tableInfo.getKeyColumn());
} else if (null != tableInfo.getKeySequence()) {
keyGenerator = TableInfoHelper.genKeyGenerator(this.methodName, tableInfo, builderAssistant);
keyProperty = tableInfo.getKeyProperty();
keyColumn = tableInfo.getKeyColumn();
}
}
// 生成默认的全字段的SQL
String defaultSql = getDefaultSql(tableInfo);
// 生成去除掉租户信息的SQL
String noTenantSql = getNoTenantSql(tableInfo);
// 这里比较关键,写好SQL模板,test里面的isolated是在外面通过方法参数传入的,noTenant的变量是在启动初始化就判断好了
String finalSql = "<script>\n"
+ " <choose>\n"
+ " <when test=\"isolated and " + noTenant + "\">\n"
+ noTenantSql
+ " </when>\n"
+ " <otherwise>\n"
+ defaultSql
+ " </otherwise>\n"
+ " </choose>\n"
+ " </script>";
SqlSource sqlSource = super.createSqlSource(configuration, finalSql, modelClass);
return this.addInsertMappedStatement(mapperClass, modelClass, methodName, sqlSource, keyGenerator, keyProperty, keyColumn);
}
/**
* 获取默认的SQL
* @param tableInfo
* @return
*/
private String getDefaultSql(TableInfo tableInfo) {
List<TableFieldInfo> fieldList = tableInfo.getFieldList();
return generateSql(tableInfo,fieldList);
}
/**
* 获取没有租户信息的SQL
* @param tableInfo
* @return
*/
private String getNoTenantSql(TableInfo tableInfo) {
List<TableFieldInfo> fieldList = tableInfo.getFieldList().stream().filter(filed -> {
return !filed.getProperty().equals(COMPANY_ID_FIELD) && !filed.getProperty().equals(CHAIN_ID_FIELD) && !filed.getProperty()
.equals(ORG_ID_FIELD);
}).collect(Collectors.toList());
return generateSql(tableInfo,fieldList);
}
/**
* 生成SQL
* @param tableInfo
* @param fieldList
* @return
*/
private String generateSql(TableInfo tableInfo,List<TableFieldInfo> fieldList) {
String insertSqlColumn = tableInfo.getKeyInsertSqlColumn(true, null, false) +
this.filterTableFieldInfo(fieldList, predicate, TableFieldInfo::getInsertSqlColumn, EMPTY);
String columnScript = LEFT_BRACKET + insertSqlColumn.substring(0, insertSqlColumn.length() - 1) + RIGHT_BRACKET;
String insertSqlProperty = tableInfo.getKeyInsertSqlProperty(true, ENTITY_DOT, false) +
this.filterTableFieldInfo(fieldList, predicate, i -> i.getInsertSqlProperty(ENTITY_DOT), EMPTY);
insertSqlProperty = LEFT_BRACKET + insertSqlProperty.substring(0, insertSqlProperty.length() - 1) + RIGHT_BRACKET;
String valuesScript = SqlScriptUtils.convertForeach(insertSqlProperty, "entityList", null, ENTITY, COMMA);
String finalValuesScript = "\n" + fillInsertField(fieldList, valuesScript) + "\n";
return String.format(INSERT_SQL, tableInfo.getTableName(), columnScript, finalValuesScript);
}
/**
* 填充创建所需要的字段信息,如创建时间、创建人等
* @param fieldList
* @param valuesScript
* @return
*/
private String fillInsertField(List<TableFieldInfo> fieldList,String valuesScript) {
for (TableFieldInfo tableFieldInfo : fieldList) {
// 字段名
String property = tableFieldInfo.getProperty();
// #{et.xxx} 对应foreach里的变量信息
String valueExp = String.format("#{et.%s}",property);
if (CREATE_TIME_FIELD.equals(property)) {
valuesScript = valuesScript.replace(valueExp, "\n<choose>\n"
+ " <when test=\"createTime != null and et.createTime == null\">\n"
+ " #{createTime}\n"
+ " </when>\n"
+ " <otherwise>\n"
+ " #{et.createTime}\n"
+ " </otherwise>\n"
+ "</choose>");
}
if (CREATE_BY_FIELD.equals(property)) {
valuesScript = valuesScript.replace(valueExp, "\n<choose>\n"
+ " <when test=\"createBy != null and et.createBy == null\">\n"
+ " #{createBy}\n"
+ " </when>\n"
+ " <otherwise>\n"
+ " #{et.createBy}\n"
+ " </otherwise>\n"
+ "</choose>");
}
if (INSERT_BY_FIELD.equals(property)) {
valuesScript = valuesScript.replace(valueExp, "\n<choose>\n"
+ " <when test=\"insertBy != null and et.insertBy == null\">\n"
+ " #{insertBy}\n"
+ " </when>\n"
+ " <otherwise>\n"
+ " #{et.insertBy}\n"
+ " </otherwise>\n"
+ "</choose>");
}
}
return valuesScript;
}
}
自定义主键生成方法
上面有段代码没有使用Mybatis-Plus默认的主键生成填充逻辑,后面改造使用自定义的主键生成回填逻辑,文末也会解读一下这个主键生成逻辑的源码的坑
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.Statement;
import java.util.Collection;
import java.util.Iterator;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.ExecutorException;
import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.Reflector;
import org.apache.ibatis.session.Configuration;
/**
* 自定义主键生成器
* 1、继承Jdbc3KeyGenerator,才能使用主键生成策略
* 2、仅用于insertBatchSomeColumn,其他方法需要特殊定制
* 3、适用于能返回自增主键的数据库,不支持的数据库也无法使用这个方法
*/
public class CustomerKeyGenerator extends Jdbc3KeyGenerator {
private TableInfo tableInfo;
public CustomerKeyGenerator(TableInfo tableInfo) {
this.tableInfo = tableInfo;
}
@Override
public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
// do nothing
}
@Override
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
final String[] keyProperties = ms.getKeyProperties();
if (keyProperties == null || keyProperties.length == 0) {
return;
}
// 支持主键返回的数据库才能使用这个方法
try (ResultSet rs = stmt.getGeneratedKeys()) {
final ResultSetMetaData rsmd = rs.getMetaData();
final Configuration configuration = ms.getConfiguration();
if (rsmd.getColumnCount() < keyProperties.length) {
// Error?
} else {
String keyProperty = tableInfo.getKeyProperty();
if (StringUtils.isBlank(keyProperty)) {
return;
}
Class<?> keyPropertiesClass = tableInfo.getKeyType();
// 通过反射获取到当前的数据对象
MetaObject metaParam = configuration.newMetaObject(parameter);
Object entityListObject = metaParam.getValue("entityList");
Collection<?> entityList = (Collection<?>) entityListObject;
Iterator<?> iterator = entityList.iterator();
// 主键列表
while (rs.next()) {
Object entity = iterator.next();
String columnName = rs.getMetaData().getColumnName(1);
if (!"GENERATED_KEY".equals(columnName)) {
continue;
}
// 根据主键列类型获取对应的主键值
Object value = rs.getObject(1, keyPropertiesClass);
Reflector reflector = tableInfo.getReflector();
// 使用mybatis自带的Reflector设置主键值
reflector.getSetInvoker(keyProperty).invoke(entity, new Object[]{value});
}
}
} catch (Exception e) {
throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e, e);
}
}
}
定义 CommonBaseMapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.google.common.base.Strings;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Map;
import org.apache.ibatis.annotations.Param;
public interface CommonBaseMapper<T> extends BaseMapper<T> {
/**
* 批量插入 仅适用于mysql
* 插入返回的ID会回显到list的对象中,创建信息(创建时间等)不会回显
* @param entityList 实体列表
* @return 影响行数
*/
default Integer customInsertBatchSomeColumn(@Param("entityList") Collection<T> entityList) {
// 自定义租户隔离逻辑
boolean isolated = ServiceContextHolder.getServiceContext().isIsolated();
// 填充创建时间-createTime
LocalDateTime createTime = LocalDateTime.now();
// 填充用户ID-createBy
Integer userId = ServiceContextHolder.getServiceContext().getUserId();
final Integer createBy = userId == null ? Tenant.FICTIONAL_USER_ID : userId;
// 填充用户账号-insertBy
String account = ServiceContextHolder.getServiceContext().getAccount();
final String insertBy = !Strings.isNullOrEmpty(account) ? account : Tenant.SYSTEM_ACCOUNT;
return customInsertBatchSomeColumn(isolated, createTime, createBy, insertBy, entityList);
}
/**
* 批量插入 仅适用于mysql
*
* @param isolated 是否隔离租户
* @param entityList 实体列表
* @return 影响行数
*/
// 无默认实现的 对应的就是 CustomInsertBatchSomeColumnMethod 方法实现的SQL模板配置
Integer customInsertBatchSomeColumn(
@Param("isolated") boolean isolated,
@Param("createTime") LocalDateTime createTime,
@Param("createBy") Integer createBy,
@Param("insertBy") String insertBy,
@Param("entityList") Collection<T> entityList
);
}
使用方法
@Repository
// 继承自定义的mapper,就能使用默认的方法了
public interface UserMapper extends CommonBaseMapper<User> {
}
@Component
public class UserService {
@Autowired
private UserMapper userMapper;
public int batchInsert(List<OrgAutodialerServer> list) {
return userMapper.customInsertBatchSomeColumn(list);
}
}
遇到的问题
1. 参数信息无法传递
通过接口的default默认实现来实现参数的传递
像这里的SQL片段createTime这种字段,批量插入是不走mybatis的MetaObjectHandler 的默认填充逻辑的,
createTime就是从外面的方法带入的数据信息,et.createTime就是循环内的创建时间,如果有值则默认不填充数据
<when test="createTime != null and et.createTime == null">\n"
#{createTime}\n"
</when>\n"
<otherwise>\n"
#{et.createTime}\n"
</otherwise>
最终出来的SQL应该是这样
INSERT INTO user (name,age,sex,create_time,insert_by,create_by) VALUES
<foreach collection="entityList" item="et" separator=",">
(
#{et.name},#{et.age},#{et.sex}
<choose>
<when test="createTime != null and et.createTime == null">
#{createTime}
</when>
<otherwise>
#{et.createTime}
</otherwise>
</choose>,
<choose>
<when test="insertBy != null and et.insertBy == null">
#{insertBy}
</when>
<otherwise>
#{et.insertBy}
</otherwise>
</choose>,
<choose>
<when test="createBy != null and et.createBy == null">
#{createBy}
</when>
<otherwise>
#{et.createBy}
</otherwise>
</choose>
)
</foreach>
2. 主键信息无法自动回填
上面有段代码没有使用Mybatis-Plus默认的主键生成填充逻辑,也顺便解读一下这个主键生成逻辑的源码的坑
// keyGenerator = Jdbc3KeyGenerator.INSTANCE;
keyGenerator = new CustomerKeyGenerator(tableInfo);
以下源码解析可以重点关注带中文标记的地方
public class Jdbc3KeyGenerator implements KeyGenerator {
// 这个常量很重要,后续可以重点关注,param2
private static final String SECOND_GENERIC_PARAM_NAME = ParamNameResolver.GENERIC_NAME_PREFIX + "2";
/**
* A shared instance.
*
* @since 3.4.3
*/
public static final Jdbc3KeyGenerator INSTANCE = new Jdbc3KeyGenerator();
private static final String MSG_TOO_MANY_KEYS = "Too many keys are generated. There are only %d target objects. "
+ "You either specified a wrong 'keyProperty' or encountered a driver bug like #1523.";
@Override
public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
// do nothing
}
@Override
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
// 实际处理动作
processBatch(ms, stmt, parameter);
}
public void processBatch(MappedStatement ms, Statement stmt, Object parameter) {
final String[] keyProperties = ms.getKeyProperties();
if (keyProperties == null || keyProperties.length == 0) {
return;
}
try (ResultSet rs = stmt.getGeneratedKeys()) {
final ResultSetMetaData rsmd = rs.getMetaData();
final Configuration configuration = ms.getConfiguration();
if (rsmd.getColumnCount() < keyProperties.length) {
// Error?
} else {
// 分配回填主键的方法
assignKeys(configuration, rs, rsmd, keyProperties, parameter);
}
} catch (Exception e) {
throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e, e);
}
}
@SuppressWarnings("unchecked")
private void assignKeys(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd, String[] keyProperties,
Object parameter) throws SQLException {
if (parameter instanceof ParamMap || parameter instanceof StrictMap) {
// Multi-param or single param with @Param
assignKeysToParamMap(configuration, rs, rsmd, keyProperties, (Map<String, ?>) parameter);
} else if (parameter instanceof ArrayList && !((ArrayList<?>) parameter).isEmpty()
&& ((ArrayList<?>) parameter).get(0) instanceof ParamMap) {
// Multi-param or single param with @Param in batch operation
assignKeysToParamMapList(configuration, rs, rsmd, keyProperties, (ArrayList<ParamMap<?>>) parameter);
} else {
// Single param without @Param
assignKeysToParam(configuration, rs, rsmd, keyProperties, parameter);
}
}
private void assignKeysToParam(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd,
String[] keyProperties, Object parameter) throws SQLException {
Collection<?> params = collectionize(parameter);
if (params.isEmpty()) {
return;
}
List<KeyAssigner> assignerList = new ArrayList<>();
for (int i = 0; i < keyProperties.length; i++) {
assignerList.add(new KeyAssigner(configuration, rsmd, i + 1, null, keyProperties[i]));
}
Iterator<?> iterator = params.iterator();
while (rs.next()) {
if (!iterator.hasNext()) {
throw new ExecutorException(String.format(MSG_TOO_MANY_KEYS, params.size()));
}
Object param = iterator.next();
assignerList.forEach(x -> x.assign(rs, param));
}
}
private void assignKeysToParamMapList(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd,
String[] keyProperties, ArrayList<ParamMap<?>> paramMapList) throws SQLException {
Iterator<ParamMap<?>> iterator = paramMapList.iterator();
List<KeyAssigner> assignerList = new ArrayList<>();
long counter = 0;
while (rs.next()) {
if (!iterator.hasNext()) {
throw new ExecutorException(String.format(MSG_TOO_MANY_KEYS, counter));
}
ParamMap<?> paramMap = iterator.next();
if (assignerList.isEmpty()) {
for (int i = 0; i < keyProperties.length; i++) {
assignerList
.add(getAssignerForParamMap(configuration, rsmd, i + 1, paramMap, keyProperties[i], keyProperties, false)
.getValue());
}
}
assignerList.forEach(x -> x.assign(rs, paramMap));
counter++;
}
}
private void assignKeysToParamMap(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd,
String[] keyProperties, Map<String, ?> paramMap) throws SQLException {
if (paramMap.isEmpty()) {
return;
}
Map<String, Entry<Iterator<?>, List<KeyAssigner>>> assignerMap = new HashMap<>();
for (int i = 0; i < keyProperties.length; i++) {
Entry<String, KeyAssigner> entry = getAssignerForParamMap(configuration, rsmd, i + 1, paramMap, keyProperties[i],
keyProperties, true);
Entry<Iterator<?>, List<KeyAssigner>> iteratorPair = MapUtil.computeIfAbsent(assignerMap, entry.getKey(),
k -> MapUtil.entry(collectionize(paramMap.get(k)).iterator(), new ArrayList<>()));
iteratorPair.getValue().add(entry.getValue());
}
long counter = 0;
while (rs.next()) {
for (Entry<Iterator<?>, List<KeyAssigner>> pair : assignerMap.values()) {
if (!pair.getKey().hasNext()) {
throw new ExecutorException(String.format(MSG_TOO_MANY_KEYS, counter));
}
Object param = pair.getKey().next();
pair.getValue().forEach(x -> x.assign(rs, param));
}
counter++;
}
}
// 重点关注这个方法
private Entry<String, KeyAssigner> getAssignerForParamMap(Configuration config, ResultSetMetaData rsmd,
int columnPosition, Map<String, ?> paramMap, String keyProperty, String[] keyProperties, boolean omitParamName) {
Set<String> keySet = paramMap.keySet();
// A caveat : if the only parameter has {@code @Param("param2")} on it,
// it must be referenced with param name e.g. 'param2.x'.
// 这里就是最顶上的常量所代表的含义,如果传入的参数不止有entityList的话(大于等于1个参数),这里的返回就会为false,不会回填ID,关键就是这里
boolean singleParam = !keySet.contains(SECOND_GENERIC_PARAM_NAME);
int firstDot = keyProperty.indexOf('.');
if (firstDot == -1) {
if (singleParam) {
return getAssignerForSingleParam(config, rsmd, columnPosition, paramMap, keyProperty, omitParamName);
}
throw new ExecutorException("Could not determine which parameter to assign generated keys to. "
+ "Note that when there are multiple parameters, 'keyProperty' must include the parameter name (e.g. 'param.id'). "
+ "Specified key properties are " + ArrayUtil.toString(keyProperties) + " and available parameters are "
+ keySet);
}
String paramName = keyProperty.substring(0, firstDot);
if (keySet.contains(paramName)) {
String argParamName = omitParamName ? null : paramName;
String argKeyProperty = keyProperty.substring(firstDot + 1);
return MapUtil.entry(paramName, new KeyAssigner(config, rsmd, columnPosition, argParamName, argKeyProperty));
}
if (singleParam) {
return getAssignerForSingleParam(config, rsmd, columnPosition, paramMap, keyProperty, omitParamName);
} else {
throw new ExecutorException("Could not find parameter '" + paramName + "'. "
+ "Note that when there are multiple parameters, 'keyProperty' must include the parameter name (e.g. 'param.id'). "
+ "Specified key properties are " + ArrayUtil.toString(keyProperties) + " and available parameters are "
+ keySet);
}
}
private Entry<String, KeyAssigner> getAssignerForSingleParam(Configuration config, ResultSetMetaData rsmd,
int columnPosition, Map<String, ?> paramMap, String keyProperty, boolean omitParamName) {
// Assume 'keyProperty' to be a property of the single param.
String singleParamName = nameOfSingleParam(paramMap);
String argParamName = omitParamName ? null : singleParamName;
return MapUtil.entry(singleParamName, new KeyAssigner(config, rsmd, columnPosition, argParamName, keyProperty));
}
private static String nameOfSingleParam(Map<String, ?> paramMap) {
// There is virtually one parameter, so any key works.
return paramMap.keySet().iterator().next();
}
private static Collection<?> collectionize(Object param) {
if (param instanceof Collection) {
return (Collection<?>) param;
}
if (param instanceof Object[]) {
return Arrays.asList((Object[]) param);
} else {
return Arrays.asList(param);
}
}
private static class KeyAssigner {
private final Configuration configuration;
private final ResultSetMetaData rsmd;
private final TypeHandlerRegistry typeHandlerRegistry;
private final int columnPosition;
private final String paramName;
private final String propertyName;
private TypeHandler<?> typeHandler;
protected KeyAssigner(Configuration configuration, ResultSetMetaData rsmd, int columnPosition, String paramName,
String propertyName) {
this.configuration = configuration;
this.rsmd = rsmd;
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.columnPosition = columnPosition;
this.paramName = paramName;
this.propertyName = propertyName;
}
// 实际填充数据的方法逻辑
protected void assign(ResultSet rs, Object param) {
if (paramName != null) {
// If paramName is set, param is ParamMap
param = ((ParamMap<?>) param).get(paramName);
}
MetaObject metaParam = configuration.newMetaObject(param);
try {
if (typeHandler == null) {
if (!metaParam.hasSetter(propertyName)) {
throw new ExecutorException("No setter found for the keyProperty '" + propertyName + "' in '"
+ metaParam.getOriginalObject().getClass().getName() + "'.");
}
Class<?> propertyType = metaParam.getSetterType(propertyName);
typeHandler = typeHandlerRegistry.getTypeHandler(propertyType,
JdbcType.forCode(rsmd.getColumnType(columnPosition)));
}
if (typeHandler == null) {
// Error?
} else {
Object value = typeHandler.getResult(rs, columnPosition);
metaParam.setValue(propertyName, value);
}
} catch (SQLException e) {
throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e,
e);
}
}
}
}