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进行封装,方便用户使用
相关推荐
架构文摘JGWZ3 小时前
Java 23 的12 个新特性!!
java·开发语言·学习
拾光师4 小时前
spring获取当前request
java·后端·spring
aPurpleBerry4 小时前
neo4j安装启动教程+对应的jdk配置
java·neo4j
我是苏苏4 小时前
Web开发:ABP框架2——入门级别的增删改查Demo
java·开发语言
xujinwei_gingko4 小时前
Spring IOC容器Bean对象管理-Java Config方式
java·spring
2301_789985944 小时前
Java语言程序设计基础篇_编程练习题*18.29(某个目录下的文件数目)
java·开发语言·学习
IT学长编程4 小时前
计算机毕业设计 教师科研信息管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解
java·毕业设计·springboot·毕业论文·计算机毕业设计选题·计算机毕业设计开题报告·教师科研管理系统
m0_571957584 小时前
Java | Leetcode Java题解之第406题根据身高重建队列
java·leetcode·题解
程序猿小D4 小时前
第二百三十五节 JPA教程 - JPA Lob列示例
java·数据库·windows·oracle·jdk·jpa
Java小白笔记5 小时前
关于使用Mybatis-Plus 自动填充功能失效问题
spring boot·后端·mybatis