1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > mybatis源码:mybatis的sql解析

mybatis源码:mybatis的sql解析

时间:2024-08-20 06:18:59

相关推荐

mybatis源码:mybatis的sql解析

目的

本文主要学习记录原生mybatis源码中sql解析的学习;

mybatis的核心原理,我的理解是这样的:

将xml中或者注解中配置的sql,以方法的维度,存入到mappedStatements这个map集合中,key是全类名+方法名,value是根据sql生成的MappedStatement类将dao接口的动态代理工厂,存入knownMappers这个map中,key是接口的全类名,value是根据接口生成的动态代理工厂在通过mybatis调用sql的时候,根据接口类型,从这两个map中获取对应的value

应用

public static void main(String[] args) throws IOException {String resource = "mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);SqlSession sqlSession = sqlSessionFactory.openSession();//查询sql的第一种方式// List<OperChannel> list = sqlSession.selectList("com.springsource.study.mybatis.OperChannelMapper.selectAll");// OperChannel operChannel = sqlSession.selectOne("com.springsource.study.mybatis.OperChannelMapper.selectAll",1);System.out.println("+++++++++++++++++");//查询sql的第二种方式:动态代理OperChannelMapper operChannelMapper = sqlSession.getMapper(OperChannelMapper.class);System.out.println(operChannelMapper.selectAll(1));mit();System.out.println(operChannelMapper.selectAll(1));}

这是自己测试demo的main方法

源码

首先,我们要从SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);这行代码开始看起

在这个gif中,展示的是上面build方法里面的调用逻辑,我们看org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration这个方法的代码

