Mybatis 源码学习(二十一) —— session 包

Mybatis 系列文章导航

session 包是整个 MyBatis 对外的接口包,也是离用户最近的包。
因为要使用 MyBatis 或者说得到代理对象我们都是在和 session 包中的类进行交互。

    // 通过 mybatis 的工具类 Resources 获取配置文件
    InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
    // 将配置文件交给 SqlSessionFactoryBuilder 以构建 SqlSessionFactory
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    // 得到 SqlSession
    SqlSession sqlSession = sqlSessionFactory.openSession();
    // 获取代理后的 UserMapper
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

可以发现其中比较关键的几个类 SqlSessionFactoryBuilderSqlSessionFactorySqlSession 都是在 session 包中所定义的。

SqlSession 相关类

SqlSession 相关类图
请添加图片描述

生成 SqlSession

从最开始的示例代码中我们可以发现,要生成一个 SqlSession 需要经过多个步骤,我们只关注和 session 包中进行交互的步骤。

  1. 创建 SqlSessionFactoryBuilder 传入配置信息。
  2. 利用 SqlSessionFactoryBuilder 构建出 SqlSessionFactory
  3. 通过 SqlSessionFactory 开启一个 SqlSession

SqlSessionFactoryBuilder

public class SqlSessionFactoryBuilder {
  public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      // 传入配置文件,创建一个 XMLConfigBuilder 类
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      // 解析配置文件,得到配置文件对应的 Configuration 对象
      // 根据 Configuration 对象,获得一个 DefaultSqlSessionFactory
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("");
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException ignore) {
      }
    }
  }
  
  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }
}

DefaultSqlSessionFactory

public class DefaultSqlSessionFactory implements SqlSessionFactory {
  // MyBatis 全局配置信息
  private final Configuration configuration;
  
  @Override
  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }
  
  // 从 DataSource 中开启一个会话
  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      // 得到当前 Configuration 的运行环境
      final Environment environment = configuration.getEnvironment();
      // 从缓存中得到事务工厂
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      // 创建事务
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      // 构建执行器
      final Executor executor = configuration.newExecutor(tx, execType);
      // 构建 SqlSession
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx);
      throw ExceptionFactory.wrapException("");
    } finally {
      ErrorContext.instance().reset();
    }
  }

}

DefaultSqlSession

executor 包是最为核心的执行器包。而 session 包的一个重要的作用就是将这个能力暴露给用户,让用户能使用,所以 DefaultSqlSession 的作用就这个。

public class DefaultSqlSession implements SqlSession {
  // MyBatis 全局配置信息
  private final Configuration configuration;
  // 执行器
  private final Executor executor;
  // 是否自动提交
  private final boolean autoCommit;
  // 缓存是否已经被污染
  private boolean dirty;
  // 游标列表
  private List<Cursor<?>> cursorList;
  
  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    return selectList(statement, parameter, rowBounds, Executor.NO_RESULT_HANDLER);
  }
  
  private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    try {
      // 得到映射语句
      MappedStatement ms = configuration.getMappedStatement(statement);
      // 调用 Executor 的方法
      return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("“);
    } finally {
      ErrorContext.instance().reset();
    }
  }
}

SqlSessionManager

SqlSessionMangerSqlSession 相关类分开讲解是因为它算是一种工具类,就算不用这个工具类,也能使用 MyBatis。但是在某些场景下使用它可能会更加方便。

并且 SqlSessionManager 十分的特殊,因为它同时实现了工厂类和其产品类的接口。

它的一个重要功能就是在内部实现了线程之间 SqlSession 的隔离,也就是会为每一个线程创建不同的 SqlSession,而这个功能也是通过动态代理实现的,可以发现动态代理真的特别强大。

public class SqlSessionManager implements SqlSessionFactory, SqlSession {
  private final SqlSessionFactory sqlSessionFactory;
  private final SqlSession sqlSessionProxy;
  
