MyBatis官方文档
http://www.mybatis.org/mybatis-3/zh/
MyBatis技术内幕-徐郡明
https://book.douban.com/subject/27087564/
文章出处
https://segmentfault.com/a/1190000015117926
1. MyBatis简介
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录
2. MyBatis运行原理
流程
当框架启动时,通过configuration解析config.xml配置文件和mapper.xml映射文件,映射文件可以使用xml方式或者注解方式,然后由configuration获得sqlsessionfactory对象,再由sqlsessionfactory获得sqlsession数据库访问会话对象,通过会话对象获得对应DAO层的mapper对象,通过调用mapper对象相应方法,框架就会自动执行SQL语句从而获得结果。
3. xml解析与配置解析
mybatis启动(编程)
1 | String resource = "org/mybatis/example/mybatis-config.xml"; |
再来看下这个build操作在底层做了什么
1 | public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { |
很明显的看到mybatis通过XMLConfigBuilder初始化并且解析了我们的配置文件,最后得到一个Configuration类型的对象传给另外一个build操作,这个build操作最后直接new了一个DefaultSqlSessionFactory对象并且返回
4. 什么是Mapper对象?
由上面的叙述我们已经知道我们与mybatis交互主要是通过配置文件或者配置对象,但是我们最终的目的是要操作数据库的,所以mybatis为我们提供了sqlSession这个对象来进行所有的操作,也就是说我们真正通过mybatis操作数据库只要对接sqlSession这个对象就可以了。那么问题来了,我们怎么样通过sqlSession来了操作数据库的呢?
- 问题1:如何获取sqlSession?
1 | public SqlSession openSession() { |
由上面代码我们可知我们可以通过SqlSessionFactory的openSession去获取我们的sqlSession,也就是默认得到一个DefaultSqlSession对象。
- Mapper对象怎么来的?
平时我们使用如下代码获得一个Mapper对象。
1 | public <T> T getMapper(Class<T> type) { |
通过调用DefaultSqlSession的getMapper方法并且传入一个类型对象获取,底层呢调用的是配置对象configuration的getMapper方法,configuration对象是我们在加载DefaultSqlSessionFactory时传入的。
然后我们再来看下这个配置对象的getMapper,传入的是类型对象(补充一点这个类型对象就是我们平时写的DAO层接口,里面是一些数据库操作的接口方法。),和自身也就是DefaultSqlSession。
1 | public <T> T getMapper(Class<T> type, SqlSession sqlSession) { |
我们看到这个configuration的getMapper方法里调用的是mapperRegistry的getMapper方法,参数依然是类型对象和sqlSession。这里呢,我们要先来看下这个MapperRegistry即所谓Mapper注册器是什么。
1 | public class MapperRegistry { |
从这里我们可以知道其实啊这个MapperRegistry就是保持了一个Configuration对象和一个HashMap,而这个HashMap的key是类型对象,value呢是MapperProxyFactory。我们这里先不管MapperProxyFactory是什么东西,我们现在只需要知道MapperRegistry是这么一个东西就可以了。这里有人会问MapperRegistry对象是怎么来的,这里呢是在初始化Configuration对象时初始化了这个MapperRegistry对象的,代码大家可以去看,为了避免混乱,保持贴出来的代码是一条线走下来的,这里就不贴出来了。接下来我们继续看下这个MapperRegistry的getMapper方法。
1 | public <T> T getMapper(Class<T> type, SqlSession sqlSession) { |
这里我们可以看到从knownMappers中获取key为类型对象的MapperProxyFactory对象。然后调用MapperProxyFactory对象的newInstance方法返回,newInstance方法传入sqlSession对象。到这里我们可能看不出什么端倪,那我们就继续往下看这个newInstance方法做的什么事情吧。
1 | public class MapperProxyFactory<T> { |
这里我们可以看到MapperProxyFactory直接new了一个MapperProxy对象,然后调用另外一重载的newInstance方法传入MapperProxy对象。这里我们可以看出一些东西了,通过调用Proxy.newProxyInstance动态代理了我们的mapperProxy对象!这里的mapperInterface即我们的dao层(持久层)接口的类型对象。
总结下就是我们通过sqlSesssion.getMapper(clazz)得到的Mapper对象是一个mapperProxy的代理类!
- 问题3:为什么我调用mapper对象方法就能发出sql操作数据库?
通过上面的讲解,我们知道了这个mapper对象其实是一个一个mapperProxy的代理类!所以呢这个mapperProxy必然实现了InvocationHandler接口。
1 | public class MapperProxy<T> implements InvocationHandler, Serializable { |
所以当我们调用我们的持久层接口的方法时必然就会调用到这个MapperProxy对象的invoke方法,所以接下来我们进入这个方法看看具体mybatis为我们做了什么。
1 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { |
从代码中我们可以看到前面做了一个判断,这个判断主要是防止我们调用像toString方法或者equals方法时也能正常调用。然后我们可以看到它调用cachedMapperMethod返回MapperMethod对象,接着就执行这个MapperMethod对象的execute方法。这个cachedMapperMethod方法主要是能缓存我们使用过的一些mapperMethod对象,方便下次使用。这个MapperMethod对象主要是获取方法对应的sql命令和执行相应SQL操作等的处理
1 | public class MapperMethod { |
说到这个mapperMethod对象的execute方法,我们看下代码具体做了什么事情吧。
1 | public Object execute(SqlSession sqlSession, Object[] args) { |
我们可以清晰的看到这里针对数据库的增删改查做了对应的操作,这里我们可以看下查询操作。我们可以看到这里针对方法的不同返回值作了不同的处理,我们看下其中一种情况。
1 | param = this.method.convertArgsToSqlCommandParam(args); |
这里我们可以看到它将方法参数类型转换成数据库层面上的参数类型,最后调用sqlSession对象的selectOne方法执行。所以我们看到最后还是回到sqlSession对象上来,也就是前面所说的sqlSession是mybatis提供的与数据库交互的唯一对象。
接下来我们看下这个selectOne方法做了什么事,这里我们看的是defaultSqlSession的selectOne方法。
1 | public <T> T selectOne(String statement, Object parameter) { |
我们看到它调用selectList方法,通过去返回值的第一个值作为结果返回。那么我们来看下这个selectList方法。
1 | public <E> List<E> selectList(String statement, Object parameter) { |
我们可以看到这里调用了executor的query方法,我们再进入到query里看看。这里我们看的是BaseExecutor的query方法。
1 | public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { |
这里我们抓住这样的一句话
1 | list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); |
进入这个方法
1 | private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { |
我们看到有个一个方法doQuery,进入方法看看做了什么。点进去后我们发现是抽象方法,我们选择simpleExecutor子类查看实现。
1 | public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { |
我们可以看到通过configuration对象的newStatementHandler方法构建了一个StatementHandler,然后在调用prepareStatement方法中获取连接对象,通过StatementHandler得到Statement对象。另外我们注意到在获取了Statement对象后调用了parameterize方法。继续跟踪下去(自行跟踪哈)我们可以发现会调用到ParameterHandler对象的setParameters去处理我们的参数。所以这里的prepareStatement方法主要使用了StatementHandler和ParameterHandler对象帮助我们处理语句集和参数的处理。最后还调用了StatementHandler的query方法,我们继续跟踪下去。
这里我们进入到PreparedStatementHandler这个handler查看代码。
1 | public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { |
看到这里,我们终于找到了操作数据库的地方了,就是ps.execute()这句代码。底层我们可以发现就是我们平时写的JDBC!然后将这个执行后的PreparedStatement交给resultSetHandler处理结果集,最后返回我们需要的结果集。