MyBatis源码之:SqlSession(Factory)

1. SqlSessionFactory

java 复制代码
/**
 * 工厂类,主要负责创建SqlSession
 */
public interface SqlSessionFactory {

    /**
     * openSession()方法的参数用于指定生成的SqlSession的性质
     * 1. autoCommit:是否自动提交
     * 2. level:     事务的隔离级别
     * 3. execType:  底层执行器的类型,这个之后再详细说明
     */
    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对象
     * Configuration中包含MyBatis的各种配置,比如:是否开启驼峰命名(mapUnderscoreToCamelCase)等
     */
    Configuration getConfiguration();
}

SqlSessionFactory接口有两个实现类:SqlSessionManagerDefaultSqlSessionFactory

1.1. SqlSessionManager

java 复制代码
/**
 * SqlSession管理器;底层依赖另一个SqlSessionFactory来创建SqlSession
 */
public class SqlSessionManager implements SqlSessionFactory, SqlSession {
    private final SqlSessionFactory sqlSessionFactory;
    
    @Override
    public SqlSession openSession() {
        return sqlSessionFactory.openSession();
    }

    // 其它重载的openSession()方法和上面的openSession()方法类似,因此省略

    @Override
    public Configuration getConfiguration() {
        return sqlSessionFactory.getConfiguration();
    }
}

1.2. DefaultSqlSessionFactory

java 复制代码
/**
 * 默认的SqlSessionFactory;本类可以看作是SqlSessionFactory的唯一实现
 */
public class DefaultSqlSessionFactory implements SqlSessionFactory {

    /**
     * 底层维护一个Configuration对象,该对象需要在构造时指定
     */
    private final Configuration configuration;
    public DefaultSqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
    }

    /**
     * 获取environment底层的TransactionFactory;如果获取不到,则返回一个新的ManagedTransactionFactory
     */
    private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
        if (environment == null || environment.getTransactionFactory() == null) {
            return new ManagedTransactionFactory();
        }
        return environment.getTransactionFactory();
    }

    /**
     * 有6个openSession()方法的底层都是在调用本方法
     *
     * @param execType   执行器的类型;如果用户没有指定,则取默认值configuration.getDefaultExecutorType()
     * @param level      事务隔离级别;如果用户没有指定,则为null,此时采用数据库的默认隔离级别
     * @param autoCommit 是否自动提交;如果用户没有指定,则取默认值false
     */
    private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, 
            boolean autoCommit) {
        Transaction tx = null;
        try {
            // 通过Configuration获取到环境信息,并获取到其中的TransactionFactory
            final Environment environment = configuration.getEnvironment();
            final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);

            // 通过数据源来创建一个Transaction对象(即懒初始化的Connection对象)
            tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);

            // 通过Configuration来创建相应类型的执行器,并将该Transaction绑定到该执行器上
            // 由于Configuration中包含Mybatis拦截器的配置,因此Configuration在创建了执行器之后,还会用拦截器对其进行包装
            final Executor executor = configuration.newExecutor(tx, execType);

            // 创建DefaultSqlSession,并把执行器绑定到该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();
        }
    }

    /**
     * 另外2个openSession()方法底层都是在调用本方法;本方法和上面的方法类似
     */
    private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
        try {
            // 读取JDBC连接的autoCommit属性
            // 如果出现了异常,说明JDBC驱动或数据库不支持事务,此时认为autoCommit为true
            boolean autoCommit;
            try {
                autoCommit = connection.getAutoCommit();
            } catch (SQLException e) {
                autoCommit = true;
            }
            
            final Environment environment = configuration.getEnvironment();
            final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
            
            // 直接通过用户自己指定的JDBC连接来创建对应的Transaction对象
            final Transaction tx = transactionFactory.newTransaction(connection);
            
            // 创建相应类型的执行器,再创建DefaultSqlSession,并把执行器绑定到该SqlSession上
            final Executor executor = configuration.newExecutor(tx, execType);
            return new DefaultSqlSession(configuration, executor, autoCommit);
        
        } catch (Exception e) {
            throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
        } finally {
            ErrorContext.instance().reset();
        }
    }

    @Override
    public Configuration getConfiguration() {
        return configuration;
    }
}

