Mybatis源码解析以及和Spring整合后的变化

MyBatis源码解析

先看看不整合Spring,mybatis原生的基本用法:

        //1.第一步获取SqlSessionFactory工厂
        String resource = "conf.xml";
        //使用类加载器加载mybatis的配置文件(它也加载关联的映射文件)
        InputStream is = Test1.class.getClassLoader().getResourceAsStream(resource);
        //构建sqlSession的工厂
        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(is);


        //第二步:创建能执行映射文件中sql的sqlSession
        SqlSession session = sessionFactory.openSession();

        //第三步:获取mapper的代理类
        UserMapper mapper = session.getMapper(UserMapper.class);


        //第四步: 执行查询返回一个唯一user对象的sql
        User user = mapper.selectUser(1);

再来看看整合了Spring后,mybatis的用法

@Autowire
UserMapper userMapper;

public User selectUser(int id){
    User user = userMapper.selectUser(1);
    return user;
}

接下来具体开始源码分析了:

1.1 获取SqlSessionFactory工厂

 public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
        SqlSessionFactory var5;
        try {
            // 用xmlConfigBuilder读取配置文件
            XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
            var5 = this.build(parser.parse());
        } 
        return var5;
    }

其中的关键处 XMLConfigBuilder .parse()

/*
这个方法把mybatis的所有xml都解析成Configuration对象,其中 每一个mapper.xml 解析放在MapperRgistry中。
每一个mapper.xml中的 <select>|<update>等节点都解析成MappedStatement对象存放在Configuration.mappedStatements中。
MappedStatement对象是执行真正查询所需要的重要对象。
*/
public Configuration parse() {
        if (this.parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        } else {
            this.parsed = true;
            this.parseConfiguration(this.parser.evalNode("/configuration"));
            return this.configuration;
        }
    }

得到Configuration对象,再通过 SqlSessionFactoryBuilder().build(Configuration); 就得到了SqlSessionFactory对象了。

在整合Spring之后,这一步在Spring容器初始化的时候,就会进行,并生成SqlSessionFactory实例放在Ioc容器中。

1.2 获取会话SqlSession(相当于JDBC的Connection)

sqlSessionFactory.openSession(); 实际是调用了openSessionFromDataSource方法

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;
        DefaultSqlSession var8;
        try {
            // 根据Configuration获取环境
            Environment environment = this.configuration.getEnvironment();
            TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
            // 创建一个事务 这里面是一个sqlsession就创建一个事务,且是自动提交的。
            tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
            // 创建一个Executor,将事务和Executor绑定到一起。
            Executor executor = this.configuration.newExecutor(tx, execType);
            var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
        } 
        return var8;
    }

最重要的是Executor对象在获取SqlSession的时候同时也创建了,且作为构造器参数,创建了SqlSession对象。这说明一个SqlSession对象对应一个Executor。
接下来看Executor的初始化过程

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
        // 根据Executor的类型配置来选择创建哪一种Executor,默认是 SimpleExecutor
        Object executor;
        if (ExecutorType.BATCH == executorType) {
            executor = new BatchExecutor(this, transaction);
        } else if (ExecutorType.REUSE == executorType) {
            executor = new ReuseExecutor(this, transaction);
        } else {
            executor = new SimpleExecutor(this, transaction);
        }
    // 如果全局配置中开启了二级缓存,就将上面的SimpleExecutor再包装一层。
        if (this.cacheEnabled) {
            executor = new CachingExecutor((Executor)executor);
        }
        // 插件拦截器将其再包装一次。
        Executor executor = (Executor)this.interceptorChain.pluginAll(executor);
        return executor;
    }

这段代码中药注意的是,Executor根据全局配置生成的不同类型,因为执行操作是Executor来完成的,不同的Executor查询方法不同。且Executor最后还被插件拦截器再包装了一次。

在整合Spring后,这一步不会再显示调用,而是会根据使用条件来调用,具体什么条件后面运行那一步会具体讲到的

1.2.1 两种查询的对比

如果是SimpleExecutor(没有开启二级缓存)查询的方法如下:

SimpleExecutor(BaseExecutor)查询 一级缓存

//这里只是简单提一下一级缓存的问题
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        .....
            List list;
            try {
                ++this.queryStack;
                // 从一级缓存中获取,如果有,那么直接返回。
                list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
                if (list != null) {
                    this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
                } else {
                // 如果没有那去数据库中查询
                    list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
                }
            } 
            ......
    }

CachingExecutor查询 二级缓存

