Mybatis工作原理

1、首先通过 SqlSessionFactoryBuilder创建SqlSessionFactory工厂类,再通过SqlSessionFactory工厂类创建SqlSession对象。

  •  每个mybatis-config.xml对应一个SqlSessionFactory对象,也就是说如果有多套环境,每个环境创建一个SqlSessionFactory
  • SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,建议使用单例模式或者静态单例模式。
//sqlSessionFactory用工厂模式生产sqlSession
public class MybatisUtils {
 
    private static SqlSessionFactory sqlSessionFactory;
 
    static {
        //使用mybatis工具类的第一步——获取SqlSessionFactory对象
        try {
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 
    //既然有了 SqlSessionFactory,顾名思义,我们就可以从中获得 SqlSession 的实例了。
    // SqlSession 完全包 含了面向数据库执行 SQL 命令所需的所有方法。
    // 你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。
    public static SqlSession getSqlSession(){
        SqlSession sqlSession = sqlSessionFactory.openSession();
        return sqlSession;
    }
 
}

 2、SqlSession接口

//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 statement);

  <T> T selectOne(String statement, Object parameter);

  <E> List<E> selectList(String statement);

  <E> List<E> selectList(String statement, Object parameter);

  <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);

  <K, V> Map<K, V> selectMap(String statement, String mapKey);

  <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);

  <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);

  void select(String statement, Object parameter, ResultHandler handler);

  void select(String statement, ResultHandler handler);

  void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);

  int insert(String statement);

  int insert(String statement, Object parameter);

  int update(String statement);

  int update(String statement, Object parameter);

  int delete(String statement);

  int delete(String statement, Object parameter);

  void commit();

  void commit(boolean force);

  void rollback();

  void rollback(boolean force);

  List<BatchResult> flushStatements();

  void close();

  void clearCache();

  Configuration getConfiguration();

  <T> T getMapper(Class<T> type);

  Connection getConnection();
}
  • 正如其名,Sqlsession对应着一次数据库会话。
  • 它是应用程序与持久层之间执行交互操作的一个单线程对象,SqlSession对象完全包含以数据库为背景的所有执行SQL操作的方法,它的底层封装了JDBC连接,可以用SqlSession实例来直接执行被映射的SQL语句.
  • 由于数据库会话不是永久的,因此Sqlsession的生命周期也不应该是永久的,相反,在你每次访问数据库时都需要创建它(当然并不是说在Sqlsession里只能执行一次sql,你可以执行多次,当一旦关闭了Sqlsession就需要重新创建它)。
  • 创建Sqlsession的地方只有一个,那就是SqlsessionFactory的openSession方法 
    public SqlSession openSession() {
        return openSessionFromDataSource(configuration.getDefaultExecutorType(),null, false);
    }
  •  我们可以看到实际创建SqlSession的地方是openSessionFromDataSource,如下:
    private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevellevel, boolean autoCommit) {
 
        Connection connection = null;
 
        try {
 
            finalEnvironment environment = configuration.getEnvironment();
 
            final DataSourcedataSource = getDataSourceFromEnvironment(environment);
 
           TransactionFactory transactionFactory =getTransactionFactoryFromEnvironment(environment);
 
           connection = dataSource.getConnection();
 
            if (level != null) {
 
               connection.setTransactionIsolation(level.getLevel());
 
            }
 
           connection = wrapConnection(connection);
 
           Transaction tx = transactionFactory.newTransaction(connection,autoCommit);
 
            Executor executor = configuration.newExecutor(tx, execType);
 
            return newDefaultSqlSession(configuration, executor, autoCommit);
 
        } catch (Exceptione) {
 
           close Connection(connection);
 
            throwExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
 
        } finally {
 
           ErrorContext.instance().reset();
 
        }
 
    }

可以看出,创建sqlsession经过了以下几个主要步骤:

1)       从配置中获取Environment;

2)       从Environment中取得DataSource;

3)       从Environment中取得TransactionFactory;

4)       从DataSource里获取数据库连接对象Connection;

