Mybatis源码分析:从一个简单的例子开始

Posted Ferron Zhu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Mybatis源码分析:从一个简单的例子开始相关的知识,希望对你有一定的参考价值。

文章目录

0、前言

  • 笔者从现在开始准备开始更新部分java开发相关框架的源码分析学习,就先从myabtis开始吧。文章内容基于源码和官方文档。
  • mybatis中文官方文档:https://mybatis.org/mybatis-3/zh/

1、从一个简单的例子开始

从官网的例子开始:从xml中构建SqlSessionFactory

//resource路径
String resource = "org/mybatis/example/mybatis-config.xml";
//获得resource的文件流
InputStream inputStream = Resources.getResourceAsStream(resource);
//使用resource的文件流创建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

第三行代码最终走的是下面的方法

//前文传入的inputStream,在这里已经封装成了mybatis中的Configuration config对象
public SqlSessionFactory build(Configuration config) 
    //返回的默认DefaultSqlSessionFactory,是SqlSessionFactory接口的一个实现类
    return new DefaultSqlSessionFactory(config);

我们可以简单看一下mybatis中的Configuration类的几个属性。

//数据库的配置环境类
protected Environment environment;
//执行类型
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
//数据库id
protected String databaseId;
//重要:MapperRegistry
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);

到此为止官方的创建SqlSessionFactory的三行代码我们解析完毕。我们可以知道:当调用完SqlSessionFactoryBuilder()的build方法后,程序会将xml文件封装成一个Configuration,然后返回一个SqlSessionFactory的默认实现类实例。

紧接着官方给出了下列的代码:

try (SqlSession session = sqlSessionFactory.openSession()) 
  BlogMapper mapper = session.getMapper(BlogMapper.class);
  Blog blog = mapper.selectBlog(101);

我们首先了解一下三个session究竟是什么,再继续我们的源码学习。

  • SqlSessionFactoryBuilder,说白了就是一个工具人,用于创建SqlSessionFactory,一旦SqlSessionFactory创建了,其就没有存在的意义了
  • SqlSessionFactory:Session的工厂,可以类比成数据库的连接池,保存着一堆的Session。SqlSessionFactory的作用域应该是整个应用的周期,应用运行过程中其应该一直存活
  • Session:可以类比成数据库的一次连接,相当于数据库的一次请求,用完需要关闭。

介绍完这些,我们就很清楚try括号里面的代码实际就是从SqlSessionFactory拿出一个Session。我们扒一下DefaultSqlSessionFactory实现类的实现代码。

@Override
public SqlSession openSession() 
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);


private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) 
    Transaction tx = null;
    try 
        final Environment environment = configuration.getEnvironment();
        final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        final Executor executor = configuration.newExecutor(tx, execType);
        return new DefaultSqlSession(configuration, executor, autoCommit);
     catch (Exception e) 
        closeTransaction(tx); // may have fetched a connection so lets call close()
        throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
     finally 
        ErrorContext.instance().reset();
    

可以看到openSession()的最终结果就是将一堆乱七八遭的东西封装成了mybatis包的DefaultSqlSession类实例,并且返回。

我们简单扒一扒DefaultSqlSession类

public class DefaultSqlSession implements SqlSession 
    public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) 
        this.configuration = configuration;
        this.executor = executor;
        this.dirty = false;
        this.autoCommit = autoCommit;
      

其实看到第一行我们就知道了,mybatis中的DefaultSqlSession类是sql包中SqlSession接口的实现类!也就是实际上最终返回的其实是SqlSession接口的实现类。

接着往下走

//DefaultSqlSession的getMapper()方法
@Override
public <T> T getMapper(Class<T> type) 
    return configuration.getMapper(type, this);


//Configuration的getMapper()方法
public <T> T getMapper(Class<T> type, SqlSession sqlSession) 
    return mapperRegistry.getMapper(type, sqlSession);


//MapperRegistry的getMapper()方法
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);
    

MapperRegistry的getMapper()方法大概就是接受一个Class的对象,然后在根据配置文件加载的knownMappers中去寻找存在的MapperProxyFactory,如果不存在,可能是未在mybatis-config.xml中配置相应的Mapper。

最终mapperProxyFactory.newInstance(sqlSession)执行过程如下。

public T newInstance(SqlSession sqlSession) 
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);


public class MapperProxy<T> implements InvocationHandler, Serializable 
    

可以看到,mybatis创建mapper对象的是通过MapperProxy这个对象实现的,MapperProxy是究竟是什么呢?往下看MapperProxy实现了InvocationHandler这个接口。懂了吧?本质就是动态代理!通过动态代理获得程序运行时传入的class,再创建对象的Mapper对象返回。

接下来生成的Mapper对象之后,调用接口里面的方法,就得执行对应的sql方法了。

以上是关于Mybatis源码分析:从一个简单的例子开始的主要内容,如果未能解决你的问题,请参考以下文章

Mybatis源码分析:从一个简单的例子开始

MyBatis源码分析插件实现原理

源码学习之MyBatis的底层查询原理

从 0 开始手写一个 Mybatis 框架,三步搞定!

MyBatis源码分析insert方法update方法delete方法处理流程(上篇)

MyBatis源码分析之@SelectProvider注解使用详解