Mybatis源码阅读(五 ):接口层——SqlSession

SqlSession

SqlSession是Mybatis的核心接口之一,对外提供常用的数据库操作api。mybatis提供了两个SqlSession的实现,其中最常用的是DefaultSqlSession。

SqlSession的代码如下

/**
 * 接口层,也是开发人员使用mybatis去操作sql所使用的主要的接口
 *
 * @author Clinton Begin
 */
public interface SqlSession extends Closeable {

    /**
     * 查询sql单条数据
     *
     * @param <T>       返回的数据类型
     * @param statement sql
     * @return Mapped object
     */
    <T> T selectOne(String statement);

    /**
     * 指定sql并传入实参去查单条数据
     *
     * @param <T>       返回的数据类型
     * @param statement 预编译的带有?的sql
     * @param parameter 用户传入的实参,与前面sql绑定
     * @return Mapped object
     */
    <T> T selectOne(String statement, Object parameter);

    /**
     * 执行sql查询多条数据
     *
     * @param <E>       返回的数据类型
     * @param statement sql
     * @return List of mapped object
     */
    <E> List<E> selectList(String statement);

    /**
     * 指定sql并传入实参去查多条数据
     *
     * @param <E>       返回的数据类型
     * @param statement 预编译的带有问号的sql
     * @param parameter 用户传入的实参,与前面的sql绑定
     * @return List of mapped object
     */
    <E> List<E> selectList(String statement, Object parameter);

