Mybatis源码分析(二)- SqlSessionFactory和SqlSession详解

本系列以Mybatis 3.3.X分支源码作为分析源,mybatis源码Git地址:https://github.com/mybatis/mybatis-3.git

 SqlSessionFactory 作为Mybatis的应用入口,主要提供了各种获取SqlSession实例的方法。SqlSessionFactory在大部分情况下,建议在系统中生成唯一实例(读写分离、多库连接等除外)。

SqlSession主要提供了常用的数据库增删查改操作。

一、 SqlSessionFactory类

  SqlSessionFactory主要定义了几个重载的openSession方法,其主要实现为org.apache.ibatis.session.defaults.DefaultSqlSessionFactory。

首先描述下SqlSessionFactory的所有方法涉及到的参数:

booleaautoCommit主要为connection.setAutoCommit(autoCommit)的参数,表示是否自动提交

Connection connection:数据库连接

TransactionIsolationLevel level:事务隔离级别,其为enum参数,主要定义了五种事务隔离级别与Connection中五种隔离级别一一对应。这里简要描述下各个级别的含义:

TransactionIsolationLevel.NONE 表示不支持事务

TransactionIsolationLevel.READ_UNCOMMITTED 表示可能发生脏读、不可重复读和幻读, 属于限制性最弱的隔离级别,并发性较高

TransactionIsolationLevel.READ_COMMITTED  表示可能发生不可重复读和幻读。SqlServer和Oracle默认采用此隔离级别

TransactionIsolationLevel.REPEATABLE_READ 表示可能发生幻读。Mysql默认采用此隔离级别

TransactionIsolationLevel.SERIALIZABLE 最高的事务隔离级别,通过强制对事务排序来避免幻读。缺点是在大并发下容易导致大量超时和锁竞争

下面给出脏读,不可重复读和幻读的基本描述:

脏读:指事务读取了其他事务未提交的数据。比如一个事务正在访问并修改数据,此修改还未提交到数据库中,此时另外一个事务需要访问此数据,正好读取到修改后的数据并使用,此时第一个事务因为某些原因进行回滚,最终导致数据的不正确性。

不可重复读:指同一个事务内,多次读取同一数据得到不同的结果。比如当前一个事务在一开始读取了数据并执行其他逻辑操作,此时另外一个事务修改了此数据,在第一个事务内再次读取数据时,发现结果与第一次不一致。

幻读:与不可重复读类似,主要指一个事务操作一组数据时,其他事务新增或者删除了该组部分数据,导致第一个事务读取时发现与想要的结果不一致,出现幻读现象。比如一个事务获取表中标志位为0的数据有100条,然后更新所有数据的标志位为1,此时其他事务添加了一条标志位为0的数据并提交,第一个事务再读取标志位为0的数据时发现还有1条。

ExecutorType execType:定义执行类型的枚举,进入源码可以看到仅定义了SIMPLE, REUSE, BATCH三种类型。其中SIMPLE表示每个语句的执行会创建一个新的预处理器;REUSE表示复用预处理器;BATCH表示执行器批量执行更新语句

不带参数的openSession是大部分情况下的使用模式,默认采用配置文件的执行器,采用数据库默认事务隔离级别且关闭自动提交。

分析了参数后,再看看DefaultSqlSessionFactory默认不带参数的openSession是如何使用的:

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

不带参数的openSession是大部分情况下的使用模式,默认采用配置文件的执行器,采用数据库默认事务隔离级别且关闭自动提交。

DefaultSessionFactory主要实现逻辑有两个方法openSessionFromDataSource和openSessionFromConnection,从方法名和参数可以看出,前者主要采用配置的dataSource获取数据库连接,后者为人工设置数据库连接。因为两者实现类同,这里描述下openSessionFromDataSource的实现。

1 获取事务对象

 final Environment environment = configuration.getEnvironment();
 //获取事务工厂,优先从Environment中获取,不存在则新建ManagedTransactionFactory实例
 final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
 //获取事务对象
 tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);

