mybatis 原理_Mybatis和Spring集成整合原理详情

Mybatis和Spring集成原理

  • 前面的系列文章都是单独讲解MyBatis,并未将其和Spring整合起来,看看整合的原理。在了解之前我们大概梳理一下流程:首先MyBatis的核心组件有:SqlSessionFactory和Java接口代理对象,前者用于创建SqlSession,后者是接口的代理对象实现接口访问数据库。
  • 本文会按照代码,和基本的思路分析Spring整合MyBatis,如果直接需要源码,可以跳到文章尾部;
b7c56e1a-998e-4258-a09e-e239e3192d32

一、代码差异

  • 先看看集成前需要准备的代码,包括:实体类、接口、Xml文件
@Mapperpublic interface UserDao {    List findAll();}@Datapublic class User {    Integer id;    private String name;    private String job;}<?xml version="1.0" encoding="UTF-8" ?>select * from user
  • 前面的内容,不管MyBatis集成Spring与否都是一样的,后面不同的地方分别展示;

1.1 MyBatis

  • 先看一段单独使用Mybatis的代码,代码包括:
177134272c7b4ab286b69f0c9f17043b
  • 下面是配置文件,配置了别名扫描,Xml文件,数据源等配置,这里留意,后面和Spring集成后这些都不需要了
<?xml version="1.0" encoding="UTF-8" ?>

1.2 MyBatis和Spring

  • 再看一段MyBatis和Spring集成后的使用代码:
/** * @Description Spring和MyBatis集成后测试 */public class Test01Combine {    @Test    public void test() throws Exception {        //ApplicationContext app = new ClassPathXmlApplicationContext("spring.xml"); //加载xml配置文件        ApplicationContext app = new AnnotationConfigApplicationContext(MainConfig.class); //加载配置对象        System.out.println("------cut-off---------");        UserDao mapper = app.getBean(UserDao.class);        List all = mapper.findAll();        for (User u : all) {            System.out.println(u);        }    }}
  • 集成后,除了最前面的实体类、接口、Xml文件,还需要一个配置类,也可以是XML配置文件,如果用配置文件,测试类中就使用注释了的一行加载,使用配置类更加简洁,使用AnnotationConfigApplicationContext加载,配置类如下:
/** * @Classname MainConfig * @Description Spring集成MyBatis后的配置类 */@Configuration@ComponentScan("com.intellif.mozping")public class MainConfig {    private static final String URL = "jdbc:mysql://192.168.12.168:3306/test";    private static final String USERNAME = "root";    private static final String PASSWORD = "introcks1234";    private static final String DRIVER = "com.mysql.jdbc.Driver";    /**     * MyBatis自身数据源     */    @Bean("unPooledDataSource")    DataSource unPooledDataSource() {        UnpooledDataSource dataSource = new UnpooledDataSource();        dataSource.setUrl(URL);        dataSource.setDriver(DRIVER);        dataSource.setUsername(USERNAME);        dataSource.setPassword(PASSWORD);        return dataSource;    }    /**     * 配置sqlSessionFactoryBean,用于创建 sqlSessionFactory     * 这里通过Qualifier来注入一个数据源,如果注入的是dataSource,就注入DruidDataSource     * 如果注入 unPooledDataSource,就不使用数据源,使用的是MyBatis内置的数据源     */    @Bean("sqlSessionFactoryBean")    public SqlSessionFactoryBean sqlSessionFactoryBean(@Qualifier("unPooledDataSource") DataSource dataSource) {        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();        //1.数据源配置        sqlSessionFactoryBean.setDataSource(dataSource);        //2.别名扫描        sqlSessionFactoryBean.setTypeAliasesPackage("com.intellif.mozping.entity");        //3.Xml映射文件扫描,这里传的是数组,每一个元素只能代表一个xml,不能*.xml代表全部        sqlSessionFactoryBean.setMapperLocations(new ClassPathResource("mapper/User.xml"));        //注意前面的别名和Xml扫描都可以在配置文件配,这然后这里加入配置就好了,        //如果前面配置了,其实配置文件就可以不要了        //sqlSessionFactoryBean.setConfigLocation(new ClassPathResource("mybatis-config.xml"));        return sqlSessionFactoryBean;    }    @Bean("mapperScannerConfigurer")    MapperScannerConfigurer mapperScannerConfigurer() {        MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();        mapperScannerConfigurer.setBasePackage("com.intellif.mozping.dao");        return mapperScannerConfigurer;    }}
  • MyBatis和Spring集成的关键是mybatis-spring这个包,源码:https://github.com/mybatis/spring ,通过这个包来实现自动配置,我们主要看看增加的几个Bean,其实就是我们配置的几个Bean;
  • 其中 SqlSessionFactoryBean 用于创建 SqlSessionFactory,MapperScannerConfigurer用于扫描Java接口,unPooledDataSource是MyBatis内置的数据源(也可以使用Druid等第三方数据源),

二、主要类

2.1 SqlSessionFactoryBean

  • SqlSessionFactoryBean用于创建 SqlSessionFactory,这可以在Spring中创建SqlSessionFactory,回顾我们前面1.1中的代码,在没有和Spring集成时我们需要一个配置文件来创建 SqlSessionFactory ,集成之后显然这些配置也是需要的,只不过这次是将配置交给SqlSessionFactoryBean,然后由SqlSessionFactoryBean来创建 SqlSessionFactory;

2.1.1 核心属性

  • 核心属性自然是原本我们需要在配置文件中写的MyBatis的全部配置,下面只列举了一小部分:
private Resource configLocation;  //配置对象  private Configuration configuration;  //xml文件地址  private Resource[] mapperLocations;      //数据源  private DataSource dataSource; //用于构建sqlSessionFactory的Builder,这个类是mybatis自身提供的,在参考文章:[Mybatis 核心流程01-初始化阶段] 有分析过  private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();  private SqlSessionFactory sqlSessionFactory;  private Interceptor[] plugins;  //类型处理器,可以自定义类型处理器  private TypeHandler>[] typeHandlers;  //别名配置  private Class>[] typeAliases;
  • 下面是 SqlSessionFactoryBean 核心属性提供的 setXX 方法:
6f528810-2611-49a8-9384-9965371dc711


2.1.2 buildSqlSessionFactory

  • buildSqlSessionFactory是最核心的方法,用于创建SqlSessionFactory;这里面使用了 MyBatis 原本创建 SqlSessionFactory 的几个Builder辅助类,包括XMLConfigBuilder,XMLMapperBuilder(将映射文件转换为配置)和
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {    Configuration configuration;    XMLConfigBuilder xmlConfigBuilder = null;    if (this.configuration != null) {      configuration = this.configuration;      if (configuration.getVariables() == null) {        configuration.setVariables(this.configurationProperties);      } else if (this.configurationProperties != null) {        configuration.getVariables().putAll(this.configurationProperties);      }    } else if (this.configLocation != null) {      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);      configuration = xmlConfigBuilder.getConfiguration();    } else {      configuration = new Configuration();      if (this.configurationProperties != null) {        configuration.setVariables(this.configurationProperties);      }    }    if (this.objectFactory != null) {      configuration.setObjectFactory(this.objectFactory);    }    if (this.objectWrapperFactory != null) {      configuration.setObjectWrapperFactory(this.objectWrapperFactory);    }    if (this.vfs != null) {      configuration.setVfsImpl(this.vfs);    }    if (hasLength(this.typeAliasesPackage)) {      String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,          ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);      for (String packageToScan : typeAliasPackageArray) {        configuration.getTypeAliasRegistry().registerAliases(packageToScan,                typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);      }    }    if (!isEmpty(this.typeAliases)) {      for (Class> typeAlias : this.typeAliases) {        configuration.getTypeAliasRegistry().registerAlias(typeAlias);      }    }    if (!isEmpty(this.plugins)) {      for (Interceptor plugin : this.plugins) {        configuration.addInterceptor(plugin);      }    }    if (hasLength(this.typeHandlersPackage)) {      String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,          ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);      for (String packageToScan : typeHandlersPackageArray) {        configuration.getTypeHandlerRegistry().register(packageToScan);      }    }    if (!isEmpty(this.typeHandlers)) {      for (TypeHandler> typeHandler : this.typeHandlers) {        configuration.getTypeHandlerRegistry().register(typeHandler);      }    }    if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls      try {        configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));      } catch (SQLException e) {        throw new NestedIOException("Failed getting a databaseId", e);      }    }    if (this.cache != null) {      configuration.addCache(this.cache);    }    if (xmlConfigBuilder != null) {      try {        xmlConfigBuilder.parse();      } catch (Exception ex) {        throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);      } finally {        ErrorContext.instance().reset();      }    }    if (this.transactionFactory == null) {      this.transactionFactory = new SpringManagedTransactionFactory();    }    configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));    if (!isEmpty(this.mapperLocations)) {      for (Resource mapperLocation : this.mapperLocations) {        if (mapperLocation == null) {          continue;        }        try {         //加载xml映射文件          XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),              configuration, mapperLocation.toString(), configuration.getSqlFragments());          xmlMapperBuilder.parse();        } catch (Exception e) {          throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);        } finally {          ErrorContext.instance().reset();        }      }    }     //根据配置对象创建 SqlSessionFactory    return this.sqlSessionFactoryBuilder.build(configuration);  }
  • 大概梳理一下SqlSessionFactoryBean的设计,本身提供了很多SetXX方法用于设置配置,配置的设置比较灵活,既可以单独设置某一些配置,也可以设置配置对象或者配置文件的问题,因此我们既可以保留mybatis的配置文件,也可以去掉通过SqlSessionFactoryBean注入相关的配置。
  • SqlSessionFactoryBean继承了FactoryBean(不了解FactoryBean需要补一补啦),因此它是一个工厂Bean,可以创建SqlSessionFactory;
  • 在构建SqlSessionFactory的时候,所用到的方式还是保留了MyBatis中本身构建的方式,SqlSessionFactoryBean 本身就是将配置转换为配置对象,这里从最后一行代码 this.sqlSessionFactoryBuilder.build(configuration) 可以看出来,SqlSessionFactoryBean本身就做了一下配置转换,那么创建 SqlSessionFactory 的时机是什么时候呢,看2.1.3 。
这里需要我们对整个MyBatis的原理,流程需要有一定的了解,可以参考前面的相关文章。

2.1.3 创建时机

  • 前面我们看到了创建SqlSessionFactory的方法细节,那么创建的时机是在什么时候?SqlSessionFactoryBean 实现了 InitializingBean接口,时机就在Bean初始化完成后调用该接口的回调方法afterPropertiesSet,代码如下:
  @Override  public void afterPropertiesSet() throws Exception {      this.sqlSessionFactory = buildSqlSessionFactory();    }
  • 这里会创建 SqlSessionFactory ,且是在Bean初始化完成之后调用 afterPropertiesSet , 这和Bean的生命周期知识相关。

2.2 MapperFactoryBean

  • 在我们了解MapperScannerConfigurer之前,先简单看看 MapperFactoryBean 的作用,它的作用就是创建Java接口的代理对象,对于MyBatis这个细节不清楚的建议阅读参考文章[2]
  • MapperFactoryBean也实现了FactoryBean,我们看看getObject方法,其实就是获取SqlSession之后再获取Mapper对象,和我们1.1中的代码 UserDao mapper = sqlSession.getMapper(UserDao.class) 一样,因此其实 MapperFactoryBean 本身也不需要做太多工作,因为获取代理对象的工作还是委托给 SqlSession来做的,而这是MyBatis本身提供的机制。
  @Override  public T getObject() throws Exception {    return getSqlSession().getMapper(this.mapperInterface);  }
  • 由MapperFactoryBean 的作用我们知道,如果我们有一个接口比如前面的 UserDao ,需要得到代理对象就可以实例化一个MapperFactoryBean类型的Bean,注释中给出的配置方式如下:
    
  • 不过假设我们有很多接口,为每一个接口配置一次显然不方便,在直接使用MyBatis的过程中,我们通过UserDao mapper = sqlSession.getMapper(UserDao.class);来获取代理对象,与Spring集成之后,他会将全部的代理对象创建好放在IOC容器,因此就有了MapperScannerConfigurer

2.3 MapperScannerConfigurer

  • MapperScannerConfigurer 用于扫描我们的Java接口,给他配置一个路径即可
    @Bean("mapperScannerConfigurer")    MapperScannerConfigurer mapperScannerConfigurer() {        MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();        mapperScannerConfigurer.setBasePackage("com.intellif.mozping.dao");        return mapperScannerConfigurer;    }
  • 下面是测得代码中获取到的代理对象,和直接使用MyBatis是一样的
74e83996c5714c159e42d8618c518ece
  • MapperScannerConfigurer 扫描Java接口的机制是什么?这里不具体分析源码,稍微带一下,MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,通过接口方法postProcessBeanDefinitionRegistry来修改Bean定义信息,将包下全部的Java接口扫描并注册Bean定义信息,设置Bean类型是MapperFactoryBean类型,因此后面容器就会初始化对应的MapperFactoryBean,有兴趣可以阅读参考文章[3]
  • 另外也可以去掉MapperScannerConfigurer这个Bean的定义,使用注解:@MapperScan(value=“com.intellif.mozping.dao”),它和Bean的效果是一样的,具体看2.4。
  • MapperScannerConfigurer扫描多个Java接口并创建对应的代理对象,所以一般不会直接使用MapperFactoryBean,而是使用 MapperScannerConfigurer;

2.4 @MapperScan

  • @MapperScan注解可以起到和 MapperScannerConfigurer 这个Bean的一样的功能,比如下面两段代码是等效的:
    //注解扫描接口    @MapperScan(value="com.intellif.mozping.dao")    //MapperScannerConfigurer扫描接口    @Bean("mapperScannerConfigurer")    MapperScannerConfigurer mapperScannerConfigurer() {        MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();        mapperScannerConfigurer.setBasePackage("com.intellif.mozping.dao");        return mapperScannerConfigurer;    }
  • 如果了解ImportBeanDefinitionRegistrar接口(不了解请阅读参考文章[4]:),那么 @MapperScan的原理就很好理解,看下面的代码:
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)@Documented@Import(MapperScannerRegistrar.class)@Repeatable(MapperScans.class)public @interface MapperScan {    //省略代码...}
  • MapperScannerRegistrar类是关键,从下面的方法registerBeanDefinitions可以看到,其作用主要是将注解了MapperScan的类Bean定义信息注册,而注解了MapperScan的正是我们MyBatis的Java接口;
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {  @Override  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {    //扫描注解了MapperScan的类    AnnotationAttributes mapperScanAttrs = AnnotationAttributes        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));    if (mapperScanAttrs != null) {      registerBeanDefinitions(mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0));    }  }  //省略其他代码...}

三、小结

46cdba00d4b24f318e720991bcd1ac09

到此这篇关于文章就结束了!

总结

另外本人整理了一些Spring的视频资料,一共有8集,以及一些Java的学习视频以及资料,免费分享给大家,想要资料的可以 点赞 关注 私信 ‘资料’ 即可免费领取。深入底层,剖析源码。了解本质。 爱编程,爱生活,爱分享!

c864ba665e1b4ceda7a0865340741dcb

希望对大家有所帮助,有用的话点赞给我支持!!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值