private void parseConfiguration(XNode root) {try {//issue #117 read properties firstpropertiesElement(root.evalNode("properties"));Properties settings = settingsAsProperties(root.evalNode("settings"));loadCustomVfs(settings);loadCustomLogImpl(settings);/*** 解析别名,将名别存到了一个map中TYPE_ALIASES,key值是别名,value是class;比如:map.put(boolean,Boolean.class);* 这个map中存储的是程序员自己定义的别名和公用的(比如:数据源信息)*/typeAliasesElement(root.evalNode("typeAliases"));//解析插件pluginElement(root.evalNode("plugins"));//自定义实例化对象的行为objectFactoryElement(root.evalNode("objectFactory"));//下面两个主要是配合 MateObject来使用的;方便反射操作实体类objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));reflectorFactoryElement(root.evalNode("reflectorFactory"));settingsElement(settings);// read it after objectFactory and objectWrapperFactory issue #631//在这里解析environment信息,获取到数据源;根据配置文件中DataSource节点设置的type类型,从别名的map中获取到对应的dataSourceFactoryenvironmentsElement(root.evalNode("environments"));databaseIdProviderElement(root.evalNode("databaseIdProvider"));typeHandlerElement(root.evalNode("typeHandlers"));/*** 在这个方法中,完成了两个重要的事情:* 1.解析mapper配置信息,将SQL封装成mapperstatement,并将该对象存到mappedStatement这个map中* 2.把根据接口生成的mapperProxyFactory存到knownMappers这个map中*/mapperElement(root.evalNode("mappers"));} catch (Exception e) {throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);}}

该方法中,入参中的root节点,就是mybatis配置文件的根节点<configuration></configuration>的; 这个方法就是解析mybatis配置文件中的配置信息;由于本文我们只说mybatis对sql的解析源码,所以,我们关注的是 mapperElement(root.evalNode(“mappers”));这个方法:

该方法中是对mapper.xml文件配置的优先级判断:官方提供了四种xml的配置方式,分别是截图中的package、resource、url、class四种方式,这四种方式优先级从前到后,是因为源码中是按照这个优先级来解析的;我的代码中用的是resource,所以,我们以resource的这种配置为例:

在resource的判断分支中,会调用org.apache.ibatis.builder.xml.XMLMapperBuilder#parse方法,这里需要说明的是:mybatis在解析配置文件的时候,会把所有的配置信息,转换成对应的entity实体类,最后将所有的entity实体类聚合到configuration这个类中;

public void parse() {if (!configuration.isResourceLoaded(resource)) {//序号1:解析mapper,并把sql语句包装成mappedStatement put到map中configurationElement(parser.evalNode("/mapper"));//configuration.addLoadedResource(resource);//序号2:将namespace和mapperProxyFactory对象存放到一个knownMappers中,在getMapper的时候 有用到bindMapperForNamespace();}parsePendingResultMaps();parsePendingCacheRefs();//序号3:重新解析在前面解析过程中报错的xml;incompleteStatementsparsePendingStatements();}

在这个方法中,完成的就是注释中的三个操作

首先,序号1:这里面完成的就是将sql解析成mappedStatement添加到mappedStatements里面

序号2:这里是将接口的动态代理工厂存入knownMappers

序号3:是对xml解析中报错的sql进行重新解析

sql解析为mappedStatement对象

对于configurationElement(parser.evalNode("/mapper"));的调用逻辑,我不在一一拷贝源码了,直接放张截图;

首先会解析到mapper.xml文件中,<mapper></mapper>节点中select、insert、update、delete对应的配置信息,然后在方法3这里,会对获取到的配置信息进行解析组装;

在方法3这里,我们需要注意一点:这里在catch捕获到异常之后,会把当前的XMLStatementBuilder对象存到一个linkedList集合中,以便上面序号3这里进行重新解析

org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNodepublic void parseStatementNode() {String id = context.getStringAttribute("id");String databaseId = context.getStringAttribute("databaseId");if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {return;}Integer fetchSize = context.getIntAttribute("fetchSize");Integer timeout = context.getIntAttribute("timeout");SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));boolean isSelect = sqlCommandType == SqlCommandType.SELECT;//是否刷新缓存,如果是select,默认是false,如果是非select,默认是true,boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);//是否使用二级缓存,默认值:查询使用,非查询(insert、update、delete)不使用boolean useCache = context.getBooleanAttribute("useCache", isSelect);//是否需要处理嵌套查询结果 group byboolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);// Include Fragments before parsingXMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);//替换incluses标签为对应的sql标签中的值includeParser.applyIncludes(context.getNode());// Parse selectKey after includes and remove them.//解析selectkeyprocessSelectKeyNodes(id, parameterTypeClass, langDriver);// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)//解析sql,根据sql文本来判断是否需要动态解析,如果没有动态sql语句且只有 #{}的时候,直接使用静态解析(使用?占位符);如果有${},就不解析,在后面使用的时候再解析SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);//这里是把所有参数解析出来之后,build到一个mapperStatement中builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,resultSetTypeEnum, flushCache, useCache, resultOrdered,keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);}

这个方法,我只拷贝了部分代码,因为这里的解析实在太长了,我们可以看到,这里是从context中根据key获取对应的配置value,context是在调用XMLStatementBuilder()的带参构造函数的时候,赋值的,可以转到上面截图中方法3这里,

final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);

这里的context就是XNode对象;

在解析到所有的配置信息之后,最后调用了addMappedStatement()这个方法,来进行参数的复制,然后添加到了mappedStatements这个map中,value就是生成的mappedStatement对象,key就是全类名+方法名;

获取接口的代理工厂

接下来,会执行上面说到的 “序号2” 这里的方法;

这里的代码比较简单,就两个方法

private void bindMapperForNamespace() {String namespace = builderAssistant.getCurrentNamespace();if (namespace != null) {Class<?> boundType = null;try {// 根据mapper.xml中的namespace,通过class.forName获取到对应的class对象,这也是mapper.xml的namespace必须要和接口的全类名一致的原因boundType = Resources.classForName(namespace);} catch (ClassNotFoundException e) {//ignore, bound type is not required}if (boundType != null) {// 如果当前knownMappers已经包含了当前接口,就无需再次添加if (!configuration.hasMapper(boundType)) {// Spring may not know the real resource name so we set a flag// to prevent loading again this resource from the mapper interface// look at MapperAnnotationBuilder#loadXmlResourceconfiguration.addLoadedResource("namespace:" + namespace);// 将接口和接口对应的mapperProxyFactory放入到knownMappers中configuration.addMapper(boundType);}}}}

在这个方法中,完成了mapperProxyFactory的生成和保存;

调用

总之,不管是原生的mybatis、还是springboot和mybatis的整合,都要完成这两步。这两步的完成,是为了后面的调用:

我们先说第一节中 查询sql的第二种方式:动态代理这种方式

这里我们可以看到,最后是通过存入到knownMappers中的mapperProxyFactory,生成接口的代理对象;然后在调用方法的时候,会进入到MapperProxy的invoke()方法;

然后我们再来看mappedStatements这个map的使用

这里可以看到,对应使用selectList这种方式的,是通过全类名+方法名,从mappedStatement中获取对应的对象,完成之后的sql调用

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。