当前位置: 首页 > 后端技术 > Java

MybatisMapper代理对象生成和调用流程

时间:2023-04-01 13:26:05 Java

你写在mapper.xml文件中的SQL语句最后是怎么执行的?我们写的mapper接口最终是如何生成代理对象并被调用执行的呢?这部分内容应该是Mybatis框架中最为关键和复杂的部分。今天这篇文章的主要目的是搞清楚:mybatis框架中的mapper.xml文件是如何初始化的?mapper接口生成动态代理对象的过程。mapper接口动态代理对象的执行过程。掌握了以上三个问题,我们就掌握了Mybatis的核心。Mapper初始化过程参考mapper.xml文件的解析过程。此操作在创建SqlSessionFactory期间或在构建SqlSessionFactory之前同步完成。XMLMapperBuilder负责解析mapper.xml文件,SqlSessionFactorBean的buildSqlSessionFactory()方法会针对不同的配置情况进行解析。最常用的是在配置文件中指定mapper.xml文件的路径(即源码中的mapperLocations):if(this.mapperLocations!=null){if(this.mapperLocations.length==0){记录器。warn(()->“指定了属性‘mapperLocations’,但未找到匹配的资源。”);}else{for(ResourcemapperLocation:this.mapperLocations){if(mapperLocation==null){继续;}try{XMLMapperBuilderxmlMapperBuilder=newXMLMapperBuilder(mapperLocation.getInputStream(),targetConfiguration,mapperLocation.toString(),targetConfiguration.getSqlFragments());xmlMapperBuilder.parse();}catch(Exceptione){thrownewNestedIOException("解析映射资源失败:'"+mapperLocation+"'",e);}最后{ErrorContext.instance().reset();}LOGGER.debug(()->"已解析的映射器文件:'"+mapperLocation+"'");}}}else{LOGGER.debug(()->“未指定属性‘mapperLocations’。”);}返回this.sqlSessionFactoryBuilder.build(targetConfiguration);创建一个XMLMapperBuilder对象并调用parse()方法完成解析XMLMapperBuilder#configurationElement()parse方法会调用configurationElement()方法,而解析mapper.xml的关键部分在configurationElement方法中。今天重点分析mapper.xml文件中的SQL语句,即insert、update、delete、select等标签的分析。privatevoidconfigurationElement(XNodecontext){try{//获取命名空间Stringnamespace=context.getStringAttribute("namespace");if(namespace==null||namespace.isEmpty()){thrownewBuilderException("Mapper的命名空间不能为空");}builderAssistant.setCurrentNamespace(命名空间);//解析二级缓存RefcacheRefElement(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(Exceptione){thrownewBuilderException("解析MapperXML时出错。XMLl位置是“”+资源+“”。Cause:"+e,e);}}如果继续跟踪sql语句的解析部分,会发现在sql语句最后解析完成后,会创建一个MappedStatement保存在配置对象中(以xml文件的形式,在id为key值的Map中):MappedStatementstatement=statementBuilder.build();configuration.addMappedStatement(statement);returnstatement;这样我们就可以理解在sql语句写完之后mapper.xml被解析,最后保存在配置对象的mappedStatements中,mappedStatements实际上是以mapper文件中相关tag的id值作为key值,由hashMapper接口生成的动态代理过程。我们都知道说明Mapper对象是通过SqlSession的getMapper方法获取的,其实Mapper接口的代理对象也是在这个调用过程中生成的:@OverridepublicTgetMapper(Classtype){returngetConfiguration().getMapper(类型,这个);}调用Configuration的getMapper方法:publicTgetMapper(Classtype,SqlSessionsqlSession){returnmapperRegistry.getMapper(type,sqlSession);}调用mapperRegistry的getMapper方法,首先从knownMappers(以namespace为key值保存mapperProxyFactory的HashMap)中获取mapperProxyFactory,mapperProxyFactory顾名思义就是一个mapper代理对象工厂,负责创建mapper代理对象。获取到mapperProxyFactory之后,调用newInstance方法:publicTgetMapper(Classtype,SqlSessionsqlSession){finalMapperProxyFactorymapperProxyFactory=(MapperProxyFactory)knownMappers.get(type);if(mapperProxyFactory==null){thrownewBindingException("Type"+type+"不为MapperRegistry所知。");}try{returnmapperProxyFactory.newInstance(sqlSession);}catch(Exceptione){thrownewBindingException("获取映射器实例时出错。原因:"+e,e);}}调用MapperProxy的newInstance方法:protectedTnewInstance(MapperProxymapperProxy){return(T)Proxy.newProxyInstance(mapperInterface.getClassLoader(),newClass[]{mapperInterface},mapperProxy);}publicTnewInstance(SqlSessionsqlSession){finalMapperProxymapperProxy=newMapperProxy<>(sqlSession,mapperInterface,methodCache);返回新实例(映射器代理);}典型的JDK动态代理生成逻辑,传入的回调参数是mapperProxy对象,也就是说我们对Mapper接口的方法调用最终会通过生成的动态代理对象调用回调对象mapperProxy的invoke方法至此,我们从源码的角度分析了mapper接口动态代理的生成逻辑。现在我们已经弄清楚了mapper动态代理的生成过程。最重要的是我们知道mapper接口的方法调用最终会转化为mapperProxy方法调用的invoke。mapper接口的动态代理对象执行过程的问题到此就清楚了。其实就是MapperProxy的invoke方法。稍微研究一下MapperProxy,我们发现他的invoke方法最终会调用MapperMethod的execute方法:参数);}execute方法根据调用方法的sql类型调用sqlSession的insert、update、delete、select方法,相应的方法会根据调用方法名从配置中匹配MappedStatement执行我们的sql语句在mapper.xml中配置(参考XMLMapperBuilder#configurationElement()部分)。这样我们也就明白了为什么mapper.xml文件中配置的sql语句的id一定要对应mapper接口中的方法名,因为Mybatis需要通过mapper接口中的方法名来匹配sql语句,最后执行sql语句!任务完成!其实虽然我们知道SqlSession的默认实现对象是DefaultSqlSession,但是我们在mapper中写的sql语句。都是动态代理,我们下次再分析。上一篇MybatisL事务管理机制下一篇MybatisSqlSession初始化及调用流程