public class CachingExecutor implements Executor {
    // delegate 相当于SimpleExecutor ,CachingExecutor 只是对其做了一个包装。
    private Executor delegate;
    private TransactionalCacheManager tcm = new TransactionalCacheManager();
    .......
   // 二级缓存的查询方法
    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        // 获取二级缓存对象。
        Cache cache = ms.getCache();
        if (cache != null) {
            this.flushCacheIfRequired(ms);
            if (ms.isUseCache() && resultHandler == null) {
                this.ensureNoOutParams(ms, parameterObject, boundSql);
                // 从二级缓存中查询
                List<E> list = (List)this.tcm.getObject(cache, key);
                if (list == null) {
                    // 如果没有,再转到上面那个方法,即去一级缓存中获取。
                    list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                    this.tcm.putObject(cache, key, list);
                }

                return list;
            }
        }
        return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }
 }

二级缓存中的数据必须在SqlSession关闭后,由一级缓存中迁移过去,而不是开启了二级缓存,就会在查询到数据后,存入一级缓存,再存入二级缓存。
使用二级缓存,数据库映射的model必须序列化。

1.3 得到Mapper接口的代理对象

sqlSession.getMapper(xxMapper.class);

//实质还是调用了Configuration中的MapperRegistry获取Mapper
this.configuration.getMapper(type, this);  
this.mapperRegistry.getMapper(type, sqlSession);

 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
        .....
        //就是通过反射生成了一个xxxMapper对应的代理类 MapperProxy<T>
          return mapperProxyFactory.newInstance(sqlSession);
        .....
            }
        }
    }

从上面代码可以看出,所有的Mapper接口最后都生成MapperProxy,先具体来看一下MapperProxy的构成

public class MapperProxy<T> implements InvocationHandler, Serializable {
    private static final long serialVersionUID = -6424540398559729838L;
    private final SqlSession sqlSession;
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethod> methodCache;
    public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
        // 包含一个SqlSession
        this.sqlSession = sqlSession;
        // 相应的Mapper接口
        this.mapperInterface = mapperInterface;
        this.methodCache = methodCache;
    }
    ......
 }

从MapperProxy的构成可以看出MapperProxy,每一个Mapper接口都有对应的MapperProxy,但是重点是:包含SqlSession,说明每一个会话的MapperProxy都是不同的!!

在整合Spring后,这一步都已经在Spring IOC 初始化的时候,已经完成了,每一个Mapper接口都有一个对应的MaperProxy代理类放在Ioc容器中,供后面调用。 但是根据上面分析,每一个会话的MapperProxy都是不同的,即不可能做成单例形式。
Spring是如何解决这个问题的呢?
Spring在初始化生成MapperProxy代理类的时候,其中的SqlSession全部用一个单例类SqlSessionTemplate代替了。其中的妙处后面运行的时候会说到。

1.4 执行操作(select为例)

xxxMapper.selectUser(1);
在前面分析了,这个xxxMapper实际上已经是代理对象 MapperProxy

MapperProxy.invoke()

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            // 如果是Object的方法,如getClass() 之类的就直接放行。
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            }
            if (this.isDefaultMethod(method)) {
                return this.invokeDefaultMethod(proxy, method, args);
            }
        }
    // 将方法封装成mapperMethod对象
        MapperMethod mapperMethod = this.cachedMapperMethod(method);
        return mapperMethod.execute(this.sqlSession, args);
    }

mapperMethod.execute(this.sqlSession, args)

public Object execute(SqlSession sqlSession, Object[] args) {
        Object param;
        Object result;
        // 根据执行类型 <select> | <update> 等来区别处理
        switch(this.command.getType()) {
        case INSERT:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
            break;
        case UPDATE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
            break;
        case DELETE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
            break;
            // 查询类型
        case SELECT:
            // 根据返回值来处理。
            if (this.method.returnsVoid() && this.method.hasResultHandler()) {
                this.executeWithResultHandler(sqlSession, args);
                result = null;
            } else if (this.method.returnsMany()) {
                result = this.executeForMany(sqlSession, args);
            } else if (this.method.returnsMap()) {
                result = this.executeForMap(sqlSession, args);
            } else if (this.method.returnsCursor()) {
                result = this.executeForCursor(sqlSession, args);
            } else {
            // 封装参数
                param = this.method.convertArgsToSqlCommandParam(args);
                // 开始使用MapperProxy中的sqlSession开始执行了。
                result = sqlSession.selectOne(this.command.getName(), param);
            }
            break;
      ......
    }

DefaultSqlsession.selectOne(this.command.getName(), param)

public <T> T selectOne(String statement, Object parameter) {
      // 实际是调用selectList方法
        List<T> list = this.selectList(statement, parameter);
        if (list.size() == 1) {
            return list.get(0);
        }
  }

  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        List var5;
      // 获取到<select>节点代表的 MappedStatement 对象
            MappedStatement ms = this.configuration.getMappedStatement(statement);
            // 利用executor开始执行查询。
            var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
        } 
    .....
        return var5;
    }

