Mybatis源码分析十四之MyBatis-Spring整合

一、MyBatis-Spring

MyBatis-Spring会将Mybatis无缝的接入到Spring容器中,也就是可以正常的使用Spring来管理Mybatis。先看一个整合demo。

@Configuration
public class Configure {
  @Bean
  public SqlSessionFactory sqlSessionFactory() throws Exception {
    SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
    factoryBean.setDataSource(new MyDataSource());
    return factoryBean.getObject();
  }

  @Bean
  public MapperFactoryBean mapperFactoryBean() throws Exception {
    MapperFactoryBean bean=new MapperFactoryBean();
    bean.setMapperInterface(UserMapper.class);
    bean.setSqlSessionFactory(sqlSessionFactory());
    return bean;
  }
  public static void main(String[] args) {
    AnnotationConfigApplicationContext a=new AnnotationConfigApplicationContext(Configure.class);
    UserMapper u= (UserMapper) a.getBean(UserMapper.class);
    ;
    System.out.println(u.getUser("1"));
  }
}

以上是以注解的方式去整合Spring和Mybatis,非常的简单,其中有两个重要的信息就是SqlSessionFactoryBean 和MapperFactoryBean ,前者是获取到SqlSessionFactory 对象,后者返回接口的代理对象,所以先按照这个思路进行分析。

二、SqlSessionFactoryBean

他实现了FactoryBean< SqlSessionFactory>, InitializingBean, ApplicationListener< ApplicationEvent> 接口,并且都会实现对应的方法,首先afterPropertiesSet方法会被调用,这个方法通过调用buildSqlSessionFactory()方法来实例化SqlSessionFactory 对象。
buildSqlSessionFactory()比较多,大致逻辑如下:

  1. 定义好XMLConfigBuilder xmlConfigBuilder和Configuration targetConfiguration局部变量
  2. 判断是否已经有了Configuration, 如果存在,赋值给targetConfiguration
  3. 如果还没有Configuration,继续判断是否设置了configLocation,也就是mybatis的配置文件,存在就实例化XMLConfigBuilder 来获取Configuration,否则走4
  4. 直接new一个Configuration对象
  5. 设置基本上属性,以及环境对象
  6. 如果xmlConfigBuilder不为空,调用其parse()方法
  7. 是否配置了mapperLocations属性,也就是mapper映射文件,配置了就通过XMLMapperBuilder解析映射文件,逻辑和mybatis一样
  8. 通过sqlSessionFactoryBuilder对象返回SqlSessionFactory,这和mybatis中的逻辑是一样的

所以SqlSessionFactoryBean会通过afterPropertiesSet实例化SqlSessionFactory对象,然后通过getObject() 方法返回,需要注意的是我们要传入对应的数据源,此时我们可以通过spring容器来获取SqlSessionFactoryBean实例,然后通过这个实例得到对应的SqlSessionFactory对象。

三、MapperFactoryBean

第二部分,我们可以通过spring来得到SqlSessionFactory,但是SqlSession以及Mapper接口都还没有通过spring容器来管理,这部分工作就是MapperFactoryBean所作的工作。
先看MapperFactoryBean的继承关系:

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T>{}

继承自SqlSessionDaoSupport ,实现了FactoryBean接口。第一部分的demo中我们需要设置对应的mapper接口和SqlSessionFactory对象。从结构来看一个mapper接口对应一个MapperFactoryBean对象。我们先分析setSqlSessionFactory方法,实现在父类中。

    public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
        if(this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
        //创建一个sqlSessionTemplate实例,先不用管这个对象
            this.sqlSessionTemplate = this.createSqlSessionTemplate(sqlSessionFactory);
        }
    }

我们继续查看SqlSessionDaoSupport 的父类DaoSupport,他实现了InitializingBean接口,所以会先执行afterPropertiesSet方法。

    public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
       //检查配置类 
        this.checkDaoConfig();
        try {
        //默认是空方法
            this.initDao();
        } catch (Exception var2) {
            throw new BeanInitializationException("Initialization of DAO failed", var2);
        }
    }

checkDaoConfig方法的实现在子类MapperFactoryBean中。

    protected void checkDaoConfig() {
        super.checkDaoConfig();
        Assert.notNull(this.mapperInterface, "Property 'mapperInterface' is required");
        Configuration configuration = this.getSqlSession().getConfiguration();
        if(this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
            try {
            //调用配置类对象的addMapper方法
                configuration.addMapper(this.mapperInterface);
            } catch (Exception var6) {
                this.logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", var6);
                throw new IllegalArgumentException(var6);
            } finally {
                ErrorContext.instance().reset();
            }
        }

    }

此时Spring容器中保存的是MapperFactoryBean对象,他是一个FactoryBean实现,如果要获取本身对象只能是需要加上“&”,所以本例中要拿到UserMapper对象,会从MapperFactoryBean的getObject方法中获取。

    public T getObject() throws Exception {
        return this.getSqlSession().getMapper(this.mapperInterface);
    }

getSqlSession()方法返回的是SqlSessionTemplate实例,是SqlSession的一个实现,其getMapper方法和DefaultSqlSession中的getMapper方法是一样的调用逻辑。MapperFactoryBean每次只能添加一个接口,这不利于我们的开发,所以我们需要批量设置的时候可以使用MapperScannerConfigurer只需配置好SqlSessionFactory和一个basePackage即可。

四、SqlSessionTemplate

这部分分析一下MyBatis-Spring中SqlSession的实现,SqlSessionTemplate的创建是在MapperFactoryBean实例调用其setSqlSessionFactory方法时创建。

    new SqlSessionTemplate(sqlSessionFactory);

其中有个重要属性sqlSessionProxy,SqlSession中的增删改成方法的调用都是通过sqlSessionProxy来实现的,他是一个动态代理对象,主要代理逻辑在SqlSessionInterceptor的invoke方法。从源码中可以知道,他的提交、回滚、关闭方法都是不可用的,直接抛异常(不能进行手动操作),所以我们主要来看一下SqlSessionInterceptor的invoke方法:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//重新获取一个SqlSession实例,默认得到的是DefaultSqlSession实例,因为每次执行语句都是用新的SqlSession对象,所以解决了mybatis中线程不安全的问题
            SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);

            Object unwrapped;
            try {
            //执行方法
                Object result = method.invoke(sqlSession, args);
                //如果当前执行了事务,则提交事务
                if(!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
                    sqlSession.commit(true);
                }

                unwrapped = result;
            } catch (Throwable var11) {
                unwrapped = ExceptionUtil.unwrapThrowable(var11);
                if(SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
                    SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
                    sqlSession = null;
                    Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException)unwrapped);
                    if(translated != null) {
                        unwrapped = translated;
                    }
                }

                throw (Throwable)unwrapped;
            } finally {
            //关闭SqlSession
                if(sqlSession != null) {
                    SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
                }

            }

            return unwrapped;
        }

从invoke方法可知,SqlSessionProxy又重新实例化了一个SqlSession对象来执行具体的操作。

五、事务工厂

我们知道,mybatis中配置数据源的时候,会选配置一个事务工厂,JDBC和MANAGER,MyBatis-Spring中也创建了一个事务工厂SpringManagedTransactionFactory->new SpringManagedTransaction(dataSource)。所以mybatis的事务可以交给Spring中的事务管理。

到此,整个Mybatis的源码分析过程就已经告一段落,若有什么需要改进的、有什么不对的地方、请留言!谢谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

菜鸟+1024

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值