Mybatis的Mapper代理对象生成及调用过程示例详解
作者:福
导读
你在mapper.xml文件中写的sql语句最终是怎么被执行的?我们编写的mapper接口最终是怎么生成代理对象并被调用执行的?
这部分内容应该是Mybatis框架中最关键、也是最复杂的部分,今天文章的主要目标是要搞清楚:
- mapper.xml文件是怎么初始化到Mybatis框架中的?
- mapper接口生成动态代理对象的过程。
- mapper接口动态代理对象的执行过程。
掌握了以上3个问题,我们就掌握了Mybatis的核心。
Mapper初始化过程
指的是mapper.xml文件的解析过程。
这个动作是在SqlSessionFactory创建的过程中同步完成的,或者说是在SqlSessionFactory被build出来之前完成。
XMLMapperBuilder负责对mapper.xml文件做解析,SqlSessionFactorBean的buildSqlSessionFactory()方法中会针对不同配置情况进行解析。其中我们最常用的是在配置文件中指定mapper.xml文件的路径(就是源码中的这个mapperLocations):
if (this.mapperLocations != null) { if (this.mapperLocations.length == 0) { LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found."); } else { for (Resource mapperLocation : this.mapperLocations) { if (mapperLocation == null) { continue; } try { XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments()); xmlMapperBuilder.parse(); } catch (Exception e) { throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e); } finally { ErrorContext.instance().reset(); } LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'"); } } } else { LOGGER.debug(() -> "Property 'mapperLocations' was not specified."); } return this.sqlSessionFactoryBuilder.build(targetConfiguration);
创建XMLMapperBuilder对象并调用parse()方法完成解析。
XMLMapperBuilder#configurationElement()
parse方法会调用configurationElement()方法,对mapper.xml的解析的关键部分就在configurationElement方法中。
我们今天把问题聚焦在mapper.xml文件中sql语句的解析,也就是其中的insert、update、delete、select等标签的解析。
private void configurationElement(XNode context) { try { //获取namespace String namespace = context.getStringAttribute("namespace"); if (namespace == null || namespace.isEmpty()) { throw new BuilderException("Mapper's namespace cannot be empty"); } builderAssistant.setCurrentNamespace(namespace); //二级缓存Ref解析 cacheRefElement(context.evalNode("cache-ref")); //二级缓存配置的解析 cacheElement(context.evalNode("cache")); //parameterMap标签的解析 parameterMapElement(context.evalNodes("/mapper/parameterMap")); //resultMap标签的解析 resultMapElements(context.evalNodes("/mapper/resultMap")); //sql标签的解析 sqlElement(context.evalNodes("/mapper/sql")); //关键部分:sql语句的解析 buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e); } }
对sql语句解析部分继续跟踪会发现,最终sql语句解析完成之后会创建MappedStatement并保存在configuration对象中(以xml文件中的id为key值的Map中):
MappedStatement statement = statementBuilder.build(); configuration.addMappedStatement(statement); return statement;
这样我们就明白了,mapper.xml中编写的sql语句被解析后,最终保存在configuration对象的mappedStatements中了,mappedStatements其实是一个以mapper文件中相关标签的id值为key值的hashMap。
Mapper接口生成动态代理过程
我们都知道Mapper对象是通过SqlSession的getMapper方法获取到的,其实Mapper接口的代理对象也就是在这个调用过程中生成的:
@Override public <T> T getMapper(Class<T> type) { return getConfiguration().getMapper(type, this); }
调用Configuration的getMapper方法:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); }
调用mapperRegistry的getMapper方法,首先从knownMappers(以namespace为key值保存mapperProxyFactory的HashMap)中获取到mapperProxyFactory,mapperProxyFactory人如其名,就是mapper代理对象工厂,负责创建mapper代理对象。
获取到mapperProxyFactory之后,调用newInstance方法:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) 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); } }
调用MapperProxy的newInstance方法:
protected T newInstance(MapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } public T newInstance(SqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); }
典型的JDK动态代理生成逻辑,传入的回调参数为mapperProxy对象,也就是说我们对Mapper接口的方法调用,最终通过生成的动态代理对象,会调用到这个回调对象mapperProxy的invoke方法。
至此,mapper接口动态代理的生成逻辑我们就从源码的角度分析完毕,现在我们已经搞清楚mapper动态代理的生成过程,最重要的是,我们知道mapper接口的方法调用最终会转换为对mapperProxy的invoke方法的调用。
mapper接口动态代理对象的执行过程
这个问题现在已经明确了,其实就是MapperProxy的invoke方法。
对MapperProxy稍加研究,我们发现他的invoke方法最终会调用MapperMethod的execute方法:
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable { return mapperMethod.execute(sqlSession, args); }
execute方法根据调用方法的sql类型分别调用sqlSession的insert、update、delete、select方法,相应的方法会根据调用方法名从configuration中匹配MappedStatement从而执行我们在mapper.xml中配置的sql语句(参考XMLMapperBuilder#configurationElement()部分)。
因此我们也就明白了为什么mapper.xml文件中配置的sql语句的id必须要对应mapper接口中的方法名,因为Mybatis要通过mapper接口中的方法名去匹配sql语句、从而最终执行该sql语句!
任务完成!
其实虽然我们知道SqlSession默认的落地实现对象是DefaultSqlSession,我们在mapper.xml中编写的sql语句其实是DefaultSqlSession负责执行的,但是MapperMethod中sqlSession其实也是代理对象(DefaultSqlSession的代理对象),所以说Mybatis中到处都是动态代理,这部分我们下次再分析。
以上就是Mybatis的Mapper代理对象生成及调用过程示例详解的详细内容,更多关于Mybatis Mapper代理对象生成调用的资料请关注脚本之家其它相关文章!