this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
上面的代码这一行,会根据Executor的类型不同而有不同情况,前面也介绍了,两种缓存情况下的查询。但如果从两级缓存中都没有获取到,那么去数据库中查询的方法是
BaseExecutor.queryFromDatabase();

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);
        List list;
        try {
            // 获取结果
            list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
        } finally {
            this.localCache.removeObject(key);
        }
        // 将结果存到一级缓存中去。
        this.localCache.putObject(key, list);
    ....
        return list;
    }

上面的代码主要是说明查询到的数据会存储到一级缓存中去,真正执行继续看doQuery方法
doQuery方法是由BaseExecutor的实现类重写的,默认生成的是SimpleExecutor,就继续分析这里面的代码
SimpleExecutor.doQuery()

public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    // Jdbc的用来执行操作的Statement类
        Statement stmt = null;
        List var9;
        try {
            Configuration configuration = ms.getConfiguration();
            // 通过Configuration获取到StatementHandler对象
            StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            //通过StatementHandler 来预处理sql语句
            stmt = this.prepareStatement(handler, ms.getStatementLog());
            // 执行查询,实际是调用jdbc的statement.execute();
            var9 = handler.query(stmt, resultHandler);
        } finally {
            this.closeStatement(stmt);
        }
        return var9;
    }

运行到这里,整个执行过程就走完了。其中有几个细节需要讲一下:

1.4.1 四大插件相关对象

除了前面一个Executor,还有三个是StatementHandler、ParemeterHandler、ResultSetHandler
其中 ParemeterHandler、ResultSetHandler 都在StatementHandler创建的时候,一起创建并作为属性放到了StatementHandler中。

public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
        ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
        // 关注点
        parameterHandler = (ParameterHandler)this.interceptorChain.pluginAll(parameterHandler);
        return parameterHandler;
    }

public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) {
        ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
        // 关注点
        ResultSetHandler resultSetHandler = (ResultSetHandler)this.interceptorChain.pluginAll(resultSetHandler);
        return resultSetHandler;
    }

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 上面两个方法都会在 new RoutingStatementHandler() 构造方法中出现
        StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
        // 关注点
        StatementHandler statementHandler = (StatementHandler)this.interceptorChain.pluginAll(statementHandler);
        return statementHandler;
    }

可以看出三个关注点,都有同样的一行代码
this.interceptorChain.pluginAll(statementHandler); 前面Executor创建的时候也有。插件的原理,不是本文重点,就不细讲。

1.4.2 真正的执行对象statement是哪里来的?

Statement stmt = this.prepareStatement(handler, ms.getStatementLog());
具体看这个方法

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
       // 获取到Connenction,又是一个jdbc对象
        Connection connection = this.getConnection(statementLog);
        // 实际就是由connection.prepareStatement()得到的。
        Statement stmt = handler.prepare(connection, this.transaction.getTimeout());
        handler.parameterize(stmt);
        return stmt;
    }

通过上面的代码,可以看出Statement是由Connection来的,那Connection又是哪里来的呢?

protected Connection getConnection(Log statementLog) throws SQLException {
        // 通过事务得到的,前面提到了sqlSession和事务是绑定的
        Connection connection = this.transaction.getConnection();
        return statementLog.isDebugEnabled() ? ConnectionLogger.newInstance(connection, statementLog, this.queryStack) : connection;
    }

至此,已经明白了,Mybatis中获取SqlSession的同时,获取到事务,事务中含有代表数据源的DataSource对象,Connenction对象就是通过dataSource.openConnection()获取到的。这里涉及到事务相关,这里不细讲,因为在和Spring整合后,事务由Spring来管理。

1.4.3 Spring整合后,这一步的变化

在最前面的代码中也可以看出执行操作这一步,Spring整合后,依然会有,所以整合后的运行流程和分析的并没有什么变化。唯一不同的是,前面提到了MapperProxy中Sqlsession被SqlSessionTemplate 取代了。

这导致的就是:
执行过程中的 MappedMethod.execute() 中的 sqlSession.selectOne() 方法不再是接下来的步骤了,而中间插了一段动态代理产生的切面

先来看SqlSessionTemplate的构成