1.3. 小结

  1. 一个DefaultSqlSession底层绑定了一个Executor
  2. 一个Executor底层绑定了一个Transaction
  3. Transaction其实就相当于原生的JDBC连接
  4. 因此,一个SqlSession就对应了一个原生的JDBC连接
text 复制代码
    +------------------------------------+
    |          DefaultSqlSession         |
    | +--------------------------------+ |
    | |            Executor            | |
    | | +----------------------------+ | |
    | | |         Transaction        | | |
    | | | +------------------------+ | | |
    | | | |       Connection       | | | |
    | | | +------------------------+ | | |
    | | +----------------------------+ | |
    | +--------------------------------+ |
    +------------------------------------+

2. SqlSession

java 复制代码
/**
 * 与MyBatis进行交互的主要接口,我们可以通过该接口来执行SQL命令、获取Mapper和管理事务
 */
public interface SqlSession extends Closeable {

    /**
     * 与CRUD相关的操作;部分参数的含义如下:
     * 1. statement:select等标签的id(比如"com.xxx.UserMapper.getUser"),而不是具体的SQL语句
     * 2. rowBounds:分页参数;如果指定了分页参数,则MyBatis在查询出所有数据后,会截取出属于当前页的数据
     * 3. handler:结果处理器;主要负责对解析到的实体类对象进行处理,比如将其保存到List集合中
     * 
     * 注意,selectCursor()方法返回的是结果集的游标
     * 游标相当于一个迭代器,每次获取下一个元素时,MyBatis就会读取结果集的下一行数据并将其封装成实体类对象
     * 因此,当查询的数据量比较大时,使用游标可以有效减少内存的占用(内存中不会出现大量的实体类对象)
     * 而且,当只需要在查询结果中获取到第一个符合要求的记录时,使用游标也可以减少不必要的结果集映射操作
     * 
     * 还要注意的是,select()方法竟然是没有返回值的
     * 这是因为select()方法允许用户自己处理实体类对象,因此要求用户自己传入一个ResultHandler实例
     * 由于最终的处理结果是存放在ResultHandler实例中的,且ResultHandler接口并没有定义获取最终结果的方法
     * 因此,就只能让用户自己通过ResultHandler实例来获取最终的处理结果了
     */
    <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);
    <T> Cursor<T> selectCursor(String statement);
    <T> Cursor<T> selectCursor(String statement, Object parameter);
    <T> Cursor<T> selectCursor(String statement, Object parameter, 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);

    /**
     * 我们知道,JDBC的Statement及其子类都可以执行批处理操作,因此SqlSession自然也需要支持批处理功能
     * SqlSession会将待执行executeBatch()方法的Statement放在一个集合中(注意,批处理语句只能是更新语句)
     * 当调用本方法时,SqlSession会逐个调用这些Statement的executeBatch()方法,并返回相应的执行结果
     * 注意,只有当SqlSession底层的执行器是BatchExecutor时,该方法才有意义;因为只有BatchExecutor才支持批处理操作
     */
    List<BatchResult> flushStatements();

    /**
     * 提交事务、回滚事务、关闭连接
     */
    void commit();
    void commit(boolean force);
    void rollback();
    void rollback(boolean force);
    void close();

    /**
     * 其它方法
     */
    void clearCache();
    Configuration getConfiguration();
    <T> T getMapper(Class<T> type);
    Connection getConnection();
}

SqlSession有3个实现类:DefaultSqlSessionSqlSessionManagerSqlSessionTemplate

其中的SqlSessionTemplate可以先不管,它是在Spring和MyBatis整合时才会用到的

3. SqlSessionManager

3.1. 简单方法

java 复制代码
/**
 * SqlSession管理器;负责为每个线程维护它们各自的SqlSession;作用类似于ThreadLocal
 */
public class SqlSessionManager implements SqlSessionFactory, SqlSession {

    /**
     * 底层真正干活的SqlSessionFactory;这个前面有说过,因此这里省略与它相关的代码
     */
    private final SqlSessionFactory sqlSessionFactory;

    // =======================================================================================