5)       在取得的数据库连接上创建事务对象Transaction;

6)       创建Executor对象(该对象非常重要,事实上sqlsession的所有操作都是通过它完成的);

7)       创建sqlsession对象。
 

3、Executor接口:创建Sqlsession时会创建Executor。Sqlsession对数据库的操作都是通过Executor来完成的,对sqlsession方法的访问最终都会落到executor的相应方法上去。

public interface Executor {
 
 
 
  ResultHandler NO_RESULT_HANDLER = null;
 
 
 
  int update(MappedStatement ms, Object parameter) throws SQLException;
 
 
 
  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
 
 
 
  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
 
 
 
  List<BatchResult> flushStatements() throws SQLException;
 
 
 
  void commit(booleanrequired) throws SQLException;
 
 
 
  void rollback(booleanrequired) throws SQLException;
 
 
 
  CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);
 
 
 
  boolean isCached(MappedStatement ms, CacheKey key);
 
 
 
  void clearLocalCache();
 
 
 
  void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);
 
 
 
  Transaction getTransaction();
 
 
 
  void close(booleanforceRollback);
 
 
 
  boolean isClosed();
 
 
 
  void setExecutorWrapper(Executor executor);
 
 
 
}
  • Executor接口实现类 :Mybatis默认的Executor是SimpleExecutor

 Executor主要完成以下几项内容:

  1. 处理缓存,包括一级缓存和二级缓存
  2. 获取数据库连接
  3. 创建Statement或者PrepareStatement对象
  4. 访问数据库执行SQL语句
  5. 处理数据库返回结果

下面就不得不提到mybatis的一级缓存和二级缓存

        Mybatis中有一级缓存和二级缓存,默认情况下一级缓存是开启的,而且是不能关闭的。二级缓存默认关闭。

        一级缓存是指 SqlSession 级别的缓存,当在同一个 SqlSession 中进行相同的 SQL 语句查询时,第二次以后的查询不会从数据库查询,而是直接从缓存中获取,一级缓存最多缓存 1024 条 SQL。

        二级缓存是指可以跨 SqlSession 的缓存。是 mapper 级别的缓存,对于 mapper 级别的缓存不同的sqlsession 是可以共享的。

  • 一级缓存的作用域是一个sqlsession内;
  • 二级缓存作用域是针对mapper进行缓存;

一级缓存

public class CacheTest extends BaseMapperTest {
    @Test
    public void testL1Cache(){
        //获取SqlSession
        SqlSession sqlSession = getSqlSession();
        SysUser user1 = null;
        try {
            //获取UserMapper接口
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            //调用selectById方法
            user1 = userMapper.selectById(1L);
            //对当前获取的对象重新赋值
            user1.setUserName("New Name");
            //再次查询获取id相同的用户
            SysUser user2 = userMapper.selectById(1L);
            //虽然没有更新数据库,但是user1和user2的名字相同
            Assert.assertEquals("New Name",user2.getUserName());
            //无论如何user1和user2是同一个实例
            Assert.assertEquals(user1,user2);
        }finally {
            sqlSession.close();
        }
        System.out.println("开启新的SqlSession");
        //开启一个新的sqlsession
        sqlSession = getSqlSession();
        try {
            //获取UserMapper接口
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            SysUser user2 = userMapper.selectById(1L);
            Assert.assertNotEquals("New Name",user2.getUserName());
            //这里的user2和前一个session查询的结果是两个不同的实例
            Assert.assertNotEquals(user1,user2);
            //执行删除操作
            userMapper.deleteById(2L);
            //获取user3
            SysUser user3 = userMapper.selectById(1L);
            //这里的user2和user3是两个不同的实例
            Assert.assertNotEquals(user2,user3);
        }finally {
            sqlSession.close();
        }
    }
}

        运行结果如下,在第一个try语句块中,我们写了两次查询数据库的语句,但是打印出的sql只有一条,证明第二条没有查询数据库,而是查询了缓存,并且user1和user2是同一个实例,先从数据库中获取user1的值,user1重新对userName赋值,第二次查询user2发现user2的userName是user1修改后的值。
        Mybatis一级缓存存在于SqlSession的生命周期中,在同一个SqlSession中查询时,Mybatis会把执行的方法和参数通过算法生成缓存的键值,将键值和查询结果放到一个Map中,如果同一个SqlSession中执行的方法和参数完全一致,那么通过算法生成相同的键值,当Map缓存对象中已经 存在该键值时,则会返回缓存中的对象.
        第二个try语句块中我们重新获取了一个新的sqlsession,查询结果显示,user2和第一个语句块中的user1没有任何关系,当我们执行删除操作后,用同一个sqlsession执行相同的查询,结果赋值给user3,结果表示user2和user3是不同的实例,原因是因为所有的insert,update,delete操作都会清空一级缓存。

