Mybatis源码解析一(SqlSessionFactory和SqlSession的获取)

2022-09-02 16:32:08 浏览数 (1)

大家好,又见面了,我是你们的朋友全栈君。

一、SqlSessionFactory

SqlSessionFactory是MyBatis的关键对象, 它是个单个数据库映射关系经过编译后的内存镜像; SqlSessionFactory对象的实例可以通过SqlSessionFactoryBuilder对象类获得; SqlSessionFactoryBuilder从XML配置文件或一个预先定制的Configuration的实例构建出SqlSessionFactory的实例; 每一个MyBatis的应用程序都以一个SqlSessionFactory对象的实例为核心, 同时SqlSessionFactory也是线程安全的, SqlSessionFactory一旦被创建, 应该在应用执行期间都存在; 在应用运行期间不要重复创建多次, 建议使用单例模式SqlSessionFactory是创建SqlSession的工厂;

Configuration.xml相关配置:

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <properties>
        <property name="driver" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true"/>
        <property name="username" value="root"/>
        <property name="password" value="m123"/>
    </properties>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?characterEncoding=utf-8"/>
                <property name="username" value="root"/>
                <property name="password" value="m123"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="comlicibatismapperUserMapper.xml"/>
        <mapper class="com.lic.ibatis.mapper.UserMapperAnn"></mapper>
    </mappers>
</configuration>

UserMapper.xml配置:

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lic.ibatis.dao.UserMapper">

    <select id="getUserById" parameterType="int" resultType="com.lic.ibatis.entity.User">
        select * from user where id = #{id}
    </select>
</mapper>

测试类:

代码语言:javascript复制
package com.lic.ibatis.test;

import com.lic.ibatis.entity.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.Reader;

public class MybatisHelloWorld {
  public static void main(String[] args) {
    try {
      Reader reader = Resources.getResourceAsReader("Configuration.xml");
      SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
      SqlSession session = sqlSessionFactory.openSession();
      try {
        User user = (User) session.selectOne("com.lic.ibatis.dao.UserMapper.getUserById", 1);
        System.out.println(user.toString());

        User user2 = (User) session.selectOne("com.lic.ibatis.dao.UserMapper.getUserById", 1);
        System.out.println("第二次查询" user2.toString());
      } finally {
        session.close();
      }

    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}

分析:

  1. 对Configuration.xml配置文件进行解析, 生成SqlSessionFactory
  2. 通过SqlSessionFactory获取一个SqlSession实例
  3. 使用selectOne()方法进行查询

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);

SqlSessionFactoryBuilder#build(java.io.Reader)方法实现:

代码语言:javascript复制
 public SqlSessionFactory build(Reader reader) {
    return build(reader, null, null);
  }

public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      /**
       * 创建XML配置解析器
       */
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      /**
       * 1. parser.parse(): 解析配置文件,创建配置类Configuration
       * 2. build(): 创建SqlSessionFactory对象,并返回
       */
      return build(parser.parse());

    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

分析:

  1. 创建XML配置解析器
  2. 解析配置文件
  3. 创建DefaultSqlSessionFactory对象

1. 创建XML配置解析器

XMLConfigBuilder()方法实现:

代码语言:javascript复制
public XMLConfigBuilder(Reader reader, String environment, Properties props) {
    /**
     * new XPathParser(reader, true, props, new XMLMapperEntityResolver())的参数含义: Reader,是否进行DTD 校验,属性配置,XML实体节点解析器
     */
    this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
  }


public XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver) {
    //参数配置
    commonConstructor(validation, variables, entityResolver);
    //将配置信息输入流封装为Document对象, 以便后面进行解析
    this.document = createDocument(new InputSource(reader));
  }

private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }

2. 解析配置文件

XMLConfigBuilder#parse()方法实现:

代码语言:javascript复制
 public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    /**
     * mybatis配置文件解析的主流程:
     * 1. parser.evalNode("/configuration") --> 获取到根节点
     * 2. 根据根标签<configuration>开始解析
     */
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

XMLConfigBuilder#parseConfiguration()方法实现:

