赞
踩
本文用的是3.5.10版本
源码地址:https://github.com/mybatis/mybatis-3/releases
文档地址:https://mybatis.org/mybatis-3/zh/sqlmap-xml.html
环境的搭建本文不做阐述了,文档里面有,本文适用已经对Mybatis使用有一定了解的人阅读!
一. Mybatis源码详解
二. Mybatis二级缓存详解
三. Mybatis三大执行器介绍
四. Mybatis拦截器源码详解
我们先看看Mybatis调用Mysql和JDBC调用Mysql有什么区别?这样就知道Mybatis帮我们做了哪些事了
Connection connection =null; try { // 加载驱动 Class.forName("com.mysql.cj.jdbc.Driver"); //1.获取连接 数据源 connection = DriverManager.getConnection(url, user, password); //2.获取操作对象 PreparedStatement preparedStatement = connection.prepareStatement("select * from test_user where id=?"); //3.传参处理 preparedStatement.setInt(1,1); //4.执行 ResultSet resultSet = preparedStatement.executeQuery(); //5.返回值处理 while (resultSet.next()){ TestVO testVO = new TestVO( resultSet.getInt("id"), resultSet.getString("name"), resultSet.getString("team"), resultSet.getInt("grade") ); System.out.println(JSONObject.toJSON(testVO)); } } catch (Exception e) { e.printStackTrace(); }finally { if(connection!=null){ connection.close(); } }
加载驱动就不说了,总的分几步:
// 获取数据源 获取SQL语句 获取Mapper 传参处理 执行 返回值处理 try { String resource = "resources/mybatis-config.xml"; // 通过classLoader获取到配置文件 InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); // 获取会话工厂 SqlSessionFactory sqlSessionFactory = builder.build(inputStream); // 获取一个会话 SqlSession sqlSession = sqlSessionFactory.openSession(); // 动态代理方式 TestMapper mapper = sqlSession.getMapper(TestMapper.class); List<TestVO> dataList = mapper.findDataList(1,"张三"); System.out.println("通过动态代理返回结果" + JSONObject.toJSON(dataList)); sqlSession.close(); } catch (Exception e) { e.printStackTrace(); }
首先要达成一个共识,Mybatis最终调用Mysql的方式和JDBC是不是一样的?
是一样的,所以Mybatis只是对JDBC做了一层封装,帮我们简化了很多操作,比如:
最主要就这两个吧,SQL还是需要我们自己写,连接、预编译、执行器这些JDBC都有吧!
所以为什么Mybatis被称为半ORM框架这些懂了吧?就是SQL还需要自己写,传参和返回值处理它帮你搞定了
所以我们总结一下Mybatis做了什么?怎么帮我们做的封装,接下来分步骤一步一步看:
资源的获取就是XML文件的解析,我们再回顾一下Mybatis的代码,是不是加载了一份资源文件
数据源就是XML配置文件里面environments标签部分,我们需要解析并加载,如下图所示:
找到我们需要解析的environments标签,就是数据源的配置解析
还通过了遍历获取,说明数据源可以有多个,然后数据源被放到了Configuration里面,到这数据源就解析完成了
(这个Configuration记住哈,资源的解析完了后都是放到这里面的,很重要)
XMLConfigBuilder.parse
和上面一样同样在解析XML配置文件里面
我们看看配置文件里面mappers标签是怎么配置的,官方给出了四种配置方式:
既然配置有4种配置方法,那对应的解析的时候也有4种解析方法:
XMLConfigBuilder.mapperElement
所以我们要知道SQL语句有两个地方可以获取(应该都知道霍,SQL本身就可以写两个地方)
所以接下来我们要分两种方式来解析!
从入口处看,XML方式都会调用
有很多标签就不一一看了,直接看增删改查的标签
遍历XML节点一个一个获取
这个方法有点长我们分三个截图,分别代表三个关键的点,一个一个来
第一段: 这个是干嘛的,我们后面说(判断重复加载)
第二段:这就是获取SQL资源的地方,我们一样后面说,因为通过注解的方式解析的时候也会调用这个方法,后面一起说,先记着是LanguageDriver.createSqlSource方法
第三段:这个可以是最后的一步了,我们的数据源被加载放到了Configuration里面,所以SQL相关的也不例外要放入Configuration中
最终被封装成了MappedStatement放入了Configuration中
我们一样从入口开始看
两个入口一直往下都会到这个方法,中间的就不看了,没啥好说的
中间要做什么处理,搞什么鬼的一律不理,我们直奔主题
这个方法有点长我们同样分几段
第一段:第一步获取SQL资源,这个buildSource一样会到我们上面说的LanguageDriver.createSqlSource方法,后面一起说
第二段:是不是有点熟悉又是MapperBuilderAssistant.addMappedStatement方法,和上面一样的最后被封装成MappedStatement放入了Configuration中(和上面一样就不说了)
我们解析XML的时候或者解析注解的时候难道拿不到SQL吗?还需要单独去解析获取SQL?
这里的SQL资源获取并不代表是获取XML或者注解里面的SQL,恰恰是对里面的SQL处理一次,我们最终需要的SQL是像JDBC里面那样参数是 "?"的SQL格式,所以这里的处理是将XML或者注解里面SQL中的占位符处理掉,并建立占位符下标索引和传参参数的映射
如下图所示:
处理后SQL已经变成了最后执行所需的样子,同时产生了占位符的下标索引和传参的映射关系的ParameterMapping对象
所以需要处理两种情况:
上面我们说到createSqlSource方法,实际是在XMLLanguageDriver中,存在两个重载方法
不管是注解形式的SQL还是XML形式的SQL最终都会到这,两者解析完最终都会判断SQL是静态SQL还是动态SQL,有不同的处理方法
TextSqlNode.isDynamic()
该方法中会调用GenericTokenParser类进行对SQL中"${}"占位符的处理,而处理方法正是DynamicCheckerTokenParser.handleToken方法,该方法中将标志位设置为了True,而这种动态的SQL会在最终执行的时候才去处理(用传参替换)
动态SQL一共有两种,一是${}占位符,二是XML中存在IF标签等判断语句(这种大家可以从XML入口去看)
RawSqlSource
静态的SQL会在该类中的构造方法中直接处理了
SqlSourceBuilder.parse()
相比于动态SQL,处理的Handler换了,看替换的本文变成了"?",同时还加上了下标的映射关系
GenericTokenParser这个是占位符的处理类,就等于是一个工具类,这里就不贴了,有兴趣的可以去看看,实际开发说不定也能用得上哦
结果会报错,因为注解的方式后解析,而在注解的方式解析过程中并没有判断MappeStatement是否已经存在,此时会继续往Configuration中添加MappeStatement,而Configuration里面用的Map是自定义的StrictMap,put相同的key会报错
无事发生,因为在xml解析过程中有判断MappeStatement是否已经存在
(这就是我上面说截图第一段后面说的,至此坑已经填完)
应该都知道Mapper的执行原理是动态代理,所以Mapper也需要加载变成代理对象,结合SQL的获取方式,Mapper加载也有两种
就在XML解析的下面
XMLMapperBuilder.bindMapperForNamespace()
会调用addMapper()这个方法
就在加载的时候,也会调用addMapper()方法,我们直接看这个方法
MapperRegistry.addMapper()
先判断是否存在,不存在就直接加入到了knownMappers这个Map对象中,加入的是一个代理工厂
MapperProxyFactory,然后就没了,等着执行时调用
应该都知道,前面也说过获取的Mapper是一个代理对象,最后的执行是代理对象的执行方法,所以我们直接点进去看看代理对象的执行方法是什么,先看看代理对象如何生成的
MapperRegistry.getMapper
我们随着getMapper方法一路进来会找到该方法,一眼过去非常的眼熟是不是,先从Mapper集合取出代理工厂,然后用代理工厂去生成代理对象,这里的SqlSession是什么?就是DefaultSqlSession,里面就包含之前装载资源的Configuration
采用的就是JDK的动态代理,所以这个MapperProxy很明显里面就有最终代理对象的处理方法了
MapperProxy.invoke
很容易就找到了方法执行处,忽略掉一系列判断,我们直接找到最终的处理方法
又采用了一个静态代理的方式调用了执行
MapperMethod.execute
到了这里是不是就顺眼多了,熟悉的增删改查,我们以查为例,查还分无返回值、查多条、查Map、查一条这些情况,我们就简单一点以差一条为例子走
DefaultSqlSession.selectOne
进来这里你看看这个类下的方法,虽然是以查询一条为例,但是增删改查操作最终都会到这,而且你看看查询一条的时候实际也是查询的是list,最后只是返回了一条结果而已,下面那个报错异常相信大家也很熟悉吧
DefaultSqlSession.selectList
顺着上面下来,这里会先获取一个MappedStatement,这个是什么东西?还记得上面说的SQL资源获取吗?这里面就是SQL相关的所有资源,比如SQL语句、返回值、传参映射等等,所以这里的获取指的就是去获取现在要执行的方法对应的SQL资源(MappedStatement),通过这个方法的全路径类名+方法名称获取,然后再调用执行器执行查询!
执行这块涉及到两个知识点,一是Mybatis的二级缓存,二是Mybatis的三大执行器
这里我们先跳过这两个点,直接来看默认的执行器(SimpleExecutor)执行过程
SimpleExecutor.doQuery
这里分三步
Configuration.newStatementHandler
这里主要就是选择采用哪个StatementHandler
RoutingStatementHandler:
StatementHandler的选择是根据参数来的,这个参数在XML里面是可以配置的,默认是PreparedStatementHandler
SimpleExecutor.prepareStatement
BaseStatementHandler.prepare
获取连接就不看了,没啥看的,直接看看预处理,主要关注这个初始化
PreparedStatementHandler.instantiateStatement
看这是不是跟JDBC里面第二步一模一样?现在就只差传参处理、执行、返回值处理了是不是?
PreparedStatementHandler.parameterize
DefaultParameterHandler.setParameters
这个就是处理传参的,根据之前处理后的映射关系,找到对应类型的处理类处理参数,然后与JDBC类型映射,这整个过程都是自动完成的,所以这里的参数处理还需要所有类型的对应处理方式是不是,一旦处理方式没匹配上就会有问题对吧
TypeHandler
所以Mybatis里面列举了很多类型的处理方式,而且看方法,不仅处理参数,还处理返回值
PreparedStatementHandler.query
DefaultResultSetHandler.handleResultSets
获取了JAVA中与JDBC参数类型的映射关系、获取XML中的配置、然后遍历处理结果集
ResultSetWrapper
JAVA中与JDBC参数类型的映射关系如下
以查询一条、SimpleExecutor执行器为例:
这里网上找了图,结构上更明了:
从MyBatis代码实现的角度来看,MyBatis的主要的核心部件有以下几个:
这三个可能有些人平时用的少,但都算是Mybatis提供的一些功能,后续单独拿出来说
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。