    /**
     * 使用预编译的sql,指定传入的实参以及结果集范围
     * 查询指定范围的所有数据
     *
     * @param <E>       返回的数据类型
     * @param statement 预编译的带有问号的sql
     * @param parameter 用户传入的实参,与前面的sql绑定
     * @param rowBounds 指定查询范围
     * @return List of mapped object
     */
    <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);

    /**
     * 执行sql,返回map对象
     *
     * @param <K>       the returned Map keys type
     * @param <V>       the returned Map values type
     * @param statement Unique identifier matching the statement to use.
     * @param mapKey    The property to use as key for each value in the list.
     * @return Map containing key pair data.
     */
    <K, V> Map<K, V> selectMap(String statement, String mapKey);

    /**
     * 指定sql和实参,返回map
     *
     * @param <K>       the returned Map keys type
     * @param <V>       the returned Map values type
     * @param statement Unique identifier matching the statement to use.
     * @param parameter A parameter object to pass to the statement.
     * @param mapKey    The property to use as key for each value in the list.
     * @return Map containing key pair data.
     */
    <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);

    /**
     * 指定sql、实参、范围,返回map
     *
     * @param <K>       the returned Map keys type
     * @param <V>       the returned Map values type
     * @param statement Unique identifier matching the statement to use.
     * @param parameter A parameter object to pass to the statement.
     * @param mapKey    The property to use as key for each value in the list.
     * @param rowBounds Bounds to limit object retrieval
     * @return Map containing key pair data.
     */
    <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);

    /**
     * A Cursor offers the same results as a List, except it fetches data lazily using an Iterator.
     *
     * @param <T>       the returned cursor element type.
     * @param statement Unique identifier matching the statement to use.
     * @return Cursor of mapped objects
     */
    <T> Cursor<T> selectCursor(String statement);

    /**
     * A Cursor offers the same results as a List, except it fetches data lazily using an Iterator.
     *
     * @param <T>       the returned cursor element type.
     * @param statement Unique identifier matching the statement to use.
     * @param parameter A parameter object to pass to the statement.
     * @return Cursor of mapped objects
     */
    <T> Cursor<T> selectCursor(String statement, Object parameter);

    /**
     * A Cursor offers the same results as a List, except it fetches data lazily using an Iterator.
     *
     * @param <T>       the returned cursor element type.
     * @param statement Unique identifier matching the statement to use.
     * @param parameter A parameter object to pass to the statement.
     * @param rowBounds Bounds to limit object retrieval
     * @return Cursor of mapped objects
     */
    <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds);

    /**
     * 将查询结果通过此处的ResultHandler对象封装成对应的对象
     *
     * @param statement Unique identifier matching the statement to use.
     * @param parameter A parameter object to pass to the statement.
     * @param handler   ResultHandler that will handle each retrieved row
     */
    void select(String statement, Object parameter, ResultHandler handler);

    /**
     * Retrieve a single row mapped from the statement
     * using a {@code ResultHandler}.
     *
     * @param statement Unique identifier matching the statement to use.
     * @param handler   ResultHandler that will handle each retrieved row
     */
    void select(String statement, ResultHandler handler);

    /**
     * Retrieve a single row mapped from the statement key and parameter
     * using a {@code ResultHandler} and {@code RowBounds}.
     *
     * @param statement Unique identifier matching the statement to use.
     * @param rowBounds RowBound instance to limit the query results
     * @param handler   ResultHandler that will handle each retrieved row
     */
    void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);

    /**
     * 执行insert
     *
     * @param statement Unique identifier matching the statement to execute.
     * @return int The number of rows affected by the insert.
     */
    int insert(String statement);

    /**
     * Execute an insert statement with the given parameter object. Any generated
     * autoincrement values or selectKey entries will modify the given parameter
     * object properties. Only the number of rows affected will be returned.
     *
     * @param statement Unique identifier matching the statement to execute.
     * @param parameter A parameter object to pass to the statement.
     * @return int The number of rows affected by the insert.
     */
    int insert(String statement, Object parameter);

    /**
     * 执行update
     *
     * @param statement Unique identifier matching the statement to execute.
     * @return int The number of rows affected by the update.
     */
    int update(String statement);

    /**
     * Execute an update statement. The number of rows affected will be returned.
     *
     * @param statement Unique identifier matching the statement to execute.
     * @param parameter A parameter object to pass to the statement.
     * @return int The number of rows affected by the update.
     */
    int update(String statement, Object parameter);

    /**
     * 执行delete
     *
     * @param statement Unique identifier matching the statement to execute.
     * @return int The number of rows affected by the delete.
     */
    int delete(String statement);

    /**
     * Execute a delete statement. The number of rows affected will be returned.
     *
     * @param statement Unique identifier matching the statement to execute.
     * @param parameter A parameter object to pass to the statement.
     * @return int The number of rows affected by the delete.
     */
    int delete(String statement, Object parameter);

    /**
     * 提交事务
     */
    void commit();

    /**
     * Flushes batch statements and commits database connection.
     *
     * @param force forces connection commit
     */
    void commit(boolean force);

    /**
     * 回滚事务
     */
    void rollback();

    /**
     * Discards pending batch statements and rolls database connection back.
     * Note that database connection will not be rolled back if no updates/deletes/inserts were called.
     *
     * @param force forces connection rollback
     */
    void rollback(boolean force);

    /**
     * 将请求刷新到数据库
     *
     * @return BatchResult list of updated records
     * @since 3.0.6
     */
    List<BatchResult> flushStatements();

    /**
     * 关闭SqlSession
     */
    @Override
    void close();

    /**
     * 清空 缓存
     */
    void clearCache();

    /**
     * Retrieves current configuration.
     *
     * @return Configuration
     */
    Configuration getConfiguration();

    /**
     * 使用type获取对应的Mapper
     *
     * @param <T>  the mapper type
     * @param type Mapper interface class
     * @return a mapper bound to this SqlSession
     */
    <T> T getMapper(Class<T> type);

    /**
     * 获取该SqlSession对应的数据库连接
     *
     * @return Connection
     */
    Connection getConnection();
}

DefaultSqlSession

在mybatis单独使用的时候,DefaultSqlSession是最常使用的SqlSession实现。DefaultSqlSession核心字段如下,其中已经过多介绍的类将不再注释。


    private final Configuration configuration;
    private final Executor executor;

    /**
     * 是否自动提交事务
     */
    private final boolean autoCommit;

    /**
     * 当前缓存是否有脏数据
     */
    private boolean dirty;