public class SqlSessionTemplate implements SqlSession, DisposableBean {
    private final SqlSessionFactory sqlSessionFactory;
    private final ExecutorType executorType;
    private final SqlSession sqlSessionProxy;
    private final PersistenceExceptionTranslator exceptionTranslator;

    public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
        Assert.notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
        Assert.notNull(executorType, "Property 'executorType' is required");
        this.sqlSessionFactory = sqlSessionFactory;
        this.executorType = executorType;
        this.exceptionTranslator = exceptionTranslator;
        // sqlSessionProxy 是 SqlSession的代理类,
        // 其中具体切面在new SqlSessionTemplate.SqlSessionInterceptor() 中
        this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor());
    }

    public <T> T selectOne(String statement) {
        // 如果是sqlSessionTemplate调用selectOne,实质是它里面的属性sqlSessionProxy调用
        return this.sqlSessionProxy.selectOne(statement);
    }
}

从上面的代码中可以看出 关键的切面代码在 new SqlSessionTemplate.SqlSessionInterceptor() 中,来继续看这个类的invoke方法

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
           // 先执行SqlSessionUtils.getSqlSession()获取SqlSession,再去执行sqlSession中真正的selectOne()
            SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
            Object unwrapped;
            try {
                Object result = method.invoke(sqlSession, args);
               .....
                unwrapped = result;
            } 
            return unwrapped;
        }

SqlSessionUtils.getSqlSession()

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
        // 从当前线程中获取是否有SqlSession,TransactionSynchronizationManager中都是ThreadLocal对象
        SqlSessionHolder holder = (SqlSessionHolder)TransactionSynchronizationManager.getResource(sessionFactory);
        SqlSession session = sessionHolder(executorType, holder);
        if (session != null) {
            return session;
        } else {
            // 如果没有,就用到了openSession()方法
            session = sessionFactory.openSession(executorType);
            registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
            return session;
        }
    }

到这里,应该明白了,为什么可以用一个SqlSessionTemplate来作为MapperProxy中的SqlSession,因为实际操作用到的SqlSession,是在运行时获取到的。

1.5 多线程下事务和SqlSession的处理机制

Spring中对于多线程的处理,贯彻了两句话:
1.绑定到线程上的资源和事务的生命周期与线程并不是等长的,且线程生命周期与资源和事务没有必然关系
2.资源和事务的生命周期是等长的

SqlSession并不是多线程安全的,因此每一个线程的会话都应该是线程相关的,为了达到这个目的,通常是用ThreadLocal来实现的。SqlSession一般伴随着事务一起。因此,会把事务和资源SqlSession绑定到一起,通过ThreadLocal和线程绑定,但并不意味着它们和线程的生命周期一样。

前面提到了,事务管理通常由Spring来实现,Spring主要是通过注解 @Transactional ,放在方法上,以声明这个方法是需要事务的。
在运行的时候,如果碰到这个方法,根据不同的事务传播机制,进行不同的处理,这里假设,在这之前并没有事务,因此会出现下列几步:

  • 当前线程执行到带@Transactional注解的方法,且之前并没有事务,通过Spring aop 机制会向TransactionManage中的数据源申请一个资源(SqlSession),不同的数据层框架资源表现形式不一样,hibernate就是Session。

  • 成功获取到资源后,开启一个事务,和资源一起通过ThreadLocal绑定到当前线程中去。

  • 在前面看到 SqlSessionUtil.getSqlSession()中先从当前线程中获取,如果没有再去通过openSession()来获取SqlSession。通常如果方法加了事务注解,那么一定可以从当前线程中取得到。如果没加,那就会每一次查询都获取一次新的SqlSession.

/*
一级缓存失效问题
下面这个方法,没有加注解,因此每次查询的时候,都是用新的SqlSession,因此一级缓存就"失效";
但是如果userInfoMapper是通过原生的sqlSession.getMapper()得到的,那么一级缓存就有效了。
*/
public UserInfo getSecond(){
        UserInfo userInfo = userInfoMapper.selectByPrimaryKey((long)1);
        userInfo = userInfoMapper.selectByPrimaryKey(1L);
        return userInfo ;
    }
  • 事务aop是个环绕型的,因此方法执行完成后,会提交事务,并释放资源,即从当前线程中删去。事务和资源的生命周期到头了。

Spring事务传播机制:
主要说的是,一个带事务的方法去调用另一个带事务方法的处理机制。

Spring事务的隔离级别:
对数据库隔离级别的一个封装,如果配置了,以Spring隔离级别为准,实际用途不大,还是用数据库来做隔离级别吧。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值