Mybatis集成到Spring的原理:桥梁和扩展点

【思考】:为什么要整合spring,如何整合?

        1、为什么整合:  管理对象,将对象交给容器管理,便于容器之间依赖的解耦;对象重用,性能友好;

        2、如何整合:  通过一个Template封装方法;

1、  如何实现集成spring的关键问题?mybatis-spring.jar  包

       1、SqlSessionFactory会话工厂是怎么创建的在什么时候创建?

             在ApplicationContext启动的时候根据ApplicationContext.xml中的sqlSessionFactory的Bean配置创建;指定全局配置路径,Mapper路径,DataSource配置;

             实现类:sqlSessionFactoryBean,实现了FactoryBean,InitializingBean;

                    getObject中解析配置,调用Mybatis的全局配置方法-XmlConfigurationBuilder、解析Mapper的配置-XmlMapperBuilder;    返回DefaultSqlSessionFactory;

       2、SqlSession怎么获取?为什么不用它来getMapper?

             【思考1】能用DefaultSqlSession吗? 不能的话,为什么不能?替代品是什么?怎么在Dao层的实现类获取SqlSeaaion

                     不能使用,因为DefaultSqlSession不是线程安全的不能在spring中作为单例使用;

                     而SqlSessionTemplate是线程安全的,用其来替代DefaultSqlSession;为何是线程安全的呢?动态代理创建的sqlSessionProxy(实质是DefaultSqlSession)进行调用,其代理类是SqlSessionInterceptor,查看其invoke方法{getSqlSession(),   closeSqlSession()   方法 两个方法中去实现线程安全   在进来的时候通过ThreadLocal保存创建的SqlSessionHolder在对象TransactionSynchronicationManager中的,在执行完之后colse()  来确保线程安全的  };     一个事务一个SqlSession;

                     SqlSessionTemplate如何获取?通过继承DaoSupport就可以通过getSqlSession() 获取;

                          【问题1】:每次都要对DaoSupport的封装方法进行实现,解决办法用BaseDao继承DaoSupport实现,然后后面的继承BaseDao就可以直接用了;

                           【问题2】:不想创建实现类,用注入的方式

             【思考2】接口扫描注册:不想创建实现类怎么办?用@Autowired如何实现?

                      如何扫描的?不想创建,那么一定是交给了容器在管理;配置<bean id="mapperScanner">  的实现对配置的扫描,

                      注册的是什么?然后进行注册,这里注册的不是接口号,而是MapperFactoryBean;替换原因是为了继承DaoSupport,去获取SqlSessionTemplate;

                      

       3、@Autowired实现注入一个Mapper接口,直接使用;

     【思考】:在IOC容器里面我们注册的是什么?注册的时候发生了什么事情?  --- 将Mapper注册到容器,在调用的时候调用其方法通过反射实现调用

 

2、实战spring整合mybatis

        1,加入依赖包:

<!-- mybatis-spring 整合 -->
<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis-spring</artifactId>
  <version>1.2.2</version>
</dependency>

spring启动的时候需要使用一个bean.xml配置文件,

ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");

bean.xml文件中就涉及到整合的配置信息,整合使用到以下的配置:

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <!-- 加载数据源 -->
    <property name="dataSource" ref="dataSource"/>
    <property name="mapperLocations" value="classpath*:mappers/*Mapper.xml"/>
</bean>
 
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <!-- 指定扫描的包,如果存在多个包使用(逗号,)分割 -->
    <property name="basePackage" value="com.test.bean"/>
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>

 

3、源码分析:  SqlSessionFactoryBean、 MapperScannerConfigurer

 

1、MapperScannerConfigurer类创建ClassPathMapperScanner扫描器:

核心方法:postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)方法,看看具体内容:

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
    if (this.processPropertyPlaceHolders) {
        this.processPropertyPlaceHolders();
    }
    //创建扫描器(1、扫描basePackage包下所有的class类;  
    //    2、将所有的class类封装成ScannedGenericBeanDefinition对象)
               过滤sbd对象,只接受接口类;
               属性设置:sqlSessionFactory   、BeanClass
          3、将ScannedGenericBeanDefinition 通过注册器BeanDefinitionRegistry 注册到 DefaultListableBeanFactory
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    scanner.setAddToConfig(this.addToConfig);
    scanner.setAnnotationClass(this.annotationClass);
    scanner.setMarkerInterface(this.markerInterface);
    scanner.setSqlSessionFactory(this.sqlSessionFactory);
    scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
    scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
    scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
    scanner.setResourceLoader(this.applicationContext);
    scanner.setBeanNameGenerator(this.nameGenerator);
    scanner.registerFilters();
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
}

 