  // 利用 ThreadLocal 存放线程独有的 SqlSession
  private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<>();

  private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
    this.sqlSessionFactory = sqlSessionFactory;
    this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[]{SqlSession.class},
        new SqlSessionInterceptor());
  }
  
  @Override
  public <E> List<E> selectList(String statement) {
    return sqlSessionProxy.selectList(statement);
  }
  
  private class SqlSessionInterceptor implements InvocationHandler {

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();
      // 如果当前线程已经开启了一个 SqlSession 则直接使用
      if (sqlSession != null) {
        try {
          return method.invoke(sqlSession, args);
        } catch (Throwable t) {
          throw ExceptionUtil.unwrapThrowable(t);
        }
      } else {
        // 如果没有开启 SqlSession,则创建一个临时使用的 SqlSession
        try (SqlSession autoSqlSession = openSession()) {
          try {
            final Object result = method.invoke(autoSqlSession, args);
            autoSqlSession.commit();
            return result;
          } catch (Throwable t) {
            autoSqlSession.rollback();
            throw ExceptionUtil.unwrapThrowable(t);
          }
        }
      }
    }
  }
}

可以发现是通过 ThreadLocal 实现的线程隔离。

Configuration

在之前的文章中 Configuration 出现了很多次,可以说是老熟人了,但是我们一直都没有深入的去解析它。要知道 mybatis-config.xml 是 MyBatis 配置的入口,而配置文件的根结点就是 <configuration> 节点,因此 Configuration 对象内保存了所有的配置信息。

并且 Configuration 类对配置信息进行了进一步的加工,为许多配置项设置了默认值。

而这些可能在 MyBatis 每个地方都有使用到,所以如果很早的就去分析它的话,可能小伙伴们看了也特别的迷茫,估计很快就会跳过去,所以在 session 来分析的话,可以说是刚刚好,因为我们已经将核心的内容都讲解的差不多了。

  // <environment> 节点信息
  protected Environment environment;

  // <<<<<<<<<<< <setting> 节点信息开始 >>>>>>>>>>>
  protected boolean safeRowBoundsEnabled;
  protected boolean safeResultHandlerEnabled = true;
  protected boolean mapUnderscoreToCamelCase;
  protected boolean aggressiveLazyLoading;
  protected boolean multipleResultSetsEnabled = true;
  protected boolean useGeneratedKeys;
  protected boolean useColumnLabel = true;
  protected boolean cacheEnabled = true;
  // 在值为 null 的时候,是否仍然需要调用对应的 set 方法
  protected boolean callSettersOnNulls;
  protected boolean useActualParamName = true;
  // 当没有一个属性成功映射时返回空实例
  protected boolean returnInstanceForEmptyRow;
  protected boolean shrinkWhitespacesInSql;

  protected String logPrefix;
  protected Class<? extends Log> logImpl;
  protected Class<? extends VFS> vfsImpl;
  protected Class<?> defaultSqlProviderType;
  protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
  protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
  protected Set<String> lazyLoadTriggerMethods = new HashSet<>(Arrays.asList("equals", "clone", "hashCode", "toString"));
  protected Integer defaultStatementTimeout;
  protected Integer defaultFetchSize;
  protected ResultSetType defaultResultSetType;
  protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
  protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
  // 启动了自动映射,但是出现数据库字段找不到对应属性的情况下需要采取的措施
  // 有三种方法 没有任何操作、打印异常日志、抛出异常,默认是第一种没有任何操作
  protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
  // 是否启用来加载
  protected boolean lazyLoadingEnabled = false;
  // <<<<<<<<<<< <setting> 节点信息结束 >>>>>>>>>>>

  // <properties> 节点信息
  protected Properties variables = new Properties();
  // 反射工厂
  protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
  // 对象工厂
  protected ObjectFactory objectFactory = new DefaultObjectFactory();
  // 对象包装工厂
  protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
  // 代理工厂
  protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL
  // 数据库编号
  protected String databaseId;
  // 配置工厂,用来创建用于加载反序列化的未读属性的配置
  protected Class<?> configurationFactory;
  // 映射注册表
  protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
  // 拦截器链(用于支持插件的插入)
  protected final InterceptorChain interceptorChain = new InterceptorChain();
  // 类型处理器注册表,内置了许多,也可以通过 <typeHandler> 节点补充
  protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(this);
  // 类型别名注册表,内置了许多,也可以通过 <typeAlias> 节点补充
  protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
  // 语言驱动注册表
  protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
  // 映射的数据库操作语句
  protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
      .conflictMessageProducer((savedValue, targetValue) ->
          ". please check " + savedValue.getResource() + " and " + targetValue.getResource());
  // 缓存
  protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
  // 结果映射,即所有 <resultMap> 节点
  protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
  // 参数映射,即所以的 <parameterMap> 节点
  protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
  // 主键生成器映射
  protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");
  // 载入的资源,如映射文件资源,防止循环加载
  protected final Set<String> loadedResources = new HashSet<>();
  // SQL 语句片段,即所有的 <sql> 节点
  protected final Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers");

  // 存储暂时失败的 select、insert、update、delete 节点的解析
  protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<>();
  // 存储暂时失败的 cache-ref 节点的解析
  protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<>();
  // 存储暂时失败的 resultMap 节点解析
  protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<>();
  // 存储暂时失败的 Method 解析
  protected final Collection<MethodResolver> incompleteMethods = new LinkedList<>();

  // 用来存储跨 namespace 的缓存共享设置
  protected final Map<String, String> cacheRefMap = new HashMap<>();