    /**
     * 底层真正干活的SqlSession;注意它是个代理,并且只代理CRUD操作;代理的具体逻辑等一下再说
     */
    private final SqlSession sqlSessionProxy;

    /**
     * 所有CRUD方法底层都是在调用sqlSessionProxy的相应方法;这里贴出selectOne()方法作为示例
     */
    @Override
    public <T> T selectOne(String statement) {
        return sqlSessionProxy.<T>selectOne(statement);
    }
    
    // =======================================================================================
    
    /**
     * 私有的构造方法,用于初始化底层的sqlSessionFactory和sqlSessionProxy字段
     * 此外,本类有多个静态的重载的newInstance()工厂方法,其底层都是在调用本构造方法,因此也省略
     */
    private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
        this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
                SqlSessionFactory.class.getClassLoader(),
                new Class[]{SqlSession.class},
                new SqlSessionInterceptor());  // 代理的增强逻辑都在SqlSessionInterceptor类中
    }

    // =======================================================================================

    /**
     * 保存各个线程各自对应的SqlSession
     */
    private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<SqlSession>();

    /**
     * 为当前线程创建一个SqlSession,并将其存到ThreadLocal中
     * 该方法还有其它重载的方法,功能是一样的,因此省略
     */
    public void startManagedSession() {
        this.localSqlSession.set(openSession());
    }

    /**
     * 判断当前线程是否调用过startManagedSession()方法且未关闭它的SqlSession
     */
    public boolean isManagedSessionStarted() {
        return this.localSqlSession.get() != null;
    }

    @Override
    public <T> T getMapper(Class<T> type) {
        return getConfiguration().getMapper(type, this);
    }
}

3.2. SqlSession代理

java 复制代码
public class SqlSessionManager implements SqlSessionFactory, SqlSession {
    
    /**
     * 接下来就可以来看SqlSession代理的具体增强逻辑了;再说明一下,这里只代理SqlSession的CRUD操作
     */
    private class SqlSessionInterceptor implements InvocationHandler {
        public SqlSessionInterceptor() {
            // Prevent Synthetic Access
        }

        /**
         * 拦截目标方法(即CRUD方法)
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            
            // 拿到当前线程对应的SqlSession
            final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();
            
            // 如果当前线程确实开启了SqlSession,则调用该SqlSession对应的CRUD方法
            if (sqlSession != null) {
                try {
                    return method.invoke(sqlSession, args);
                } catch (Throwable t) {
                    throw ExceptionUtil.unwrapThrowable(t);
                }
            
            // 否则,先为当前线程创建一个临时的SqlSession,然后调用它对应的CRUD方法并提交,最后关闭该SqlSession
            } else {
                final SqlSession autoSqlSession = openSession();
                try {
                    final Object result = method.invoke(autoSqlSession, args);
                    autoSqlSession.commit();
                    return result;
                } catch (Throwable t) {
                    autoSqlSession.rollback();
                    throw ExceptionUtil.unwrapThrowable(t);
                } finally {
                    autoSqlSession.close();
                }
            }
        }
    }

    /**
     * 获取当前线程的SqlSession中的JDBC连接
     * 注意,以下方法的实现逻辑和本方法类似,因此这里就不贴出来了:
     * clearCache()、commit()、commit(force)、rollback()、rollback(force)、flushStatements()
     */
    @Override
    public Connection getConnection() {
        final SqlSession sqlSession = localSqlSession.get();
        if (sqlSession == null) {
            throw new SqlSessionException("Error:  Cannot get connection.  No managed session is started.");
        }
        return sqlSession.getConnection();
    }

    /**
     * 关闭当前线程的SqlSession,并将其从ThreadLocal中移除
     */
    @Override
    public void close() {
        final SqlSession sqlSession = localSqlSession.get();
        if (sqlSession == null) {
            throw new SqlSessionException("Error:  Cannot close.  No managed session is started.");
        }
        try {
            sqlSession.close();
        } finally {
            localSqlSession.set(null);
        }
    }
}

4. DefaultSqlSession

4.1. 简单方法

java 复制代码
/**
 * 默认的SqlSession;本类可以看作是SqlSession的唯一实现
 */
public class DefaultSqlSession implements SqlSession {
    private final Configuration configuration;
    private final Executor executor;    // 底层的执行器
    private final boolean autoCommit;   // 底层的JDBC连接是否为自动提交
    private boolean dirty;              // 当前是否有未提交的修改语句
    private List<Cursor<?>> cursorList; // 游标列表;selectCursor()方法返回的游标会被存放到该列表中

