Mybatis源码阅读(三):结果集映射3.3 —— 主键生成策略
*************************************優(yōu)雅的分割線 **********************************
分享一波:程序員賺外快-必看的巔峰干貨
如果以上內(nèi)容對你覺得有用,并想獲取更多的賺錢方式和免費(fèi)的技術(shù)教程
請關(guān)注微信公眾號:HB荷包
一個能讓你學(xué)習(xí)技術(shù)和賺錢方法的公眾號,持續(xù)更新
*************************************優(yōu)雅的分割線 **********************************
前言
在前面兩篇博客中,我們介紹了對于select語句的簡單映射和嵌套映射。mybatis中使用ResultHandler等一系列的類,將查詢結(jié)果封裝到實體類中,可以說是mybatis中最復(fù)雜的過程,而剩下的insert、update、delete語句的操作則顯得較為簡單,沒有復(fù)雜的映射邏輯。這里需要提的是在insert語句中,關(guān)于主鍵自增的問題。
KeyGenerator
在我們實際的開發(fā)中,自增主鍵還是使用比較多的。而mybatis在處理insert語句時,并不會將自增主鍵給返回,而是返回插入的條數(shù)。當(dāng)我們有業(yè)務(wù)需求要獲取插入時產(chǎn)生的自增主鍵(或者其他類型不由程序生成的主鍵)時,則可以使用KeyGenerator。
KeyGenerator是個接口,定義了processBefore和processAfter兩個方法,分別在insert之前和之后執(zhí)行,代碼如下。
/**
-
insert語句不會反回自動生成的主鍵
-
該接口用于在插入記錄時獲取自增主鍵
-
@author Clinton Begin
*/
public interface KeyGenerator {/**
- 執(zhí)行insert之前執(zhí)行,設(shè)置屬性order=“BEFORE”
- @param executor
- @param ms
- @param stmt
- @param parameter
*/
void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter);
/**
- 執(zhí)行insert之后執(zhí)行,設(shè)置屬性order=“AFTER”
- @param executor
- @param ms
- @param stmt
- @param parameter
*/
void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter);
}
KeyGenerator有三個實現(xiàn)類
其中NoKeyGenerator的方法都是空實現(xiàn),本文不再講解。
Jdbc3KeyGenerator
Jdbc3KeyGenerator用于取回數(shù)據(jù)庫生成的自增id,它對應(yīng)于mybatis-config.xml配置的useGeneratedKeys,以及insert節(jié)點中配置的useGeneratedKeys屬性。
Jdbc3KeyGenerator的processBefore是空實現(xiàn),只實現(xiàn)了processAfter方法,該方法會調(diào)用processBatch方法將SQL語句執(zhí)行后生成的主鍵記錄到用戶傳遞的實參中,而不是直接返回。代碼如下。
/*** 核心方法,將sql語句生成的主鍵存放到參數(shù)中** @param ms* @param stmt* @param parameter*/ public void processBatch(MappedStatement ms, Statement stmt, Object parameter) {// 獲取sql節(jié)點配置的keyProperties,指定主鍵的字段final String[] keyProperties = ms.getKeyProperties();if (keyProperties == null || keyProperties.length == 0) {return;}// 獲取數(shù)據(jù)庫生成的主鍵。如果沒有生成主鍵則結(jié)果集為空try (ResultSet rs = stmt.getGeneratedKeys()) {// 獲取resultSet元信息final ResultSetMetaData rsmd = rs.getMetaData();// 獲取Configurationfinal Configuration configuration = ms.getConfiguration();// 查到的列數(shù)小于主鍵的列數(shù),說明查詢有誤。mybatis不進(jìn)行處理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);} }在assignKeys方法中,處理對主鍵賦值的邏輯。該方法會判斷參數(shù)的類型,將Map、集合、對象三種情況分別進(jìn)行處理。代碼如下。
private void assignKeys(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd, String[] keyProperties,Object parameter) throws SQLException {if (parameter instanceof ParamMap || parameter instanceof StrictMap) {// 參數(shù)是mapassignKeysToParamMap(configuration, rs, rsmd, keyProperties, (Map<String, ?>) parameter);} else if (parameter instanceof ArrayList && !((ArrayList<?>) parameter).isEmpty()&& ((ArrayList<?>) parameter).get(0) instanceof ParamMap) {// 參數(shù)是集合assignKeysToParamMapList(configuration, rs, rsmd, keyProperties, ((ArrayList<ParamMap<?>>) parameter));} else {// 參數(shù)是對象assignKeysToParam(configuration, rs, rsmd, keyProperties, parameter);} }三個方法邏輯類似,這里只對assignKeysToParam方法進(jìn)行分析,。
該方法會在進(jìn)入方法時,將參數(shù)轉(zhuǎn)為集合,并遍歷集合去給主鍵進(jìn)行賦值,代碼如下。
/*** 當(dāng)參數(shù)是對象時的處理邏輯** @param configuration* @param rs* @param rsmd* @param keyProperties* @param parameter* @throws SQLException*/ private void assignKeysToParam(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd,String[] keyProperties, Object parameter) throws SQLException {// 將參數(shù)轉(zhuǎn)為集合,此時這個集合最多只有一個對象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));} }SelectKeyGenerator
MySql、PostgreSql等數(shù)據(jù)庫支持自增主鍵,在執(zhí)行insert時可以不指定主鍵,插入的過程中由數(shù)據(jù)庫去生成自增主鍵。而像Oracle、DB2等數(shù)據(jù)庫產(chǎn)品則需要使用sequence實現(xiàn)自增id,因此在insert之前必須明確指定主鍵的值。SelectKeyGenerator就用來處理不支持自增主鍵的數(shù)據(jù)庫。
SelectKeyGenerator主要用于生成主鍵,它會執(zhí)行映射配置文件中定義的selectKey節(jié)點的sql,該語句會獲取insert語句所需要的主鍵。
SelectKeyGenerator中有兩個核心字段,keyStatement用于存放解析后的selectKey節(jié)點,executeBefore用于標(biāo)識該節(jié)點是在insert之前還是之后執(zhí)行。
/*** 標(biāo)識selectKey是在insert之前還是之后執(zhí)行* true為之前,false為之后*/ private final boolean executeBefore; /*** 存放selectKey節(jié)點,用于獲取insert語句使用的主鍵*/ private final MappedStatement keyStatement;SelectKeyGenerator的processBefore和processAfter都是執(zhí)行processGeneratorKeys方法,根據(jù)executeBefore字段的值決定是在insert之前還是之后執(zhí)行。
@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);} }processGeneratorKeys方法是處理主鍵的核心代碼。該方法會先獲取selectKey節(jié)點的keyProperties配置的屬性名稱,該屬性對應(yīng)著主鍵。接著,創(chuàng)建Executor執(zhí)行器,執(zhí)行selectKey節(jié)點中的sql,查詢主鍵。Executor是mybatis的核心執(zhí)行器,后面的博客會對其進(jìn)行介紹。
selectKey執(zhí)行完畢后,會判斷查詢結(jié)果是否合法。主鍵的查詢結(jié)果肯定只會返回一條,因此查詢結(jié)果條數(shù)不為1的全部視為非法查詢。該方法代碼如下。
private void processGeneratedKeys(Executor executor, MappedStatement ms, Object parameter) {try {if (parameter != null && keyStatement != null && keyStatement.getKeyProperties() != null) {// 獲取selectKey節(jié)點的keyProperties配置的屬性名稱,表示主鍵對應(yīng)的屬性String[] keyProperties = keyStatement.getKeyProperties();final Configuration configuration = ms.getConfiguration();// 創(chuàng)建用戶傳入的實參對應(yīng)的MetaObject對象final MetaObject metaParam = configuration.newMetaObject(parameter);// 創(chuàng)建Executor對象,執(zhí)行keyStatement記錄的sql。Executor是mybatis中的執(zhí)行器,后面會講Executor keyExecutor = configuration.newExecutor(executor.getTransaction(), ExecutorType.SIMPLE);// 執(zhí)行selectKey節(jié)點的sql,查詢主鍵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 {// 主鍵查詢出來肯定只有一條,因此values的size不等于1的情況都是錯誤的// 創(chuàng)建主鍵對象對應(yīng)的MetaObjectMetaObject metaResult = configuration.newMetaObject(values.get(0));if (keyProperties.length == 1) {if (metaResult.hasGetter(keyProperties[0])) {// 取出主鍵的值,設(shè)置到用戶參數(shù)對應(yīng)的屬性中setValue(metaParam, keyProperties[0], metaResult.getValue(keyProperties[0]));} else {// 如果主鍵對象不包含get方法,就可能是基本類型或者String,直接復(fù)制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);} }該方法中有一個handleMultipleProperties方法,用于處理一張表多個主鍵的情況。由于復(fù)合主鍵耦合性高、影響性能,并且操作起來較為繁瑣,因此不推薦數(shù)據(jù)庫中給一張表設(shè)置多個主鍵,這里也就不對該方法進(jìn)行介紹。
結(jié)語
KeyGenerator的代碼較為簡單,閱讀起來也不會吃力。本周我公司已經(jīng)開始了上班,因此博客也需要開始寫了,mybatis的源碼已經(jīng)閱讀了一大半,爭取在7月份之前把剩下代碼的博客全部編寫完畢
*************************************優(yōu)雅的分割線 **********************************
分享一波:程序員賺外快-必看的巔峰干貨
如果以上內(nèi)容對你覺得有用,并想獲取更多的賺錢方式和免費(fèi)的技術(shù)教程
請關(guān)注微信公眾號:HB荷包
一個能讓你學(xué)習(xí)技術(shù)和賺錢方法的公眾號,持續(xù)更新
*************************************優(yōu)雅的分割線 **********************************
總結(jié)
以上是生活随笔為你收集整理的Mybatis源码阅读(三):结果集映射3.3 —— 主键生成策略的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Sonatype Nexus 库被删除的
- 下一篇: 工时单位天与人天的区别?