Configuration 中的重要方法

Configuration 不止存储那些配置信息,还提供了一些重要的方法。

创建执行器

  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    // 根据执行器类型创建对应的执行器,默认为 SIMPLE
    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);
    }
    
    // 如果开启了二级缓存,则还需要封装为 CachingExecutor
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    
    // 装载插件
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

创建语句执行器

  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    // 创建统一的 RoutingStatementHandler
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    // 插件装载
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

Configuration 中的代码虽然多,但是大部分都非常的简单,都是基于了存储的配置信息又提供了一些增删改查方法。所以就不在过多的讲解。

参考文献

  1. 《通用源码阅读指导书:Mybatis源码阅读详解》—— 易哥
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: MyBatis 是一个开源的持久层框架,可以方便地将 SQL 语句和 Java 对象进行映射。如果您想要学习 MyBatis 源码,可以按照以下步骤进行: 1. 了解 MyBatis 的架构和设计原理。可以阅读官方文档和相关书籍,例如《MyBatis 技术内幕》。 2. 下载 MyBatis 的源代码,并导入到 IDE 中。MyBatis 使用 Maven 进行构建,您可以使用 IDE 的 Maven 插件来下载依赖项。 3. 查看 MyBatis 的源代码结构。MyBatis 的主要代码在 `mybatis-3` 模块中,括 `src/main/java` 和 `src/main/resources` 目录。其中,`src/main/java` 目录含了 MyBatis 的核心代码,例如 `org.apache.ibatis.session.SqlSession` 类;`src/main/resources` 目录含了 MyBatis 的配置文件和映射文件。 4. 阅读 MyBatis 的源代码。可以从 MyBatis 的入口处 `org.apache.ibatis.session.SqlSessionFactoryBuilder` 开始,深入了解 MyBatis 的初始化流程、SQL 语句的执行流程、映射文件的解析和缓存等。 5. 调试 MyBatis 的源代码。可以使用 IDE 的调试功能,对 MyBatis 进行单步调试,观察代码的执行流程,加深对 MyBatis 的理解。 6. 学习 MyBatis 的单元测试。MyBatis 的单元测试位于 `src/test/java` 目录中,可以通过单元测试来了解 MyBatis 的各个功能点的使用方法和测试用例。 7. 参与 MyBatis 的开发。如果您对 MyBatis 源码有深入的了解,并希望为 MyBatis 做出贡献,可以参与 MyBatis 的开发,贡献代码和文档,提交 issue 和 PR。MyBatis 的开发社区非常活跃,可以在官方网站和 GitHub 上找到相关信息。 希望这些步骤对您学习 MyBatis 源码有所帮助。 ### 回答2: MyBatis是一个开源的Java持久层框架,通过操作对象与数据库关系映射来提供数据持久化的功能。了解MyBatis源码学习和使用该框架的重要一步。 首先,MyBatis源码结构比较清晰,主要分为核心模块和附属模块。核心模块括XML配置解析、SQL语句解析、参数处理、数据库连接管理等功能的实现,是实现MyBatis基本功能的核心部分。附属模块括缓存、事务、插件等额外功能的实现,可以根据需要进行扩展和配置。 学习MyBatis源码可以从以下几个方面入手: 1. 配置文件解析:MyBatis通过XML配置文件来进行相关的配置,了解配置文件的解析过程可以帮助理解MyBatis的初始化过程和各项配置的作用。 2. SQL语句解析与执行:MyBatis将SQL语句封装成MappedStatement对象进行管理,了解MappedStatement的生成过程,以及SQL语句的解析、参数处理和执行过程,可以深入了解MyBatis的SQL执行原理。 3. 会话管理和事务处理:MyBatis采用SqlSessionFactory和SqlSession来管理数据库连接和事务,在MyBatis源码中可以学习到如何管理数据库连接池、事务的提交和回滚等核心功能的实现。 4. 缓存机制:MyBatis提供了一级缓存和二级缓存的功能,了解缓存的生成和更新过程,以及缓存的命中和失效原理,可以提高数据库查询性能。 总之,通过学习MyBatis源码,可以加深对该框架的理解,掌握其内部实现原理,有助于在使用时更加灵活和高效地进行开发。同时,也为以后解决一些特殊问题提供了更多的思路和方法。 ### 回答3: MyBatis是一个优秀的持久层框架,学习源码有助于理解其底层原理和设计思想。 首先,可以从MyBatis的入口开始学习,即SqlSessionFactoryBuilder类。该类负责解析配置文件、创建Configuration对象,并通过Configuration对象创建SqlSessionFactory实例。 接下来,可以学习Configuration类,该类负责管理整个MyBatis的配置信息。其中括了数据库连接信息、映射文件信息、缓存信息等。在该类内部,会调用XMLMapperBuilder类解析映射文件,在解析映射文件过程中,会创建MappedStatement对象,该对象表示一条SQL语句的映射信息。 学习MappedStatement对象可以了解MyBatis的SQL语句解析过程。该对象含了SQL语句的相关信息,括参数映射关系、返回结果映射关系等。在执行SQL语句时,会使用ParameterHandler类处理参数,通过ResultSetHandler类处理查询结果。 同时,学习到Executor接口及其实现类,可以了解MyBatis的执行过程。Executor负责执行SQL语句,其中括了写操作的update方法和读操作的query方法。在执行过程中,会通过StatementHandler类创建PreparedStatement对象,并通过ResultSetHandler类处理执行结果。 最后,还可以学习MyBatis的事务处理和缓存机制。Transaction接口及其实现类负责事务管理,通过JDBC的事务机制实现了事务的提交和回滚。而Cache接口及其实现类负责缓存查询结果,在查询时会先从缓存中查找结果。 总结来说,通过学习MyBatis源码可以深入理解其底层原理和设计思想。从SqlSessionFactory的创建开始,到Configuration的配置解析、MappedStatement的创建,再到Executor的执行过程和Transaction的事务管理,以及Cache的缓存机制,逐步掌握MyBatis的各个组件和它们之间的交互关系。这对于我们使用MyBatis开发项目,解决问题和优化性能都具有积极的意义。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值