1. 程式人生 > >關於Mybatis中,selectOne/selectList中statement對mapper檔案中的id匹配方式的研究

關於Mybatis中,selectOne/selectList中statement對mapper檔案中的id匹配方式的研究

前言:
在mybatis中,對映檔案中的namespace是用於繫結Dao介面的,即面向介面程式設計。
當你的namespace繫結介面後,你可以不用寫介面實現類,mybatis會通過該繫結自動幫你找到對應要執行的SQL語句。
但是,在實際程式設計過程中,也可以使用實體類的class名稱作為namespace進行匹配。
正文:
話不多,先上程式碼:

SqlSession session = this.sqlSessionFactory.openSession();
        Map paramMap = new HashMap(16);
        try {
            ZoneId zoneId = ZoneId.systemDefault();
            paramMap.put("startTime", Date.from(startTime.atZone(zoneId).toInstant()));
            paramMap.put("endTime", Date.from(endTime.atZone(zoneId).toInstant()));
            Map aMap= session.selectOne("selectAMap", paramMap);
            return aMap;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            if (null != session) {
                session.close();
            }
        }

編寫完這段程式碼後,就想起之前想過研究下這裡是selectOne裡面的statement引數中指定的這個id是如何跟對應Mapper.xml中的id匹配起來的,這裡猜測可能是根據namespace進行匹配,遂跟進程式碼。

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        List var6;
        try {
            MappedStatement ms = this.configuration.getMappedStatement(statement);
            List<E> result = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
            var6 = result;
        } catch (Exception var10) {
            throw ExceptionFactory.wrapException("Error querying database.  Cause: " + var10, var10);
        } finally {
            ErrorContext.instance().reset();
        }

        return var6;
    }

這段程式碼中,發現MappedStatement類,emmm…應該在在這裡做的處理,點進去看。

public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) {
        if (validateIncompleteStatements) {
            this.buildAllStatements();
        }

        return (MappedStatement)this.mappedStatements.get(id);
    }       

buildAllStatements()此方法進行語句構建,再返回語句。重點來了,這裡的mappedStatements例項是一個Map物件(Configuration.StrictMap),泛型為<String, MappedStatement

protected final Map<String, MappedStatement> mappedStatements;

嗯,應該是根據傳入的id到此Map中去獲取對應的MappedStatement物件,遂檢視此Map物件的初始化方法。搜了一下,應該是這裡。

public void addMappedStatement(MappedStatement ms) {
    this.mappedStatements.put(ms.getId(), ms);
}

打上斷點,重啟專案,bingo!捕獲到了,F8走起,發現這裡有一個迴圈,具體迭代裡面的東西怎麼來的就沒看了,但是可以確定是掃描了專案中所有的Mapper檔案得來的所有Mapper.xml中的id,並且這裡是在前面加上了namespace的。

public MappedStatement addMappedStatement(String id, SqlSource sqlSource, StatementType statementType, SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class<?> parameterType, String resultMap, Class<?> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId) {
        id = this.applyCurrentNamespace(id, false);
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
        org.apache.ibatis.mapping.MappedStatement.Builder statementBuilder = new org.apache.ibatis.mapping.MappedStatement.Builder(this.configuration, id, sqlSource, sqlCommandType);
        statementBuilder.resource(this.resource);
        statementBuilder.fetchSize(fetchSize);
        statementBuilder.statementType(statementType);
        statementBuilder.keyGenerator(keyGenerator);
        statementBuilder.keyProperty(keyProperty);
        statementBuilder.keyColumn(keyColumn);
        statementBuilder.databaseId(databaseId);
        this.setStatementTimeout(timeout, statementBuilder);
        this.setStatementParameterMap(parameterMap, parameterType, statementBuilder);
        this.setStatementResultMap(resultMap, resultType, resultSetType, statementBuilder);
        this.setStatementCache(isSelect, flushCache, useCache, this.currentCache, statementBuilder);
        MappedStatement statement = statementBuilder.build();
        this.configuration.addMappedStatement(statement);
        return statement;
    }

接下來就是迭代迴圈時,發現一個有趣的現象。

public void addMappedStatement(MappedStatement ms) {
this.mappedStatements.put(ms.getId(), ms);
}

這裡put方法,每次會放兩個值到這個Map中,感到疑惑,點進去發現,如果id帶namespace也就是帶**.**,裡面會將此id處理為兩個字串,一個是原本帶namespace的,一個是經過擷取只剩下最後的id的字串,如com.abc.scInstance,這裡產生兩個字串->{
com.abc.scInstance,
scInstance
}
得出結論,return (MappedStatement)this.mappedStatements.get(id);
這裡的id不帶namespace也是可以在mappedStatements中獲取到對應的MappedStatement物件,進行匹配到對應的帶有namespace的id的語句。
而為什麼是要兩個字串,裡面存相同資料,那應該是就是當不同Mapper檔案中存在相同id時用於區分了,畢竟加上實體類的namespace得到的必定是唯一的語句。具體原因後續再研究下寫上來。

總結:
作為新手的第一次原創原始碼解析,感覺自己的完全沒有講清楚的,如果有大牛路過,希望給出指導,在下感激不盡。 --寫於踏上工作崗位的3個月後…