• 3. MyBatis与spring结合原理


    3. MyBatis与spring结合原理

    依赖: mybatis-spring

    1. MyBatis-Spring是什么

    • Mybatis-spring 用于帮助你将 MyBatis 代码无缝地整合到 Spring 中。

      • Spring 将会加载必要的 MyBatis 工厂类和 session 类
      • 提供一个简单的方式来注入 MyBatis 数据映射器和 SqlSession 到业务层的 bean 中。
      • 方便集成spring事务
      • 翻译 MyBatis 的异常到 Spring 的 DataAccessException 异常(数据访问异常)中。
    • Mybatis-spring 兼容性

      MyBatis-Spring要求Java5及以上版本还有下面列出的MyBatis和Spring版本:

      MyBatis-SpringMyBatisSpring
      1.0.0 或 1.0.13.0.1 到 3.0.53.0.0 或以上
      1.0.23.0.63.0.0 或以上
      1.1.03.1.0 或以上3.0.0 或以上

    2. 集成配置最佳实践

    1. 准备spring项目一个

    2. 在pom文件中添加mybatis-spring的依赖

    <dependency>
    	<groupId>org.mybatisgroupId>
    	<artifactId>mybatis-springartifactId>
    	<version>1.3.0version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    3. 配置SqlSessionFactoryBean
    4. 配置MapperScannerConfigurer
    
    • 1
    • 2
    1. 配置事务

    3. Spring集成Mybatis的原理分析

    分析源码之前也需要源码下载并安装到本地仓库和开发工具中,方便给代码添加注释;安装过程和mybatis源码的安装过程是一样的,这里就不再重复描述了;下载地址:https://github.com/mybatis/spring

    1. SqlSessionFactoryBean

    在 MyBatis-Spring 中, SqlSessionFactoryBean 是用于创建 Sql SessionFactory 的。

    • dataSource :用于配置数据源,该属性为必选项,必须通过这个属性配置数据源 ,这里使用了上一节中配置好的 dataSource 数据库连接池 。
    • mapper Locations : 配置 SqlSessionFactoryBean 扫描 XML 映射文件的路径,可以使用 Ant 风格的路径进行配置。(ANT PATH)
    • configLocation :用于配置mybatis config XML的路径,除了数据源外,对MyBatis的各种配直仍然可以通过这种方式进行,并且配置MyBatis settings 时只能使用这种方式。但配置文件中任意环境,数据源 和 MyBatis 的事务管理器都会被忽略;(数据源和事务会被忽略掉, 交由spring托管)
    • typeAliasesPackage : 配置包中类的别名,配置后,包中的类在 XML 映射文件中使用时可以省略包名部分 ,直接使用类名。这个配置不支持 Ant风格的路径,当需要配置多个包路径时可以使用分号或逗号进行分隔 。

    mybatis中有两种配置, 一种是只有一个的config setting配置, 另一种是由n个的接口对应的mapper文件

    
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="typeAliasesPackage" value="com.len.mybatis.entity"/>
        <property name="mapperLocations" value="classpath:sqlmapper/*.xml"/>
    bean>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    public class SqlSessionFactoryBean
        implements FactoryBean<SqlSessionFactory>, InitializingBean, 
    	ApplicationListener<ApplicationEvent> {
            
        @Override
        //在spring容器中创建全局唯一的sqlSessionFactory
        public void afterPropertiesSet() throws Exception {
            notNull(dataSource, "Property 'dataSource' is required");
            notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
            state((configuration == null && configLocation == null) 
                  || !(configuration != null && configLocation != null),
                "Property 'configuration' and 'configLocation' can not specified with together");
    
            this.sqlSessionFactory = buildSqlSessionFactory();
        }  
            
        // 将SqlSessionFactory对象注入spring容器
        public SqlSessionFactory getObject() throws Exception {
            if (this.sqlSessionFactory == null) {
                afterPropertiesSet();
            }
            return this.sqlSessionFactory;
        }
            
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    2. MapperFactoryBean

    <bean id="tUserMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
        <property name="mapperInterface" 
                  value="com.len.mybatis.mapper.TUserMapper">property>
        <property name="sqlSessionFactory" ref="sqlSessionFactory">property>
    bean> 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    public class MapperFactoryBean<T> extends SqlSessionDaoSupport 
        implements FactoryBean<T> {
        @Override
        public T getObject() throws Exception {
            return getSqlSession().getMapper(this.mapperInterface);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    org.apache.ibatis.session.defaults.DefaultSqlSession#getMapper

    public <T> T getMapper(Class<T> type) {
      return configuration.<T>getMapper(type, this);
    }
    
    • 1
    • 2
    • 3

    3. MapperScannerConfigurer

    通过 MapperScannerConfigurer类自动扫描所有的 Mapper 接口,使用时可以直接注入接口 。

    MapperScannerConfigurer中常配置以下两个属性 。

    • basePackage : 用于配置基本的包路径。可以使用分号或逗号作为分隔符设置多于一个的包路径,每个映射器将会在指定的包路径中递归地被搜索到 。
    • annotationClass : 用于过滤被扫描的接口,如果设置了该属性,那么 MyBatis 的接口只有包含该注解才会被扫描进去

    MapperScannerConfigurer和上面的MapperFactoryBean作用是一样的, 都是为了将对象放入容器中

    
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.len.mybatis.mapper"/>
    bean>
    
    • 1
    • 2
    • 3
    • 4

    BeanDefinitionRegistryPostProcessor: 这个后置处理器具有修改bean的功能, 也只有这个后置处理器可以修改bean, 因为只有这个可以拿到BeanDefinition

    public class MapperScannerConfigurer
        implements BeanDefinitionRegistryPostProcessor, 
    	InitializingBean, ApplicationContextAware, BeanNameAware {
              @Override
        public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
            if (this.processPropertyPlaceHolders) {//占位符处理
                processPropertyPlaceHolders();
            }
            //实例化ClassPathMapperScanner,并对scanner相关属性进行配置
            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();//根据上述配置,生成过滤器,只扫描合条件的class
            //扫描指定的包以及其子包
            scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage,
                ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
        }
    
        @Override
        public void afterPropertiesSet() throws Exception {
            notNull(this.basePackage, "Property 'basePackage' is required");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    org.mybatis.spring.mapper.ClassPathMapperScanner#doScan

    public Set<BeanDefinitionHolder> doScan(String... basePackages) {
        //通过父类的扫描,获取所有复合条件的BeanDefinitionHolder对象
        Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
    
        if (beanDefinitions.isEmpty()) {
            logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages)
                + "' package. Please check your configuration.");
        } else {
            //处理扫描得到的BeanDefinitionHolder集合,将集合中的每一个mapper接口转换成MapperFactoryBean后,注册至spring容器
            processBeanDefinitions(beanDefinitions);
        }
    
        return beanDefinitions;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    org.mybatis.spring.mapper.ClassPathMapperScanner#processBeanDefinitions

    // holder是一种封装参数的手法, 推荐使用
    //处理扫描得到的BeanDefinitionHolder集合,将集合中的每一个mapper接口转换成MapperFactoryBean后,注册至spring容器
    private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
        GenericBeanDefinition definition;
        //遍历集合
        for (BeanDefinitionHolder holder : beanDefinitions) {
            definition = (GenericBeanDefinition)holder.getBeanDefinition();
    
            if (logger.isDebugEnabled()) {
                logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '"
                    + definition.getBeanClassName() + "' mapperInterface");
            }
    
            // the mapper interface is the original class of the bean
            // but, the actual class of the bean is MapperFactoryBean
            //增加一个构造方法,接口类型作为构造函数的入参
            definition.getConstructorArgumentValues()
                .addGenericArgumentValue(definition.getBeanClassName()); // issue #59
            //将bean的类型转换成mapperFactoryBean
            definition.setBeanClass(this.mapperFactoryBean.getClass());
            //增加addToConfig属性
            definition.getPropertyValues().add("addToConfig", this.addToConfig);
    
            boolean explicitFactoryUsed = false;
            //增加sqlSessionFactory属性
            if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
                definition.getPropertyValues()
                    .add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
                explicitFactoryUsed = true;
            } else if (this.sqlSessionFactory != null) {
                definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
                explicitFactoryUsed = true;
            }
            //增加sqlSessionTemplate属性
            if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
                if (explicitFactoryUsed) {
                    logger.warn(
                        "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
                }
                definition.getPropertyValues()
                    .add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
                explicitFactoryUsed = true;
            } else if (this.sqlSessionTemplate != null) {
                if (explicitFactoryUsed) {
                    logger.warn(
                        "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
                }
                definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
                explicitFactoryUsed = true;
            }
    
            //修改自动注入的方式 bytype
            if (!explicitFactoryUsed) {
                if (logger.isDebugEnabled()) {
                    logger.debug(
                        "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
                }
                definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    这个里面有个非常重要的步骤, 就是将扫描到的BeanDefinition转换成MapperFactoryBean, 这就和我们的手动配置MapperFactoryBean一样了

  • 相关阅读:
    表单元素
    X11 Xlib截屏问题及深入分析四 —— XOpenDisplay函数源码分析(1)
    手把手教你开发微信小程序自定义底部导航栏
    [C++]封装
    Java 得到当前时间距离第二天凌晨还剩多少秒
    spring集成nacos简要笔记
    Fiddler抓包使用教程
    淘宝/天猫api数据接口,获得淘宝商品详情 API 返回值说明
    【Linux】——目录结构
    关于W5500网卡使用过程的部分问题记录
  • 原文地址:https://blog.csdn.net/yin18827152962/article/details/126066211