第三过滤sbd对象,只接受接口类,从下面的代码中可以看出。

protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
    return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}

第四完成sbd对象属性的设置,比如设置sqlSessionFactory、BeanClass等,这个sqlSessionFactory是本文接下来要解析的

SqlSessionFactoryBean
sbd.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
sbd.setBeanClass(MapperFactoryBean.class);
sbd.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);

第五将过滤出来的sbd对象通过这个BeanDefinitionRegistry registry注册器注册到DefaultListableBeanFactory中,这个registry就是方法postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)中的参数。

总结时序图为:


mapper接口注册之后,在什么地方实例化和使用呢?后面在分析。

 


2、创建SqlSessionFactoryBean

接着看看spring和mybatis整合的另外一个标签。

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <!-- 加载数据源 -->
    <property name="dataSource" ref="dataSource"/>
    <property name="mapperLocations" value="classpath*:mappers/*Mapper.xml"/>
</bean>

这个sqlSessionFactory的实现类中做了什么事情。首先看看这个类的声明:

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, 

     InitializingBean, ApplicationListener<ApplicationEvent> {
     //省略代码
}


从配置来看,这个Bean至少提供了两个类属性,一个是和dataSource有关,一个和mapperLocations有关,看实现什么都没有做,只是做了两次简单的赋值工作;

public void setDataSource(DataSource dataSource) {
    if (dataSource instanceof TransactionAwareDataSourceProxy) {
        this.dataSource = ((TransactionAwareDataSourceProxy)dataSource).getTargetDataSource();
    } else {
        this.dataSource = dataSource;
    }
 
}
public void setMapperLocations(Resource[] mapperLocations) {
    this.mapperLocations = mapperLocations;
}

      核心方法1:执行afterPropertiesSet方法,这个方法是接口InitializingBean中的方法,实现创建DefaultSqlSessionFactory。

public void afterPropertiesSet() throws Exception {
    Assert.notNull(this.dataSource, "Property 'dataSource' is required");
    Assert.notNull(this.sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
    this.sqlSessionFactory = this.buildSqlSessionFactory();
}


//buildSqlSessionFactory()方法中,为了简化分析,我们只关心必须要做的事情。
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
    XMLConfigBuilder xmlConfigBuilder = null;
    //初始化mybatis配置对象  configuration
    Configuration configuration;
    configuration = new Configuration();
    configuration.setVariables(this.configurationProperties);
    //省略代码
    //创建事务工厂
    if (this.transactionFactory == null) {
        this.transactionFactory = new SpringManagedTransactionFactory();
    }
 
    //包装器模式?
    Environment environment = new Environment(this.environment, this.transactionFactory, this.dataSource);
    configuration.setEnvironment(environment);
    
    if (!ObjectUtils.isEmpty(this.mapperLocations)) {
        Resource[] arr$ = this.mapperLocations;
        len$ = arr$.length;
        for(i$ = 0; i$ < len$; ++i$) {
            Resource mapperLocation = arr$[i$];
            if (mapperLocation != null) {
                try {
                    //创建XMLMapperBuilder对象;
                    XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments());
                    //解析mapperLocations 所代表的 Xml配置文件
                    xmlMapperBuilder.parse();
                } catch (Exception var20) {
                    throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", var20);
                } finally {
                    ErrorContext.instance().reset();
                }
            }
        }
    }
 
    //直接创建了DefaultSqlSessionFactory,其中还持有Configuration对象的引用
    return this.sqlSessionFactoryBuilder.build(configuration);
}


mapperLocation-->AuthUserMapper.xml,AuthUserMapper.xml的部分代码如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.test.bean.AuthUserMapper" >
  <resultMap id="BaseResultMap" type="com.test.bean.AuthUser" >
    <id column="id" property="id" jdbcType="BIGINT" />
    <result column="user_account" property="userAccount" jdbcType="VARCHAR" />
    <result column="password" property="password" jdbcType="VARCHAR" />
    <result column="create_time" property="createTime" jdbcType="TIMESTAMP" />
    <result column="update_time" property="updateTime" jdbcType="TIMESTAMP" />
    <result column="is_delete" property="isDelete" jdbcType="TINYINT" />
  </resultMap>
  //此处代码有省略
  <update id="updateByPrimaryKey" parameterType="com.test.bean.AuthUser" >
    update t_auth_user
    set user_account = #{userAccount,jdbcType=VARCHAR},
      password = #{password,jdbcType=VARCHAR},
      create_time = #{createTime,jdbcType=TIMESTAMP},
      update_time = #{updateTime,jdbcType=TIMESTAMP},
      is_delete = #{isDelete,jdbcType=TINYINT}
    where id = #{id,jdbcType=BIGINT}
  </update>
