MyBatis中Mapper接口是怎么和XML文件关联起来的 您所在的位置:网站首页 商务部批准的直销公司有哪些项目 MyBatis中Mapper接口是怎么和XML文件关联起来的

MyBatis中Mapper接口是怎么和XML文件关联起来的

2023-09-02 20:48| 来源: 网络整理| 查看: 265

目录 1.前言2. `XML` 文件和 `Mapper` 接口的解析2.1.`XML` 文件的解析2.2. `Mapper` 接口的解析 3. `XML` 文件信息和 `Mapper` 接口的使用3.1. `Mapper` 接口的使用3.2. `XML` 文件信息的使用 4. 总结

1.前言

使用过 MyBatis 的,都知道它有 Mapper 接口和 Mapper.xml 文件,那么它们是如何关联起来的呢?,又是如何一起运作的呢?接下来,我们从源码级别来分析回答这个问题

2. XML 文件和 Mapper 接口的解析 2.1.XML 文件的解析

XMl 文件的解析过程太过复杂,在这里不再过多赘述,详细过程 在这里,我们先从 这里的 2.5 节点谈起,代码如下

public void parseStatementNode() { // 省略所有的属性解析...... // 将解析出来的所有参数添加到 mappedStatements 缓存 builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); } // MapperBuilderAssistant.java 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, boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId, LanguageDriver lang, String resultSets) { if (unresolvedCacheRef) { throw new IncompleteElementException("Cache-ref not yet resolved"); } // 1.将 id 填充上 namespace,id = namespace + id(方法名) id = applyCurrentNamespace(id, false); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; // 2.使用参数构建 MappedStatement.Builder MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType) .resource(resource) .fetchSize(fetchSize) .timeout(timeout) .statementType(statementType) .keyGenerator(keyGenerator) .keyProperty(keyProperty) .keyColumn(keyColumn) .databaseId(databaseId) .lang(lang) .resultOrdered(resultOrdered) .resultSets(resultSets) .resultMaps(getStatementResultMaps(resultMap, resultType, id)) .resultSetType(resultSetType) .flushCacheRequired(valueOrDefault(flushCache, !isSelect)) .useCache(valueOrDefault(useCache, isSelect)) .cache(currentCache); ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id); if (statementParameterMap != null) { statementBuilder.parameterMap(statementParameterMap); } // 3.使用 MappedStatement.Builder 构建 MappedStatement MappedStatement statement = statementBuilder.build(); // 4.将 MappedStatement 添加到缓存 configuration.addMappedStatement(statement); return statement; } statement:表示解析出来的 Mapper.xml 文件的所有参数信息上述方法做了两件事,构建 statement,然后将其添加进 mappedStatements 缓存中mappedStatements 数据结构是一个 StrictMap,此处就是 Mapper 接口方法为什么不能重载的答案,参见文章 protected final Map mappedStatements = new StrictMap("Mapped Statements collection"); 2.2. Mapper 接口的解析

我们先从 这里的 2.6 节点谈起,代码如下

private void bindMapperForNamespace() { // 拿到当前的 namespace, String namespace = builderAssistant.getCurrentNamespace(); if (namespace != null) { Class boundType = null; try { // 1.解析 namespace 对应的绑定类型 boundType = Resources.classForName(namespace); } catch (ClassNotFoundException e) {} if (boundType != null && !configuration.hasMapper(boundType)) { // 2.boundType 不为空,并且 configuration 还没有添加 boundType, // 则将 namespace 添加到已加载列表,将 boundType 添加到 knownMappers 缓存 configuration.addLoadedResource("namespace:" + namespace); configuration.addMapper(boundType);// addMapper() 方法调用如下 } } } public void addMapper(Class type) { mapperRegistry.addMapper(type);// addMapper() 方法调用如下 } public void addMapper(Class type) { if (type.isInterface()) { if (hasMapper(type)) { throw new BindingException("Type " + type + " is already known to the MapperRegistry."); } boolean loadCompleted = false; try { // 将 namespace 对应的绑定类型放到 knownMappers 缓存中 knownMappers.put(type, new MapperProxyFactory(type)); MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } } 先看看 knownMappers 是个什么玩意,原来是一个 HashMap private final Map knownMappers = new HashMap(); 将解析过的 mapper.xml 文件的 namespace 的绑定类型 type 放到 knownMappers 缓存中这里的 type 可以理解为 Mapper 接口的类型,即 Mapper 接口 3. XML 文件信息和 Mapper 接口的使用

通过上面的分析,此时 MyBatis 已经完成了对 Mapper 接口和 XML 文件的解析工作,并且将它们分别添加进了各自的 Map 缓存中,那么它们是在哪里又取出来使用的呢?

3.1. Mapper 接口的使用

在 MapperRegistry 类中的 getMapper() 方法

@SuppressWarnings("unchecked") public T getMapper(Class type, SqlSession sqlSession) { // 获取已经解析完成的 Mapper 接口信息 final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } try { // 实例化对象 return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } } knownMappers.get():获取 namespace 的绑定类型,即 Mapper 接口类型信息在上述的 2.2. 节点中的 addMapper() 方法中有 knownMappers.put()在此时拿到 Mapper 接口类型很关键,因为马上就要创建 Mapper 接口的代理对象了,代理对象创建过程 在这里的 2.1.4.节点 3.2. XML 文件信息的使用

DefaultSqlSession 类中增删改查如下,具体代码的跟进过程 看这里 2.2.节点

@Override public int insert(String statement, Object parameter) { // 调用下面的 update() 方法 return update(statement, parameter); } @Override public int update(String statement, Object parameter) { try { dirty = true; // 从 mappedStatements 缓存拿到对应的 MappedStatement 对象,执行更新操作 MappedStatement ms = configuration.getMappedStatement(statement); return executor.update(ms, wrapCollection(parameter)); } catch (Exception e) { throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } } @Override public int delete(String statement, Object parameter) { // 调用上面的 update() 方法 return update(statement, parameter); } // select,以 executeForMany 为例 private Object executeForMany(SqlSession sqlSession, Object[] args) { List result; // 1.参数转换成sql命令参数 Object param = method.convertArgsToSqlCommandParam(args); if (method.hasRowBounds()) { RowBounds rowBounds = method.extractRowBounds(args); // selectList() 方法在最下面 result = sqlSession.selectList(command.getName(), param, rowBounds); } else { // 2.执行查询操作,selectList() 方法在最下面 result = sqlSession.selectList(command.getName(), param); } // 3.处理返回结果 if (!method.getReturnType().isAssignableFrom(result.getClass())) { if (method.getReturnType().isArray()) { return convertToArray(result); } else { return convertToDeclaredCollection(sqlSession.getConfiguration(), result); } } return result; } @Override public List selectList(String statement, Object parameter) { // selectList() 方法在最下面 return this.selectList(statement, parameter, RowBounds.DEFAULT); } @Override public List selectList(String statement, Object parameter, RowBounds rowBounds) { try { // 从 mappedStatements 缓存中拿到对应的 MappedStatement 对象,执行查询操作 MappedStatement ms = configuration.getMappedStatement(statement); return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }

关键部分代码,在即将要执行 SQL 时,在从 StrictMap 的缓存中获取

// 参数 statement 是 String 类型的 MappedStatement ms = configuration.getMappedStatement(statement); // Configuration 类中的 getMappedStatement() 方法如下 public MappedStatement getMappedStatement(String id) { // 调用下面的方法 return this.getMappedStatement(id, true); } public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) { if (validateIncompleteStatements) { buildAllStatements(); } // return mappedStatements.get(id); } // mappedStatements 的数据类型 protected final Map mappedStatements = new StrictMap("Mapped Statements collection"); 4. 总结

Mapper 接口是怎么和 XML 文件关联起来,最关键的部分

// 拿到当前的 namespace, // MyBatis 会解析为 "org.example.dao.UserMapper" String namespace = builderAssistant.getCurrentNamespace(); // 解析为 namespace 对应的绑定类型 boundType = Resources.classForName(namespace);

在这里插入图片描述



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有