    public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
        this.configuration = configuration;
        this.executor = executor;
        this.dirty = false;
        this.autoCommit = autoCommit;
    }

    public DefaultSqlSession(Configuration configuration, Executor executor) {
        this(configuration, executor, false);
    }

    @Override
    public Configuration getConfiguration() {
        return configuration;
    }

    @Override
    public <T> T getMapper(Class<T> type) {
        return configuration.<T>getMapper(type, this);
    }

    /**
     * 判断是否需要提交/回滚;参数force代表是否求强制提交/回滚
     * 如果强制提交/回滚,则返回true;否则,如果不是自动提交且执行了update语句,则也返回true
     */
    private boolean isCommitOrRollbackRequired(boolean force) {
        return (!autoCommit && dirty) || force;
    }

    @Override
    public void commit() {
        commit(false);
    }

    /**
     * 提交;底层是在调用执行器的commit()方法
     */
    @Override
    public void commit(boolean force) {
        try {
            executor.commit(isCommitOrRollbackRequired(force));
            dirty = false; // 将dirty置为false
        } catch (Exception e) {
            throw ExceptionFactory.wrapException("Error committing transaction.  Cause: " + e, e);
        } finally {
            ErrorContext.instance().reset();
        }
    }

    @Override
    public void rollback() {
        rollback(false);
    }

    /**
     * 回滚;底层是在调用执行器的rollback()方法
     */
    @Override
    public void rollback(boolean force) {
        try {
            executor.rollback(isCommitOrRollbackRequired(force));
            dirty = false; // 将dirty置为false
        } catch (Exception e) {
            throw ExceptionFactory.wrapException("Error rolling back transaction.  Cause: " + e, e);
        } finally {
            ErrorContext.instance().reset();
        }
    }

    /**
     * 执行批处理语句;底层是在调用执行器的flushStatements()方法
     */
    @Override
    public List<BatchResult> flushStatements() {
        try {
            return executor.flushStatements();
        } catch (Exception e) {
            throw ExceptionFactory.wrapException("Error flushing statements.  Cause: " + e, e);
        } finally {
            ErrorContext.instance().reset();
        }
    }

    /**
     * 清除缓存;底层是在调用执行器的clearLocalCache()方法
     */
    @Override
    public void clearCache() {
        executor.clearLocalCache();
    }

    /**
     * 获取JDBC连接;这里返回的是执行器绑定的Transaction底层的JDBC连接
     */
    @Override
    public Connection getConnection() {
        try {
            return executor.getTransaction().getConnection();
        } catch (SQLException e) {
            throw ExceptionFactory.wrapException("Error getting a new connection.  Cause: " + e, e);
        }
    }
}

4.2. CRUD操作

java 复制代码
public class DefaultSqlSession implements SqlSession {
    
