SqlSession和SqlSessionFactory

mysql

MyBatis的持久化解决方案是将用户从原始的 JDBC 访问中解放出来,用户只需定义需要操作的 SQL 语句,无须关注底层的 DJBC 操作,就可以以面向对象的方式来进行持久化操作,底层数据库连接的获取,数据访问的实现,事务控制等无须用户关心。

从而将应用从底层的 JDBC/JTA 的 API抽取出来,通过配置文件管理 JDBC 连接,让 MyBatis 解决持久化的实现,在 MyBatis 中的常见对象有 SqlSessionFactory 和 SqlSession。

SqlSessionFactory

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

SqlSessionFactory接口源码如下所示:

package org.apache.ibatis.session; import java.sql.Connection; public interface SqlSessionFactory { // 这个方法最经常用,用来创建SqlSession对象. SqlSession openSession(); SqlSession openSession(boolean autoCommit); SqlSession openSession(Connection connection); SqlSession openSession(TransactionIsolationLevel level); SqlSession openSession(ExecutorType execType); SqlSession openSession(ExecutorType execType, boolean autoCommit); SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level); SqlSession openSession(ExecutorType execType, Connection connection); Configuration getConfiguration(); }

SqlSession

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

SqlSession接口源码如下所示:

package org.apache.ibatis.session; import java.io.Closeable; import java.sql.Connection; import java.util.List; import java.util.Map; import org.apache.ibatis.executor.BatchResult; public interface SqlSession extends Closeable { <T> T selectOne(String var1); <T> T selectOne(String var1, Object var2); <E> List<E> selectList(String var1); <E> List<E> selectList(String var1, Object var2); <E> List<E> selectList(String var1, Object var2, RowBounds var3); <K, V> Map<K, V> selectMap(String var1, String var2); <K, V> Map<K, V> selectMap(String var1, Object var2, String var3); <K, V> Map<K, V> selectMap(String var1, Object var2, String var3, RowBounds var4); void select(String var1, Object var2, ResultHandler var3); void select(String var1, ResultHandler var2); void select(String var1, Object var2, RowBounds var3, ResultHandler var4); int insert(String var1); int insert(String var1, Object var2); int update(String var1); int update(String var1, Object var2); int delete(String var1); int delete(String var1, Object var2); void commit(); void commit(boolean var1); void rollback(); void rollback(boolean var1); List<BatchResult> flushStatements(); void close(); void clearCache(); Configuration getConfiguration(); <T> T getMapper(Class<T> var1); Connection getConnection(); }

SqlSessionFactory 和 SqlSession 实现过程

myBatis 框架主要是围绕着 SqlSessionFactory 进行的,创建过程大概如下:

  • 1)、定义一个 Configuration 对象,其中包含数据源、事务、mapper 文件资源以及影响数据库行为属性设置 settings
  • 2)、通过配置对象,则可以创建一个 SqlSessionFactoryBuilder 对象
  • 3)、通过 SqlSessionFactoryBuilder 获得 SqlSessionFactory 的实例
  • 4)、SqlSessionFactory 的实例可以获得操作数据的 SqlSession 实例,通过这个实例对数据库进行操作

如果是 spring 和 myBatis 整合之后的配置文件,一般以这种方式实现 SqlSessionFactory 的创建:

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!-- 注入数据库连接池 --> <property name="dataSource" ref="dataSource"/> <!-- 配置MyBatis全局配置文件: mybatis-config.xml --> <property name="configLocation" value="classpath:mybatis-config.xml"/> <!-- 扫描entity包 使用别名 --> <property name="typeAliasesPackage" value="com.isyxf.entity"/> <!-- 扫描sql配置文件:mapper需要的xml文件 --> <property name="mapperLocations" value="classpath:mapper/*.xml"/> </bean>

SqlSessionFactory 和 SqlSession 两个对象具体使用过程:

package com.cn.testIUserService; import java.io.IOException; import java.io.InputStream; 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 com.cn.entity.User; public class MyBatisTest { public static void main(String[] args) { try { //读取mybatis-config.xml文件 InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml"); //初始化mybatis,创建SqlSessionFactory类的实例 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); //创建session实例 SqlSession session = sqlSessionFactory.openSession(); /* * 接下来在这里做很多事情,到目前为止,目的已经达到得到了SqlSession对象.通过调用SqlSession里面的方法, * 可以测试MyBatis和Dao层接口方法之间的正确性,当然也可以做别的很多事情,在这里就不列举了 */ //插入数据 User user = new User(); user.setC_password("123"); user.setC_username("123"); user.setC_salt("123"); //第一个参数为方法的完全限定名:位置信息+映射文件当中的id session.insert("com.cn.dao.UserMapping.insertUserInformation", user); //提交事务 session.commit(); //关闭session session.close(); } catch (IOException e) { e.printStackTrace(); } } }

针对上面的代码给出详细关于 SqlSessionFactory 和 SqlSession 创建结构图
23

结合上述 SqlSessionFactory 和 SqlSession 使用过程和结构图涉及到的方法总结为以下步骤

第一步首先 SqlSessionFactoryBuilder 去读取 Mybatis 的配置文件,然后 build 一个 DefaultSqlSessionFactory, 即得到 SqlSessionFactory

//源码中涉及的包和具体方法为: //涉及的包为:package org.apache.ibatis.session; //第一个类为:SqlSessionFactoryBuilder,设计到此类的方法为下面部分: public SqlSessionFactory build(InputStream inputStream) { return build(inputStream, null, null); } public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { //通过XMLConfigBuilder解析配置文件,解析的配置相关信息都会封装为一个Configuration对象 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); //然后返回一个DefaultSqlSessionFactory return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { inputStream.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } } } //得到DefaultSqlSessionFactory public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); } //第二个类为:DefaultSqlSessionFactory,涉及的方法为: public DefaultSqlSessionFactory(Configuration configuration) { this.configuration = configuration; }

第二步,获取到 SqlSessionFactory 之后,就可以利用 SqlSessionFactory 方法的 openSession 来获取 SqlSession 对象了。

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { //通过Confuguration对象去获取Mybatis相关配置信息, Environment对象包含了数据源和事务的配置 // execType为执行器类型,配置文件中定义 // SimpleExecutor -- SIMPLE 就是普通的执行器。 //ReuseExecutor -执行器会重用预处理语句(prepared statements) //BatchExecutor --它是批量执行器 final Environment environment = configuration.getEnvironment(); final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); //定义执行器,是对statement的封装 final Executor executor = configuration.newExecutor(tx, execType); //最后返回一个SqlSession 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(); } }

得到 SqlSession 对象之后就可以利用 SqlSession 内部的方法进行 CRUD 操作了。

需要注意一点,Connection 对象是在 SqlSession 对象创建之后进行 CURD 操作中创建的,在 ManagedTransaction 类中找到了获取 Connection 对象的关键代码:

protected void openConnection() throws SQLException { if (log.isDebugEnabled()) { log.debug("Opening JDBC Connection"); } //dataSource 来源有三种,JndiDatasource,PooledDataSource,UnpooledDataSource,配置文件中定义 this.connection = this.dataSource.getConnection(); if (this.level != null) { this.connection.setTransactionIsolation(this.level.getLevel()); } }

PooledDataSource 和 UnPooleDataSource 的区别是 PooledDataSource 使用了连接池,为什么是连接池呐?因为创建一个 Connection 对象的过程,在底层就相当于和数据库建立通信连接,在建立通信连接的过程消耗了非常多的时间,而往往我们建立连接后(即城创建 Connection 对象后),就执行一个简单的SQL语句,然后就要抛弃掉,这是一个非常大的资源浪费!MyBatis 针对这个问题提出了 PooledDataSource 使用了连接池。

参考文章

MyBatis常用对象SqlSessionFactory和SqlSession介绍和运用