事务对象共有两种JdbcTransaction和ManagedTransaction,二者的区别详见mybatis官方文档的描述:


  • JDBC – 这个配置就是直接使用了 JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务范围。
  • MANAGED – 这个配置几乎没做什么。它从来不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接,然而一些容器并不希望这样,因此需要将 closeConnection 属性设置为 false 来阻止它默认的关闭行为。
2 获取执行器

 //通过ExecutorType和事务构建执行器
 final Executor executor = configuration.newExecutor(tx, execType);
其主要调用了Configuration的newExecutor方法实现:
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    //如果没有传递执行器类型,则采用默认的类型,默认为SIMPLE类型
    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;
  }
执行器类型在之前已经描述过,共有 SIMPLE, REUSE, BATCH三种。这里注意下如果设置为cacheEnabled,则会将执行器包装生成CachingExecutor。CachingExecutor通过代理原有执行器,实现缓存的处理逻辑,后续会针对缓存进行单独的博文描述。

3 生成SqlSession实例

主要利用Configuration,Executor和autoCommit构造DefaultSqlSession对象

return new DefaultSqlSession(configuration, executor, autoCommit);


二、 SqlSession类

SqlSession主要定义了常用的数据库访问方法主要分为select、update两大类,其主要实现为org.apache.ibatis.session.defaults.DefaultSqlSession。SqlSession主要利用Executor执行逻辑,其本身的实现相对比较简单,Executor将在下一篇分析说明。

首先描述下DefaultSqlSession的属性定义:

private Configuration configuration;
private Executor executor;
private boolean autoCommit;
private boolean dirty;

其中:前三者属性已经多次了解过,configuration表示配置信息,executor表示执行器,autoCommit表示是否自动提交。dirty参数主要用来标记当前的提交或者回滚是否需要数据库执行,仅当非自动提交下,dirty=true时,在调用commit和rollback时会执行数据库的commit和rollback。

1 select方法体描述

DefaultSqlSession提供了常用的查询操作,包括selectOne、selectList、selectMap、select几个重载方法。

selectOne方法主要通过调用selectList实现数据库的查询操作,其源码如下所示:

public <T> T selectOne(String statement, Object parameter) {
    // Popular vote was to return null on 0 results and throw exception on too many.
    List<T> list = this.<T>selectList(statement, parameter);
    if (list.size() == 1) {
      return list.get(0);
    } else if (list.size() > 1) {
      throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
      return null;
    }
  }
从源码可以看出,selectOne通过调用selectList实现查询,并对返回值进行判断处理,如果返回数据为1,则认为查询成功;返回数据大于1,则抛出TooManyResultsException异常;没有查询到数据则返回NULL

selectList、selectMap和select方法的实现都大同小异,select主要多了ResultHandler参数,从名称就可以看出为结果回调处理方法。这里仅以select作为简要分析,其源码如下所示:

@Override
  public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      executor.query(ms, wrapCollection(parameter), rowBounds, handler);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

第一步通过statement的id获取到相应的Statement对象,其中MapperdStatement包括了本次执行请求的许多重要信息,将在后续文章中一一描述。

第二步调用执行器执行实际的查询操作,需要注意下select操作因为传递handler,其返回值为void,而其他三类查询方法直接返回查询结果。

2 update方法体

update方法体系包括insert、delete、update方法。其中insert、delete都是通过调用update方法来完成实际的更新操作。update方法实现源码如下所示:

@Override
  public int update(String statement, Object parameter) {
    try {
      dirty = true;
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.update(ms, wrapCollection(parameter));
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
第一步将dirty置为true,便于后续的数据库commit和rollback

第二步获取实际的statement对象

第三步调用执行器的update方法执行实际的更新操作

SqlSession实现相对比较简单,其核心的Executor将会在下一篇文章中详细描述。






  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值