    /**
     * selectOne()方法和另外两个selectList()方法底层都是在调用本方法
     * 
     * @param statement select语句的id
     * @param parameter select语句的参数;如果用户未指定,则为null
     * @param rowBounds 分页信息;如果用户未指定,则为RowBounds.DEFAULT(即不分页)
     */
    @Override
    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        try {
            // 根据MappedStatement的id获取到MappedStatement,然后对用户传的参数进行包装,然后调用执行器的query()方法
            MappedStatement ms = configuration.getMappedStatement(statement);
            return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
        } catch (Exception e) {
            throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
        } finally {
            ErrorContext.instance().reset();
        }
    }

    /**
     * 另外两个selectMap()方法底层都是在调用本方法
     */
    @Override
    public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
        
        // 这里也是在调用selectList()方法来进行查询
        final List<? extends V> list = selectList(statement, parameter, rowBounds);
        
        // 将查询出来的List集合封装成Map集合;这里涉及到DefaultMapResultHandler和DefaultResultContext组件,先不用管
        final DefaultMapResultHandler<K, V> mapResultHandler = new DefaultMapResultHandler<K, V>(mapKey,
                configuration.getObjectFactory(), configuration.getObjectWrapperFactory(),
                configuration.getReflectorFactory());
        final DefaultResultContext<V> context = new DefaultResultContext<V>();
        for (V o : list) {
            context.nextResultObject(o);
            mapResultHandler.handleResult(context);
        }
        return mapResultHandler.getMappedResults();
    }

    /**
     * 另外两个selectCursor()方法底层都是在调用本方法
     */
    @Override
    public <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds) {
        try {
            // 调用执行器的queryCursor()方法获取游标,并把游标保存到this.cursorList中,方便管理
            MappedStatement ms = configuration.getMappedStatement(statement);
            Cursor<T> cursor = executor.queryCursor(ms, wrapCollection(parameter), rowBounds);
            registerCursor(cursor);
            return cursor;
        } catch (Exception e) {
            throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
        } finally {
            ErrorContext.instance().reset();
        }
    }

    /**
     * 另外两个select()方法底层都是在调用本方法
     */
    @Override
    public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
        try {
            // 调用执行器的query()方法;这里和selectList()方法的不同之处在于后者的结果处理器是Executor.NO_RESULT_HANDLER
            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();
        }
    }

    /**
     * 所有更新数据库的方法(增删改)底层都是在调用本方法
     * 
     * @param statement 更新语句的id
     * @param parameter 更新语句的参数;如果用户未指定,则为null
     */
    @Override
    public int update(String statement, Object parameter) {
        try {
            // 将dirty置为true,然后调用执行器的update()方法
            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();
        }
    }

    /**
     * 关闭SqlSession;底层会调用执行器的close()方法,并且将注册的游标也关闭掉
     */
    @Override
    public void close() {
        try {
            executor.close(isCommitOrRollbackRequired(false));
            closeCursors();
            dirty = false;
        } finally {
            ErrorContext.instance().reset();
        }
    }

    /**
     * 对集合/数组类型的参数进行封装,这是因为执行器无法直接处理集合/数组类型的参数
     */
    private Object wrapCollection(final Object object) {
        
        // 如果是集合类型,则以"collection"作为key,集合本身作为value存入Map中,然后返回该Map
        // 特别地,如果同时是List类型,则还会以"list"作为key,集合本身作为value存入Map中
        // StrictMap继承自HashMap,其get()方法会先判断键是否存在,不存在则抛异常;否则调用父类的get()方法
        if (object instanceof Collection) {
            StrictMap<Object> map = new StrictMap<>();
            map.put("collection", object);
            if (object instanceof List) {
                map.put("list", object);
            }
            return map;

        // 如果是数组类型,则以"array"作为key,数组本身作为value存入Map中,然后返回该Map
        } else if (object != null && object.getClass().isArray()) {
            StrictMap<Object> map = new StrictMap<>();
            map.put("array", object);
            return map;
        }
        
        // 否则,说明object为null,或者是简单类型,或者是实体类型,或者本身已经是Map类型了,此时无需包装
        return object;
    }
}

4.3. 小结

  1. DefaultSqlSession中的方法基本上都是在调用Executor的相应方法
  2. 因此,SqlSession其实就是在对Executor进行封装,方便用户使用
相关推荐
吾日三省吾码4 小时前
JVM 性能调优
java
弗拉唐5 小时前
springBoot,mp,ssm整合案例
java·spring boot·mybatis
oi776 小时前
使用itextpdf进行pdf模版填充中文文本时部分字不显示问题
java·服务器
少说多做3436 小时前
Android 不同情况下使用 runOnUiThread
android·java
知兀6 小时前
Java的方法、基本和引用数据类型
java·笔记·黑马程序员
蓝黑20206 小时前
IntelliJ IDEA常用快捷键
java·ide·intellij-idea
Ysjt | 深6 小时前
C++多线程编程入门教程(优质版)
java·开发语言·jvm·c++
凌冰_6 小时前
IDEA2023 SpringBoot整合MyBatis(三)
spring boot·后端·mybatis
shuangrenlong7 小时前
slice介绍slice查看器
java·ubuntu