DefaultSqlSession中使用到了策略模式(不知道策略模式的请看我以前的帖子)。DefaultSqlSession扮演了上下文,只是通过executor字段的不同,而选择不同的Executor去操作数据库。

DefaultSqlSession为每种SQL操作都提供了大量的重载,对于不同的参数都提供了一个重载方法, 便于开发者去调用。这里只贴出核心的方法,对于重载方法将不进行介绍。

     * 根据sql和实参查询一条数据
     * @param statement 预编译的带有?的sql
     * @param parameter 用户传入的实参,与前面sql绑定
     * @param <T>
     * @return
     */
    @Override
    public <T> T selectOne(String statement, Object parameter) {
        // 调用selectList查询多条
        List<T> list = this.selectList(statement, parameter);
        // 如果查询到的数据长度1是或者0就正常,否则抛出异常
        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;
        }
    }

    /**
     * 查询结果封装成map返回
     * 阅读源码发现,这里的selectMap貌似并不是将结果集按照属性映射成map
     * 而是把map当做list去使用。
     * 查询出多条数据,使用不同的key去封装到map
     * 这里的V应该是这一条数据映射的对象,或者是Map<String, Object>
     * @param statement Unique identifier matching the statement to use.
     * @param parameter A parameter object to pass to the statement.
     * @param mapKey    The property to use as key for each value in the list.
     * @param rowBounds Bounds to limit object retrieval
     * @param <K>
     * @param <V>
     * @return
     */
    @Override
    public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
        // 查询列表
        final List<? extends V> list = selectList(statement, parameter, rowBounds);
        // 创建Map返回集处理器
        final DefaultMapResultHandler<K, V> mapResultHandler = new DefaultMapResultHandler<>(mapKey,
                configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory());
        final DefaultResultContext<V> context = new DefaultResultContext<>();
        for (V o : list) {
            // 暂存一下当前结果对象
            context.nextResultObject(o);
            // 处理上下文中的结果对象
            mapResultHandler.handleResult(context);
        }
        // 将map返回回去
        return mapResultHandler.getMappedResults();
    }

    /**
     * 根据传入的sql、实参、查询范围去查询一个列表
     * @param statement 预编译的带有问号的sql
     * @param parameter 用户传入的实参,与前面的sql绑定
     * @param rowBounds 指定查询范围
     * @param <E>
     * @return
     */
    @Override
    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        try {
            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();
        }
    }

    /**
     * 根据sql、实参、范围查询
     * 将查询结果交给指定的ResultHandler去处理
     * @param statement Unique identifier matching the statement to use.
     * @param parameter
     * @param rowBounds RowBound instance to limit the query results
     * @param handler   ResultHandler that will handle each retrieved row
     */
    @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();
        }
    }

    @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();
        }
    }

    @Override
    public void commit(boolean force) {
        try {
            // 提交事务。提交之后将dirty设为false
            // 此时的缓存中视为没有脏数据
            executor.commit(isCommitOrRollbackRequired(force));
            dirty = false;
        } catch (Exception e) {
            throw ExceptionFactory.wrapException("Error committing transaction.  Cause: " + e, e);
        } finally {
            ErrorContext.instance().reset();
        }
    }

    @Override
    public void rollback(boolean force) {
        try {
            executor.rollback(isCommitOrRollbackRequired(force));
            dirty = false;
        } catch (Exception e) {
            throw ExceptionFactory.wrapException("Error rolling back transaction.  Cause: " + e, e);
        } finally {
            ErrorContext.instance().reset();
        }
    }

    @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();
        }
    }

代码比较简单,就不做过多的介绍。

DefaultSqlSessionFactory