代码语言:javascript复制
private void parseConfiguration(XNode root) {
    //解析配置文件中根标签下的所有子标签
    try {
      //issue #117 read properties first
      /**
       * 1. 解析properties节点
       */
      propertiesElement(root.evalNode("properties"));
      /**
       * 2. 解析settings节点
       */
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      /**
       * 3. VFS主要用来加载容器内的各种资源,比如jar或者class文件
       */
      loadCustomVfs(settings);
      loadCustomLogImpl(settings);
      /**
       * 4. 解析类型别名typeAliasesElement
       */
      typeAliasesElement(root.evalNode("typeAliases"));
      /**
       * 5. 加载插件pluginElement
       *
       *  比如: 分页插件PageHelper,再比如druid连接池提供的各种监控、拦截、预发检查功能,
       *  在使用其它连接池比如dbcp的时候,在不修改连接池源码的情况下,就可以借助mybatis的插件体系实现
       */
      pluginElement(root.evalNode("plugins"));

      /**
       * 6. 加载对象工厂objectFactoryElement
       *
       * MyBatis 每次创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成。
       * 默认的对象工厂DefaultObjectFactory做的仅仅是实例化目标类,要么通过默认构造方法,要么在参数映射存在的时候通过参数构造方法来实例化。
       */
      objectFactoryElement(root.evalNode("objectFactory"));
      /**
       * 7. 创建对象包装器工厂objectWrapperFactoryElement
       *
       * 对象包装器工厂主要用来包装返回result对象,比如说可以用来设置某些敏感字段脱敏或者加密等。
       * 默认对象包装器工厂是DefaultObjectWrapperFactory,也就是不使用包装器工厂。
       */
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      /**
       * 8. 加载反射工厂reflectorFactoryElement
       *
       * 因为加载配置文件中的各种插件类等等,为了提供更好的灵活性,
       * mybatis支持用户自定义反射工厂,不过总体来说,用的不多,
       * 要实现反射工厂,只要实现ReflectorFactory接口即可。默认的反射工厂是DefaultReflectorFactory。
       * 一般来说,使用默认的反射工厂就可以了。
       */
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      /**
       * 得到setting之后,调用settingsElement(Properties props)将各值赋值给configuration,
       * 同时在这里有重新设置了默认值,所有这一点很重要,configuration中的默认值不一定是真正的默认值。
       */
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      /**
       * 9. 加载环境配置environmentsElement
       *
       * 环境可以说是mybatis-config配置文件中最重要的部分,
       * 它类似于spring和maven里面的profile,允许给开发、
       * 生产环境同时配置不同的environment,根据不同的环境加载不同的配置,
       * 这也是常见的做法,如果在SqlSessionFactoryBuilder调用期间没有传递使用哪个环境的话,
       * 默认会使用一个名为default”的环境。找到对应的environment之后,就可以加载事务管理器和数据源了。
       * 事务管理器和数据源类型这里都用到了类型别名,JDBC/POOLED都是在mybatis内置提供的,
       * 在Configuration构造器执行期间注册到TypeAliasRegister。
       *
       *  mybatis内置提供JDBC和MANAGED两种事务管理方式,前者主要用于简单JDBC模式,
       * 后者主要用于容器管理事务,一般使用JDBC事务管理方式。
       * mybatis内置提供JNDI、POOLED、UNPOOLED三种数据源工厂,一般情况下使用POOLED数据源。
       */
      environmentsElement(root.evalNode("environments"));
      /**
       * 10. 数据库厂商标识加载databaseIdProviderElement(了解即可)
       */
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      /**
       * 11. 加载类型处理器typeHandlerElement
       *
       * 无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,
       * 还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。
       * mybatis提供了两种方式注册类型处理器,package自动检索方式和显示定义方式。
       * 使用自动检索(autodiscovery)功能的时候,只能通过注解方式来指定 JDBC 的类型。
       */
      typeHandlerElement(root.evalNode("typeHandlers"));

      /**
       * 12. 加载mapper文件 或 mapperElement  --->  (重点)
       */
      mapperElement(root.evalNode("mappers"));

    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: "   e, e);
    }
  }

分析: 对配置文件中各个节点进行解析, 将解析结果封装到Configuration类中; 这里主要看对<mappers>标签的解析

XMLConfigBuilder#mapperElement()方法实现:

