MyBatis的原理

2次阅读

共计 8181 个字符,预计需要花费 21 分钟才能阅读完成。

MyBatis 核心类

SqlSessionFactory

每一个 MyBatis 应用都是以一个 SqlSessionFactory 的实例为核心构建的。SqlSessionFactory的核心作用是什么?

从类的名称上可以看出来,SqlSessionFactory是产生 SqlSession 的工厂。SqlSessionFactory是通过 SqlSessionFactoryBuilder 这个构建器来构建的。

SqlSessionFactory是一个接口,其中定义了获取 SqlSession 的方法。

public interface SqlSessionFactory {
    // 获取一个 SqlSession
    SqlSession openSession();
    // 获取一个 SqlSession, 参数设置事务是否自动提交
    SqlSession openSession(boolean var1);
    // 通过指定 Connection 中的参数获取
    SqlSession openSession(Connection var1);
    // 获取 SqlSession, 设置事务的隔离级别,NONE(0),READ_COMMITTED(2),READ_UNCOMMITTED(1),REPEATABLE_READ(4),SERIALIZABLE(8);
    SqlSession openSession(TransactionIsolationLevel var1);
    // 获取 SqlSession,同时制定执行的类别,支持三种 SIMPLE(这种模式下,将为每个语句创建一个 PreparedStatement),REUSE(这个模式下重复使用 preparedStatment),BATCH(批量更新,insert 时候,如果没有提交,无法获取自增 id);
    SqlSession openSession(ExecutorType var1);
    SqlSession openSession(ExecutorType var1, boolean var2);
    SqlSession openSession(ExecutorType var1, TransactionIsolationLevel var2);
    SqlSession openSession(ExecutorType var1, Connection var2);
    // 获取所有的配置项
    Configuration getConfiguration();}

SqlSessionFactory包含两个实现:DefaultSqlSessionFactorySqlSessionManager
SqlSessionFactory 的实例如何创建呢?
1、通常我们是在只有 MyBatis 的项目中是使用下面的代码段来创建的:

String resource = "org/mybatis/example/mybatis-config.xml";
// 读取 mybatis 配置的问题
InputStream inputStream = Resources.getResourceAsStream(resource);
// 通过 SqlSessionFactoryBuilder 的 build 方式创建
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

2、在 Spring boot 和 MyBatis 结合的项目中我会使用下面的代码段来创建:

MyBatis-Spring-Boot-Starter 依赖将会提供如下

  • 自动检测现有的 DataSource
  • 将创建并注册 SqlSessionFactory 的实例,该实例使用 SqlSessionFactoryBean 将该 DataSource 作为输入进行传递
  • 将创建并注册从 SqlSessionFactory 中获取的 SqlSessionTemplate 的实例。
  • 自动扫描您的 mappers,将它们链接到 SqlSessionTemplate 并将其注册到 Spring 上下文,以便将它们注入到您的 bean 中。
  • 就是说,使用了该 Starter 之后,只需要定义一个 DataSource 即可(application.properties 中可配置),它会自动创建使用该 DataSource 的 SqlSessionFactory Bean 以及 SqlSessionTemplate。会自动扫描你的 Mappers,连接到 SqlSessionTemplate,并注册到 Spring 上下文中.
@Bean
public SqlSessionFactory sqlSessionFactory(@Qualifier("druidDataSource") DataSource druidDataSource) throws Exception {
    // 先创建一个 SqlSessionFactoryBean
    SqlSessionFactoryBean fb = new SqlSessionFactoryBean();
    // 在这个 bean 里设置需要的参数
    fb.setDataSource(this.dynamicDataSource(druidDataSource));
    fb.setTypeAliasesPackage(env.getProperty("mybatis.type-aliases-package"));
    fb.setTypeHandlersPackage(env.getProperty("mybatis.type-handlers-package"));
    fb.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(env.getProperty("mybatis.mapperLocations")));
    // 通过这个方法获取一个 SqlSessionFactory
    return fb.getObject();}

在代码里一直向下查找的时候就会发现:这个过程其实和上面的过程一样。
SqlSessionFactoryBean 会将一系列的属性封装成一个 Configuration 对象,然后调用
this.sqlSessionFactoryBuilder.build(configuration) 来创建。而在 sqlSessionFactoryBuilder 里主要就是解析资源的内容,然后进行创建。

在这里有分别使用了两个设计模式:

  • 工厂模式

SqlSessionFactory就是一个工厂模式——简单工厂模式的变形实现。
通过 SqlSessionFactory 中重载的不同 openSession()方法来获取不同类型的实例。

  • 建造者模式(build 模式)

SqlSessionFactoryBuilder创建 SqlSessionFactory 就是建造者模式的实现。在创建的过程中需要解析很多的文件,生成对象,进行缓存等操作,所以一个方法是很难直接写完,所以其中应用了大量的 build 模式:

protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
    XMLConfigBuilder xmlConfigBuilder = null;
    Configuration configuration;
    if (this.configuration != null) {......} else if (this.configLocation != null) {xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), (String)null, this.configurationProperties);
        configuration = xmlConfigBuilder.getConfiguration();} else {......}
    ......

    if (xmlConfigBuilder != null) {
        try {
            //builder 解析
            xmlConfigBuilder.parse();
            if (LOGGER.isDebugEnabled()) {LOGGER.debug("Parsed configuration file:'" + this.configLocation + "'");
            }
        } catch (Exception var22) {throw new NestedIOException("Failed to parse config resource:" + this.configLocation, var22);
        } finally {ErrorContext.instance().reset();}
    }
    ......
    if (!ObjectUtils.isEmpty(this.mapperLocations)) {Resource[] var29 = this.mapperLocations;
        var27 = var29.length;

        for(var5 = 0; var5 < var27; ++var5) {Resource mapperLocation = var29[var5];
            if (mapperLocation != null) {
                try {
                    //Mapper 文件 build
                    XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments());
                    xmlMapperBuilder.parse();} catch (Exception var20) { } finally {
                    // 单例模式
                    ErrorContext.instance().reset();
                }
            }
        }
    } 
    //build 一个对象返回
    return this.sqlSessionFactoryBuilder.build(configuration);
}
SqlSession

SqlSessionFactory创建完成之后,就可以通过 SqlSessionFactory 对象来获取一个 SqlSession 实例.SqlSession是一次与数据库的会话. 在他的接口中定义了一些列的 CRUD 和事务的操作接口。SqlSession是暴露给用户使用的 API, 一个 SqlSession 对应着一次数据库会话。SqlSession不是线程安全的,所以在使用的时候一定要保证他是局部变量。
他对应的类图如下:

SqlSession有几种常见的实现:DefaultSqkSession是默认的非线程安全的实现,SqlSessionManager是 Mybatis 中对 SqlSession 的线程安全实现,在内部是使用的 private ThreadLocal<SqlSession> localSqlSession = new ThreadLocal(); 的形式来保证线程安全的,SqlSessionTemplate是 MyBatis-Spring 的核心。作为 SqlSession 的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的 SqlSession。SqlSessionTemplate 是线程安全的,可以被多个 DAO 或映射器所共享使用。

因为 SqlSessionTemplate 是线程安全的,所以当 SqlSessionTemplate 是单例的时候,多线程调用 SqlSessionTemplate 仍然使用的是同一个 SqlSession, 接下来看一下SqlSessionTemplate 是如何保证线程安全的呢?

首先我们看一下 SqlSessionTemplate 的创建过程:

/**
 * 构造函数 1,需要传入参数 SqlSessionFactory
 */
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
}

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
}

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {Assert.notNull(sqlSessionFactory, "Property'sqlSessionFactory'is required");
    Assert.notNull(executorType, "Property'executorType'is required");
    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    // 创建代理类的实例,该代理类实现了 SqlSession 接口,这里使用的是基于 JDK 的动态代理,SqlSessionInterceptor 也是实现了 InvocationHandler 接口,最后代理对象的操作都会经过 invoke 执行
    //class SqlSessionInterceptor implements InvocationHandler
    this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor());
}

invoke 的实现是实现线程安全的核心:

private class SqlSessionInterceptor implements InvocationHandler {private SqlSessionInterceptor() { }
  
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
     // 获取真实的 SqlSession,这个是真实使用的,是 MyBatis 生成的 DefaultSqlSession, 获取是线程安全实现的核心
        SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);

        Object unwrapped;
        try {
            // 执行操作
            Object result = method.invoke(sqlSession, args);
            // 如果不是事务类型的,那么设置为自动提交
            if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {sqlSession.commit(true);
            }
            // 执行结果包装
            unwrapped = result;
        } catch (Throwable var11) {unwrapped = ExceptionUtil.unwrapThrowable(var11);
            if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
                sqlSession = null;
                Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException)unwrapped);
                if (translated != null) {unwrapped = translated;}
            }

            throw (Throwable)unwrapped;
        } finally {if (sqlSession != null) {SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
            }

        }
        // 返回执行结果
        return unwrapped;
    }
}

接着看 getSqlSession()的代码:

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {Assert.notNull(sessionFactory, "No SqlSessionFactory specified");
    Assert.notNull(executorType, "No ExecutorType specified");
    //SqlSessionHolder 是对 SqlSession 的一个功能包装,TransactionSynchronizationManager 是一个事务同步管理器,维护当前线程事务资源,信息以及 TxSync 集合,getResource 会从 ThreadLocal<Map<Object, Object>> resources 中获取当前线程 SqlSessionHolder 实例
    SqlSessionHolder holder = (SqlSessionHolder)TransactionSynchronizationManager.getResource(sessionFactory);
    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {return session;} else {if (LOGGER.isDebugEnabled()) {LOGGER.debug("Creating a new SqlSession");
        }
        // 如果没有获取成功,那么开启一个 SqlSession
        session = sessionFactory.openSession(executorType);
        // 注册 SessionHolder
        registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
        return session;
    }
}

registerSessionHolder()实现?

好难啊~~~~~

正文完
 0