在上一章內(nèi)容中我們又欠下了這些技術(shù)債:
parameterHandler
resultSetHandler
KeyGenerator
今天我們就來著重解決一下關(guān)于KeyGenerator的源碼藐唠。
1. KeyGenerator解析
首先我們需要了解這個(gè)類主要功能甸怕。我們來看官網(wǎng)介紹:
selectKey 元素中的語句將會首先或之后運(yùn)行胰舆,然后插入語句會被調(diào)用硕旗。這可以提供給你一個(gè)與數(shù)據(jù)庫中自動生成主鍵類似的行為,同時(shí)保持了 Java 代碼的簡潔晃痴。
我們再來看下有哪些屬性:
介紹完之后我們再來看下源碼绞佩,先來看接口源碼冬阳,其中的方法對應(yīng)order屬性的順序:
public interface KeyGenerator {
void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter);
void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter);
}
他子類繼承順序:我們以一個(gè)例子來進(jìn)行說明:
@Options(useGeneratedKeys = true, keyProperty = "id")
@Insert({"insert into country (countryname,countrycode) values (#{countryname},#{countrycode})"})
int insertBean(Country country);
然后我們來看一處源碼,看用的是KeyGenerator的哪個(gè)子類媳谁。在MappedStatement的Builder內(nèi)部類中:
mappedStatement.keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
我們可以看到用的是Jdbc3KeyGenerator這個(gè)類涂滴。
2. Jdbc3KeyGenerator解析
首先我們先來看類構(gòu)造方法:
public class Jdbc3KeyGenerator implements KeyGenerator {
/**
* A shared instance.
*
* @since 3.4.3
*/
public static final Jdbc3KeyGenerator INSTANCE = new Jdbc3KeyGenerator();
這里可以看到我們用了單例模式中的餓漢式。再來看他的實(shí)現(xiàn)方法:
@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);
}
繼續(xù)深入:
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);
}
}
那么重點(diǎn)來了:
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);
}
}
我們按照順序來進(jìn)行分析锤躁,首先便是多@Param參數(shù)情況下搁料,會形成一個(gè)ParamMap,之后調(diào)用 assignKeysToParamMap方法:
private void assignKeysToParamMap(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd,
String[] keyProperties, Map<String, ?> paramMap) throws SQLException {
//如果參數(shù)為空直接返回
if (paramMap.isEmpty()) {
return;
}
Map<String, Entry<Iterator<?>, List<KeyAssigner>>> assignerMap = new HashMap<>();
for (int i = 0; i < keyProperties.length; i++) {
//獲取組成形成KeyAssigner
Entry<String, KeyAssigner> entry = getAssignerForParamMap(configuration, rsmd, i + 1, paramMap, keyProperties[i],
keyProperties, true);
Entry<Iterator<?>, List<KeyAssigner>> iteratorPair = assignerMap.computeIfAbsent(entry.getKey(),
k -> 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++;
}
}
大概邏輯就是整理完之后挨個(gè)遍歷賦值系羞,這里重點(diǎn)便是getAssignerForParamMap方法郭计,我們進(jìn)入一起看下:
private Entry<String, KeyAssigner> getAssignerForParamMap(Configuration config, ResultSetMetaData rsmd,
int columnPosition, Map<String, ?> paramMap, String keyProperty, String[] keyProperties, boolean omitParamName) {
//判斷paramMap是否只有一個(gè)唯一key
boolean singleParam = paramMap.values().stream().distinct().count() == 1;
//獲取keyProperty的.的位置
int firstDot = keyProperty.indexOf('.');
//如果是多個(gè)參數(shù),但是keyProperty又不包含.椒振,會拋出錯(cuò)誤
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 "
+ paramMap.keySet());
}
//多參數(shù)昭伸,需要截取keyProperty的.之前部分,去paramMap拿對應(yīng)值
String paramName = keyProperty.substring(0, firstDot);
if (paramMap.containsKey(paramName)) {
String argParamName = omitParamName ? null : paramName;
String argKeyProperty = keyProperty.substring(firstDot + 1);
return entry(paramName, new KeyAssigner(config, rsmd, columnPosition, argParamName, argKeyProperty));
} else 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 "
+ paramMap.keySet());
}
}
根據(jù)注釋澎迎,我們能看懂個(gè)大概庐杨,之后就進(jìn)入getAssignerForSingleParam方法:
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 entry(singleParamName, new KeyAssigner(config, rsmd, columnPosition, argParamName, keyProperty));
}
還記得之前說的遍歷賦值邏輯么选调,也就是
pair.getValue().forEach(x -> x.assign(rs, param));方法中賦值:
我們來看下這個(gè)方法:
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)) {
Class<?> propertyType = metaParam.getSetterType(propertyName);
typeHandler = typeHandlerRegistry.getTypeHandler(propertyType,
JdbcType.forCode(rsmd.getColumnType(columnPosition)));
} else {
throw new ExecutorException("No setter found for the keyProperty '" + propertyName + "' in '"
+ metaParam.getOriginalObject().getClass().getName() + "'.");
}
}
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);
}
}
}
這里就是借助一些輔助的類來進(jìn)行反射賦值了,邏輯自己整理下不難灵份。下面我們也過一下SelectKeyGenerator類仁堪。
3. SelectKeyGenerator解析
我們先繼續(xù)來看構(gòu)造方法:
public class SelectKeyGenerator implements KeyGenerator {
public static final String SELECT_KEY_SUFFIX = "!selectKey";
private final boolean executeBefore;
private final MappedStatement keyStatement;
public SelectKeyGenerator(MappedStatement keyStatement, boolean executeBefore) {
this.executeBefore = executeBefore;
this.keyStatement = keyStatement;
}
}
之后我們整體再來看一下它的繼承實(shí)現(xiàn),我們只是簡單的過一下各吨,不會具體深入:
@Override
public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
if (executeBefore) {
processGeneratedKeys(executor, ms, parameter);
}
}
@Override
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
if (!executeBefore) {
processGeneratedKeys(executor, ms, parameter);
}
}
private void processGeneratedKeys(Executor executor, MappedStatement ms, Object parameter) {
try {
if (parameter != null && keyStatement != null && keyStatement.getKeyProperties() != null) {
String[] keyProperties = keyStatement.getKeyProperties();
final Configuration configuration = ms.getConfiguration();
final MetaObject metaParam = configuration.newMetaObject(parameter);
if (keyProperties != null) {
// Do not close keyExecutor.
// The transaction will be closed by parent executor.
Executor keyExecutor = configuration.newExecutor(executor.getTransaction(), ExecutorType.SIMPLE);
List<Object> values = keyExecutor.query(keyStatement, parameter, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER);
if (values.size() == 0) {
throw new ExecutorException("SelectKey returned no data.");
} else if (values.size() > 1) {
throw new ExecutorException("SelectKey returned more than one value.");
} else {
MetaObject metaResult = configuration.newMetaObject(values.get(0));
if (keyProperties.length == 1) {
if (metaResult.hasGetter(keyProperties[0])) {
setValue(metaParam, keyProperties[0], metaResult.getValue(keyProperties[0]));
} else {
// no getter for the property - maybe just a single value object
// so try that
setValue(metaParam, keyProperties[0], values.get(0));
}
} else {
handleMultipleProperties(keyProperties, metaParam, metaResult);
}
}
}
}
} catch (ExecutorException e) {
throw e;
} catch (Exception e) {
throw new ExecutorException("Error selecting key or setting result to parameter object. Cause: " + e, e);
}
}
這里主要SQL之前還是之后的都會到相同的方法枝笨,方法內(nèi)部就是新建一個(gè)Executor,然后向數(shù)據(jù)庫發(fā)送一條簡單的sql執(zhí)行語句揭蜒,返回內(nèi)容則賦值到參數(shù)中横浑。
最后還剩一個(gè)NoKeyGenerator
4. NoKeyGenerator解析
public class NoKeyGenerator implements KeyGenerator {
/**
* A shared instance.
* @since 3.4.3
*/
public static final NoKeyGenerator INSTANCE = new NoKeyGenerator();
@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) {
// Do Nothing
}
}
這個(gè)類就比較簡單了,沒什么特殊作用屉更。
5. 今日總結(jié)
今天我們接觸了關(guān)于KeyGenerator的源碼徙融,理解了他們的作用,我們再來做下小結(jié):
Jdbc3KeyGenerator:只能在執(zhí)行SQL之后瑰谜,它是一起的欺冀,通過業(yè)務(wù)SQL的執(zhí)行從中獲取數(shù)據(jù)庫自增主鍵信息并匹配復(fù)制
SelectKeyGenerator: 可以在執(zhí)行SQL之前或之后運(yùn)行,都是通過另起訪問一個(gè)簡單的SQL查詢到值并進(jìn)行注入
NoKeyGenerator: 什么操作都沒有