代码语言:javascript复制
 /**
   * <mappers>
   *   <mapper resource="org/mybatis/builder/AuthorMapper.xml"/> -----> 引入类路径(编译后以classes为跟的路径)下的资源
   *   <mapper url="file:///var/mappers/BlogMapper.xml"/>        -----> 入网络或磁盘路径下的资源
   *   <mapper class="org.mybatis.builder.BlogMapper"/>          -----> 引用(注册)接口: 1.有sql映射文件,映射文件名必须和接口同名,并且放在与接口同一目录下;2.没有sql映射文件,所有的sql都是基于注解写在接口上。
   *   <package name="org.mybatis.builder"/>                     -----> 扫描包下所有的引用接口
   * </mappers>
   * @param parent
   * @throws Exception
   */
  private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        /**
         *   如果要同时使用package自动扫描和通过mapper明确指定要加载的mapper,
         *   一定要确保package自动扫描的范围不包含明确指定的mapper,否则在通过
         *   package扫描的interface的时候,尝试加载对应xml文件的loadXmlResource()
         *   的逻辑中出现判重出错,报org.apache.ibatis.binding.BindingException异常,
         *   即使xml文件中包含的内容和mapper接口中包含的语句不重复也会出错,
         *   包括加载mapper接口时自动加载的xml mapper也一样会出错。
         */
        //如果配置包扫描
        if ("package".equals(child.getName())) {
          //获取需要扫描的包路径
          String mapperPackage = child.getStringAttribute("name");
          //解析包信息, 注册该包下的Mappers
          configuration.addMappers(mapperPackage);
        } else {
          //获取resource,url,mapperClass属性的值
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");

          //resource属性解析
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();

          } else if (resource == null && url != null && mapperClass == null) {
            //url属性解析
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();

          } else if (resource == null && url == null && mapperClass != null) {
            //mapperClass属性解析
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

分析: 根据<mappers> 中配置资源的方式的不同采用不同的解析方案, 这里主要看resource方式的解析

XMLMapperBuilder#parse()方法实现:

代码语言:javascript复制
public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      /**
       * 解析mapper.xml中的<mapper></mapper>标签
       */
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      /**
       * 根据接口创建MapperProxyFactory工厂
       */
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

分析:

  1. 解析mapper.xml中的<mapper></mapper>标签
  2. 根据接口创建MapperProxyFactory工厂, 用于创建mapper接口的代理对象

XMLMapperBuilder#configurationElement()方法实现:

代码语言:javascript复制
private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      //设置名称空间
      builderAssistant.setCurrentNamespace(namespace);
      //开始对mapper.xml中各个标签进行解析
      /**
       * 1. 解析缓存映射<cache-ref></cache-ref>
       */
      cacheRefElement(context.evalNode("cache-ref"));
      /**
       * 2. 解析缓存<cache></cache>
       */
      cacheElement(context.evalNode("cache"));
      /**
       * 3. 解析参数映射<parameterMap></parameterMap>
       */
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      /**
       * 4. 解析结果集映射<resultMap></resultMap>
       */
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      /**
       * 5. 解析<sql></sql>
       */
      sqlElement(context.evalNodes("/mapper/sql"));
      /**
       * 6. 解析CRUD语句<select></select> |<insert></insert> |<update></update> |<delete></delete>  (重点)
       */
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));

    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '"   resource   "'. Cause: "   e, e);
    }
  }

分析: 对<mapper>的各种子标签进行解析, 主要看CRUD语句标签<select></select> |<insert></insert> |<update></update> |<delete></delete> 的解析

XMLMapperBuilder#buildStatementFromContext(java.util.List<org.apache.ibatis.parsing.XNode>)方法实现:

代码语言:javascript复制
 private void buildStatementFromContext(List<XNode> list) {
    if (configuration.getDatabaseId() != null) {
      buildStatementFromContext(list, configuration.getDatabaseId());
    }
    buildStatementFromContext(list, null);
  }

  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    //遍历解析每条sql语句
    for (XNode context : list) {
      //用每个sql标签的上下文对象创建statementParser解析器
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        /**
         * 解析SQL节点
         */
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }

分析: 创建statementParser解析器, 调用parseStatementNode()对标签进行解析

XMLStatementBuilder#parseStatementNode()方法实现:

代码语言:javascript复制
 public void parseStatementNode() {
    /**
     *  context:
     *  <select id="getUserById" parameterType="int" resultType="com.lic.ibatis.entity.User">
     *       select * from user where id = #{id}
     *   </select>
     */
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");

    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }

    String nodeName = context.getNode().getNodeName();
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // Include Fragments before parsing
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);

    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);

    // Parse selectKey after includes and remove them.
    processSelectKeyNodes(id, parameterTypeClass, langDriver);

    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    KeyGenerator keyGenerator;
    String keyStatementId = id   SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    if (configuration.hasKeyGenerator(keyStatementId)) {
      keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
      keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
          configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
          ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }

    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    Integer fetchSize = context.getIntAttribute("fetchSize");
    Integer timeout = context.getIntAttribute("timeout");
    String parameterMap = context.getStringAttribute("parameterMap");
    String resultType = context.getStringAttribute("resultType");
    Class<?> resultTypeClass = resolveClass(resultType);
    String resultMap = context.getStringAttribute("resultMap");
    String resultSetType = context.getStringAttribute("resultSetType");
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
    if (resultSetTypeEnum == null) {
      resultSetTypeEnum = configuration.getDefaultResultSetType();
    }
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    String resultSets = context.getStringAttribute("resultSets");

    /**
     * 将解析内容封装到MappedStatement中
     */
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered,
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }

分析:

  1. 对之前解析好的各种属性进行解析配置
  2. 将所有的sql配置进行封装

MapperBuilderAssistant#addMappedStatement(. . . )方法实现:

代码语言:javascript复制
 public MappedStatement addMappedStatement(
      String id,
      SqlSource sqlSource,
      StatementType statementType,
      SqlCommandType sqlCommandType,
      Integer fetchSize,
      Integer timeout,
      String parameterMap,
      Class<?> parameterType,
      String resultMap,
      Class<?> resultType,
      ResultSetType resultSetType,
      boolean flushCache,
      boolean useCache,
      boolean resultOrdered,
      KeyGenerator keyGenerator,
      String keyProperty,
      String keyColumn,
      String databaseId,
      LanguageDriver lang,
      String resultSets) {

    if (unresolvedCacheRef) {
      throw new IncompleteElementException("Cache-ref not yet resolved");
    }
    //接口路径   方法名称
    id = applyCurrentNamespace(id, false);
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

    MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
        .resource(resource)
        .fetchSize(fetchSize)
        .timeout(timeout)
        .statementType(statementType)
        .keyGenerator(keyGenerator)
        .keyProperty(keyProperty)
        .keyColumn(keyColumn)
        .databaseId(databaseId)
        .lang(lang)
        .resultOrdered(resultOrdered)
        .resultSets(resultSets)
        .resultMaps(getStatementResultMaps(resultMap, resultType, id))
        .resultSetType(resultSetType)
        .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
        .useCache(valueOrDefault(useCache, isSelect))
        .cache(currentCache);  //二级缓存配置

    ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
    if (statementParameterMap != null) {
      statementBuilder.parameterMap(statementParameterMap);
    }

    MappedStatement statement = statementBuilder.build();
   /**
     * 将解析好的statement实例加入mappedStatements集合中
     */
    configuration.addMappedStatement(statement);
    return statement;
  }

Configuration#addMappedStatement()方法实现:

代码语言:javascript复制
 public void addMappedStatement(MappedStatement ms) {
    mappedStatements.put(ms.getId(), ms);
  }

分析: 将解析完成的MappedStatement实例加入mappedStatements集合中, key为接口路径 方法名称 value为该接口方法对应的MappedStatement实例;

3. 创建DefaultSqlSessionFactory对象

SqlSessionFactoryBuilder#build(org.apache.ibatis.session.Configuration)方法实现:

代码语言:javascript复制
public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }


public DefaultSqlSessionFactory(Configuration configuration) {
    this.configuration = configuration;
  }

二、SqlSession

SqlSession是MyBatis的关键对象, 是执行持久化操作的独享, 类似于JDBC中的Connection; 它是应用程序与持久层之间执行交互操作的一个单线程对象, 也是MyBatis执行持久化操作的关键对象; SqlSession对象完全包含以数据库为背景的所有执行SQL操作的方法, 它的底层封装了JDBC连接, 可以用SqlSession实例来直接执行被映射的SQL语句; 每个线程都应该有它自己的SqlSession实例; SqlSession的实例不能被共享, 同时SqlSession也是线程不安全的, 绝对不能讲SqlSeesion实例的引用放在一个类的静态字段甚至是实例字段中; 也绝不能将SqlSession实例的引用放在任何类型的管理范围中, 比如Servlet当中的HttpSession对象中; 使用完SqlSeesion之后关闭Session很重要, 应该确保使用finally块来关闭它.

SqlSession session = sqlSessionFactory.openSession()

DefaultSqlSessionFactory#openSession()方法实现:

代码语言:javascript复制
  @Override
  public SqlSession openSession() {
    // 使用默认的执行器类型(默认是SIMPLE),默认隔离级别,非自动提交 委托给openSessionFromDataSource方法
    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);
      // 从数据源创建一个事务, 同样,数据源必须配置, mybatis内置了JNDI、POOLED、UNPOOLED三种类型的数据源,
      // 其中POOLED对应的实现为org.apache.ibatis.datasource.pooled.PooledDataSource,它是mybatis自带实现的一个同步、
      // 线程安全的数据库连接池 一般在生产中,我们会使用dbcp或者druid连接池
      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();
    }
  }

DefaultSqlSession#DefaultSqlSession()方法的实现:

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

相关文章:

Mybatis源码解析一(SqlSessionFactory和SqlSession的获取)

Mybatis源码解析二(请求处理过程解析)

Mybatis源码解析三(模拟Mybatis)

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/139673.html原文链接:https://javaforall.cn

0 人点赞