写点什么

扒开 SqlSession 的外衣

用户头像
田维常
关注
发布于: 2020 年 12 月 29 日

关注“Java 后端技术全栈”


回复“面试”获取全套面试资料


老规矩,先上案例代码,我们按照这个案例一步一步的搞定 Mybatis 源码。


public class MybatisApplication {    public static final String URL = "jdbc:mysql://localhost:3306/mblog";    public static final String USER = "root";    public static final String PASSWORD = "123456";    public static void main(String[] args) {        String resource = "mybatis-config.xml";        InputStream inputStream = null;        SqlSession sqlSession = null;        try {            inputStream = Resources.getResourceAsStream(resource);            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);            sqlSession = sqlSessionFactory.openSession();            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);            System.out.println(userMapper.selectById(1));        } catch (Exception e) {            e.printStackTrace();        } finally {            try {                inputStream.close();            } catch (IOException e) {                e.printStackTrace();            }            sqlSession.close();        }    }
复制代码


前面我们已经讲了 Mybatis 是如何解析相关配置文件的,如果怕迷路,还是建议先看前一篇文章:


Mybatis是如何解析配置文件的?看完终于明白了


由于很多小伙伴在催,说 Mybatis 源码系列好像何时才有下文了,为此老田熬夜写了这篇。


图片


继续开撸~~


SqlSession sqlSession = sqlSessionFactory.openSession();
复制代码


前面那篇文章已经分析了,这里的 sqlSessionFactory 其实就是 DefaultSqlSessionFactory。


所以这里,我们就从 DefaultSqlSessionFactory 里的 openSession 方法开始。


public class DefaultSqlSessionFactory implements SqlSessionFactory {  private final Configuration configuration;  public DefaultSqlSessionFactory(Configuration configuration) {    this.configuration = configuration;  }  //创建session,这个方法直接调用本类中的另外一个方法  @Override  public SqlSession openSession() {    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);  }  //其实是调用这个方法  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {    Transaction tx = null;    try {      //对应xml标签<environments> ,这个在配置文件解析的时候就已经存放到configuration中了。      final Environment environment = configuration.getEnvironment();      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);      //创建一个executor来执行SQL       final Executor executor = configuration.newExecutor(tx, execType);      //这里也说明了,为什么我们代码里的SqlSession是DefaultSqlSession      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();    }  }      private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {    if (environment == null || environment.getTransactionFactory() == null) {      return new ManagedTransactionFactory();    }    return environment.getTransactionFactory();  }
复制代码


这个方法中的主要内容有:


图片


下面我们就来逐个攻破。


创建事务 Transaction


事务工厂类型可以配置为 JDBC 类型或者 MANAGED 类型。


图片


JdbcTransactionFactory 生产 JdbcTransaction。

ManagedTransactionFactory 生产 ManagedTransaction。


如果配置的 JDBC,则会使用 Connection 对象的 commit()、rollback()、close()方法来管理事务。


如果我们配置的是 MANAGED,会把事务交给容器来管理,比如 JBOSS,Weblogic。因为我们是本地跑的程序,如果配置成 MANAGED 就会不有任何事务。


但是,如果我们项目中是 Spring 集成 Mybatis,则没有必要配置事务,因为我们会直接在 applicationContext.xml 里配置数据源和事务管理器,从而覆盖 Mybatis 的配置。


创建执行器 Executor


调用 configuration 的newExecutor方法创建 Executor。


final Executor executor = configuration.newExecutor(tx, execType);//Configuration中public Executor newExecutor(Transaction transaction, ExecutorType executorType) {    executorType = executorType == null ? defaultExecutorType : executorType;    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;    Executor executor;    //第一步    if (ExecutorType.BATCH == executorType) {      executor = new BatchExecutor(this, transaction);    } else if (ExecutorType.REUSE == executorType) {      executor = new ReuseExecutor(this, transaction);    } else {      executor = new SimpleExecutor(this, transaction);    }    //第二步    if (cacheEnabled) {      executor = new CachingExecutor(executor);    }    //第三步    executor = (Executor) interceptorChain.pluginAll(executor);    return executor;  }
复制代码


此方法分三个步骤。


第一步:创建执行器


Executor 的基本类型有三种:


public enum ExecutorType {  SIMPLE, REUSE, BATCH}
复制代码


SIMPLE 为默认类型。


为什么要让抽象类 BaseExecutor 实现 Executor 接口,然后让具体实现类继承抽象类呢?


这就是模板方法模式的实现。


模板方法模式就是定义一个算法骨架,并允许子类为一个或者多个步骤提供实现。模板方法是得子类可以再不改变算法结构的情况下,重新定义算法的某些步骤。


关于模板方法模式推荐阅读:


如何快速掌握模板方法模式


抽象方法是在子类汇总实现的,每种执行器自己实现自己的逻辑,BaseExecutor 最终会调用到具体的子类中。


抽象方法


protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;protected abstract List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException;protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException;protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)  throws SQLException;
复制代码


第二步:缓存装饰


在上面代码中的第二步


if (cacheEnabled) {      executor = new CachingExecutor(executor);}
复制代码


如果cacheEnabled=true,会用装饰器设计模式对 Executor 进行装饰。


第三步:插件代理


缓存装饰完后,就会执行


executor = (Executor) interceptorChain.pluginAll(executor);
复制代码


这里会对 Executor 植入插件逻辑。


比如:分页插件中就需要把插件植入的 Executor


图片


好了,到此,执行器创建的就搞定了。


创建 DefaultSqlSession 对象


把前面解析配置文件创建的 Configuration 对象和创建的执行器 Executor 赋给 DefaultSqlSession 中的属性。


public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {  this.configuration = configuration;  this.executor = executor;  this.dirty = false;  this.autoCommit = autoCommit;}
复制代码


到这里,SqlSession(DefaultSqlSession)对象就创建完毕。


总结


本文我们讲了如何创建 SqlSession 的几个步骤,最后我们获得一个 DefaultSqlSession 对象,里面包含了执行器 Executor 和配置对象 Configuration。Executor 是 SQL 的实际执行对象。Configuration 里保存着配置文件内容。


本文源码分析的整个流程如下图:


图片


码字不易,给个在看,点个赞呗


推荐阅读


面试官:Mybatis里的设计模式有哪些?我一口气答了8种


面试官问我:能说几个常见的Linux性能调优命令吗?


发布于: 2020 年 12 月 29 日阅读数: 18
用户头像

田维常

关注

关注公众号:Java后端技术全栈,领500G资料 2020.10.24 加入

关注公众号:Java后端技术全栈,领500G资料

评论

发布
暂无评论
扒开 SqlSession 的外衣