DefaultSqlSessionFactory是一个工厂类,提供了两种创建DefaultSqlSession的方式,一种是通过数据源创建SqlSession,一种是通过用户传入的数据库连接对象来创建SqlSession。另外代码里有大量的openSession都是用于创建SqlSession对象的,但是其实现都是基于这两种方式,因此这里只把两种创建SqlSession的方式的代码贴出来,如下。

    /**
     * 通过数据源去创建SqlSession
     * @param execType
     * @param level
     * @param autoCommit
     * @return
     */
    private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;
        try {
            // 获取environment。这个是mybatis中配置的环境
            final Environment environment = configuration.getEnvironment();
            // 根据环境去获取TransactionFactory对象
            final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
            // 创建Transaction对象
            tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
            // 创建Executor对象
            final Executor executor = configuration.newExecutor(tx, execType);
            // 创建DefaultSqlSession
            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();
        }
    }

    /**
     * 通过用户提供的Connection对象去创建
     * @param execType
     * @param connection
     * @return
     */
    private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
        try {
            boolean autoCommit;
            try {
                autoCommit = connection.getAutoCommit();
            } catch (SQLException e) {
                // Failover to true, as most poor drivers
                // or databases won't support transactions
                autoCommit = true;
            }
            final Environment environment = configuration.getEnvironment();
            final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
            final Transaction tx = transactionFactory.newTransaction(connection);
            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();
        }
    }

SqlSessionManager

SqlSessionManager同时实现了SqlSession接口和SQLSessionFactory接口,因此它拥有操作数据库的能力以及创建SqlSession的功能。

SQLSessionManager核心字段如下

    private final SqlSessionFactory sqlSessionFactory;

    /**
     * localSqlSession中记录的SqlSession对象的代理对象
     */
    private final SqlSession sqlSessionProxy;

    /**
     * 记录当前线程的SqlSession对象
     */
    private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<>();

其中ThreadLocal的作用往往是作为当前线程的上下文,可以为当前线程提供全局变量。对ThreadLocal不了解的朋友也请查看我以前的文章。

SqlSessionManager提供了两种模式。一种是同一线程每次通过SqlSessionManager对象访问数据库时,都会创建一个DefaultSqlSession对象完成数据库操作,另一种则是使用localSqlSession绑定当前线程的SqlSession,在当前线程中循环使用同一个SqlSession。后者使用往往居多,这也是大家经常说的“SqlSession与线程绑定 ,每个请求都会创建SqlSession”的原因。

sqlSessionProxy是一个代理对象,在SqlSessionmanager的构造方法中使用JDK的动态代理创建完成,代码如下。

    private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
        // 使用动态代理去创建 SqlSession
        this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
                SqlSessionFactory.class.getClassLoader(),
                new Class[]{SqlSession.class},
                new SqlSessionInterceptor());
    }

SqlSessionManager中实现的SqlSession 接口方法,都是直接调用sqlSessionProxy字段记录的SqlSession代理对象的方法实现的。在创建该代理对象时使用到的SqlSessionInterceptor是SqlSessionManager的内部类,代码如下。

    private class SqlSessionInterceptor implements InvocationHandler {
        public SqlSessionInterceptor() {
            // Prevent Synthetic Access
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // 获取当前线程的SqlSession
            final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();
            if (sqlSession != null) {
                try {
                    // SqlSession不为空就调用真正的SqlSession去完成数据库的操作
                    return method.invoke(sqlSession, args);
                } catch (Throwable t) {
                    throw ExceptionUtil.unwrapThrowable(t);
                }
            } else {
                try (SqlSession autoSqlSession = openSession()) {
                    // 如果当前线程的SqlSession为空,就创建新的SqlSession对象
                    try {
                        // 使用创建的SqlSession对象完成数据库操作
                        final Object result = method.invoke(autoSqlSession, args);
                        // 提交事务
                        autoSqlSession.commit();
                        return result;
                    } catch (Throwable t) {
                        autoSqlSession.rollback();
                        throw ExceptionUtil.unwrapThrowable(t);
                    }
                }
            }
        }
    }

总结

SqlSession是单体mybatis使用最多的一个接口,可能我们在整合SSM之后就看不到这个接口了,但是其底层实现的时候也是会创建SqlSession的,虽然这个比较简单,但是也是相当重要的一个模块。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值