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

来源:互联网 发布:网络剧营销创意 编辑:程序博客网 时间:2024/05/18 07:53

本系列以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 0
原创粉丝点击