</mapper>

afterPropertiesSet()方法总结起来,就是创建了几个对象,依次是mybatis的核心类Configuration、spring和mybatis集成的事物工厂类SpringManagedTransactionFactory、mybatis的Environment类、mybatis的DefaultSqlSessionFactory类,同时还完成了对mybatis的xml文件解析,并将解析结果封装在Configuration类中。以下为时序图。

       

       核心方法2:执行onApplicationEvent方法    这个方法的调用是在spring容器初始化完成之后,该方法是接口ApplicationListener<ApplicationEvent>中的方法。

public void onApplicationEvent(ApplicationEvent event) {
    if (this.failFast && event instanceof ContextRefreshedEvent) {
        this.sqlSessionFactory.getConfiguration().getMappedStatementNames();
    }
}

调试发现this.failFast这个变量的值是false,所以这个方法不会执行,在此就不做重点分析了。

执行getObject方法
第三个被调用的就是getObject方法,该方法是接口FactoryBean<SqlSessionFactory>中的方法。对Spring源码有深入解析的朋友都清楚这个方法的调用时机是在获取对象实例的时候调用。并根据id从spring容器中获取SqlSessionFactoryBean。

        看看方法内容,就知道是直接返回创建好的sqlSessionFactory,其创建时在afterPropertiesSet()方法中创建的;其对象就是DefaultSqlSessionFactory-  mybatis的SqlSessionFactory模板实现。


public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
        this.afterPropertiesSet();
    }
    return this.sqlSessionFactory;
}

 

上面两个标签的解析过程分析完成,现在有两个问题没有答案,第一个是basePackage基础包下面扫描出来的mapper接口怎么实例化的?第二个是这两个标签创建出来的对象怎么配合使用的?

3、实例化过程

整合之后实例化过程;  在本系列的例子中,spring容器在启动的过程中,需要去实例化AuthUserServiceImpl服务类,

@Service
public class AuthUserServiceImpl implements IAuthUserService {
    @Resource
    private AuthUserMapper authUserMapper;
 
    public AuthUserServiceImpl(){
        logger.info("创建 com.test.bean.AuthUserServiceImpl");
    }
}

这个类依赖了AuthUserMapper接口,在实例化AuthUserServiceImpl的过程中需要,首先去实例化这个AuthUserMapper接口,但是接口是不能被实例化的,接下来分析这个接口实现类的创建过程。

      简单的说明:spring在初始化的过程中,会去创建AuthUserServiceImpl类,创建完成之后,会进行属性赋值,AuthUserMapper这个mybatis接口就是AuthUserServiceImpl的一个属性,

      首先根据这个mapper的名字从spring的BeanFactory中获取它的BeanDefinition,再从BeanDefinition中获取BeanClass,AuthUserMapper对应的BeanClass就是MapperFactoryBean,这是为什么呢?在上面分析的内容中提到过,也就是在创建MapperScannerConfigurer对象的时候设置的。

      接着就是创建MapperFactoryBean对象了,创建完成之后,就需要对属性进行赋值,他有哪些属性需要赋值呢?这是在创建AuthUserMapper所对应BeanDefinition对象的时候决定的,回顾上面创建MapperScannerConfigurer对象的那部分内容就知道。其中有一个属性就是SqlSessionFactoryBean,要实例化这个对象,这个就需要用到下面这个标签了

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <!-- 加载数据源 -->
    <property name="dataSource" ref="dataSource"/>
    <property name="mapperLocations" value="classpath*:mappers/*Mapper.xml"/>
</bean>

       上面的内容也已经分析过这个SqlSessionFactoryBean的创建过程。

       MapperFactoryBean对象的属性设置完成之后,就调用它的getObject()方法,来获取authUserMapper对应的实现类,从上面图中可以看出来,最后返回的就是一个代理类,这个代理类使用jdk的动态代理创建出来的。

return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(),
 new Class[]{this.mapperInterface}, mapperProxy);

        这个MapperProxy类就是InvocationHandler的实现类:

public class MapperProxy<T> implements InvocationHandler, Serializable {
 
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        } else {
            MapperMethod mapperMethod = this.cachedMapperMethod(method);
            return mapperMethod.execute(this.sqlSession, args);
        }
    }
}

        程序在调用authUserMapper对象的某个方法的时候,就会调用到MapperProxy对象的invoke()方法,去完成对数据库的操作。

 

 

 

 

最后附上一张类图,spring和mybatis整合过程中创建的类:

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值