关于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个月后…

猜你喜欢

转载自blog.csdn.net/qq_35576994/article/details/83038744