不同的sqlsession

 @Test
public void differSqlSession() {
    SqlSession sqlSession = null;
    SqlSession sqlSession2 = null;
    try {
        sqlSession = sqlSessionFactory.openSession();

        StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
        // 执行第一次查询
        List<Student> students = studentMapper.selectAll();
        for (int i = 0; i < students.size(); i++) {
            System.out.println(students.get(i));
        }
        System.out.println("=============开始不同 Sqlsession 的第二次查询============");
        // 从新创建一个 sqlSession2 进行第二次查询
        sqlSession2 = sqlSessionFactory.openSession();
        StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
        List<Student> stus = studentMapper2.selectAll();
        // 不相等
        Assert.assertNotEquals(students, stus);
        for (int i = 0; i < stus.size(); i++) {
            System.out.println("stus:" + stus.get(i));
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        if (sqlSession != null) {
            sqlSession.close();
        }
        if (sqlSession2 != null) {
            sqlSession2.close();
        }
    }
}

二级缓存

      在Mybatis的全局配置settings中有一个参数cacheEnabled,这个参数是二级缓存的全局开关.默认为true,所以可以不配置,如果将其设置为false,则在后边所有缓存的配置都不起作用了。

        mybatis中的二级缓存是mapper级别的缓存

        值得注意的是,不同的mapper都有一个二级缓存,也就是说,不同的mapper之间的二级缓存是互不影响的。为了更加清楚的描述二级缓存,先来看一个示意图:

  •  sqlSession1去查询用户id为1的用户信息,查询到用户信息会将查询数据存储到该UserMapper的二级缓存中。
  • 如果SqlSession3去执行相同 mapper下sql,执行commit提交,则会清空该UserMapper下二级缓存区域的数据。
  • sqlSession2去查询用户id为1的用户信息,去缓存中找是否存在数据,如果存在直接从缓存中取出数据。
     

CachingExecutor

        CachingExecutor用于处理二级缓存,如果缓存中不存在要查询的数据,那么将查询请求委托给其他的Executor。如果是执行SQL的增删改,那么CachingExecutor将清空二级缓存。

 BaseExecutor

        BaseExecutor是除CachingExecutor之外,其他Executor实现类的基类。该类主要处理一级缓存。

        当调用该类的查询方法时,先查看一级缓存中是否已经有数据,如果有则直接从缓存获取,如果没有调用子类的查询方法从数据库中获取。

SimpleExecutor 

        SimpleExecutor继承自BaseExecutor,该类比较简单。
        当执行增删改查时,该类获取数据库连接,创建PrepareStatement或者Statement对象,执行SQL语句,最后将数据库返回结果转化为设定的对象。下面以doQuery方法为例:

  //父类执行查询时调用该方法
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
       //创建StatementHandler对象
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      //获得数据库连接,创建Statement或者PrepareStatement
      stmt = prepareStatement(handler, ms.getStatementLog());
      //执行SQL语句,将数据库返回结果转化为设定的对象,比如List,Map或者是POJO
      return handler.<E>query(stmt, resultHandler);
    } finally {
      //关闭Statement对象
      closeStatement(stmt);
    }
  }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值