ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

MyBatis(五)MyBatis整合Spring原理分析

2020-02-23 12:39:18  阅读:260  来源: 互联网

标签:sqlSessionFactory SqlSessionTemplate Spring SqlSessionFactory targetConfiguratio


前面梳理了下MyBatis在单独使用时的工作流程和关键源码,现在看看MyBatis在和Spring整合的时候是怎么工作的

也先从使用开始

Spring整合MyBatis

1.引入依赖,除了MyBatis的依赖,还需要引入 mybatis-spring依赖
2.在spring的配置文件applicationContext.xml里配置SqlSessionFactoryBean,从名字可以看出我们是通过这个Bean来创建SqlSessionFactory

  <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="configLocation" value="classpath:mybatis-config.xml"></property>
        <property name="mapperLocations" value="classpath:mapper/*.xml"></property>
        <property name="dataSource" ref="dataSource"/>
    </bean>

3.在applicationContext.xml配置扫描Mapper的路径

可以通过三种方式
1.配置MapperScannerConfigurer
<bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.XXX.XXX"/>
    </bean>

2.配置<scan>标签
 <context:component-scan base-package="com.XXX.XXX">

3.使用@MapperScan注解

原理分析

创建会话工厂

先看下前面配置的SqlSessionFactoryBean,其类图如下所示

其中的InitializingBean有一个待实现方法afterPropertiesSet,会在初始化Bean之后调用,这里就是在afterPropertiesSet实现方法里创建了SqlSessionFactory对象

//在Bean初始化之后调用
public void afterPropertiesSet() throws Exception {
        Assert.notNull(this.dataSource, "Property 'dataSource' is required");
        Assert.notNull(this.sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
        Assert.state(this.configuration == null && this.configLocation == null || this.configuration == null || this.configLocation == null, "Property 'configuration' and 'configLocation' can not specified with together");
        //创建SqlSessionFactory
        this.sqlSessionFactory = this.buildSqlSessionFactory();
    }
 protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
        XMLConfigBuilder xmlConfigBuilder = null;
        Configuration targetConfiguration;
        //如果configuration已经存在,则把当前Bean的Properties属性也加入到Configuration
        if (this.configuration != null) {
            targetConfiguration = this.configuration;
            if (targetConfiguration.getVariables() == null) {
                targetConfiguration.setVariables(this.configurationProperties);
            } else if (this.configurationProperties != null) {
                targetConfiguration.getVariables().putAll(this.configurationProperties);
            }
        //如果configuration不存在,但是存在configLocation,则使用XmlConfigBuilder解析对应的配置文件
        } else if (this.configLocation != null) {
            xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), (String)null, this.configurationProperties);
            targetConfiguration = xmlConfigBuilder.getConfiguration();
        } else {
        //如果Configuration对象不存在,configLocation路径也没有,则使用默认的configurationProperties给configuration赋值
            LOGGER.debug(() -> {
                return "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration";
            });
            targetConfiguration = new Configuration();
            Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
        }

//基于当前factoryBean里已有的属性,对targetConfiguration对象里面的属性进行赋值
        Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
        Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
        Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);
        String[] typeHandlersPackageArray;
        if (StringUtils.hasLength(this.typeAliasesPackage)) {
            typeHandlersPackageArray = StringUtils.tokenizeToStringArray(this.typeAliasesPackage, ",; \t\n");
            Stream.of(typeHandlersPackageArray).forEach((packageToScan) -> {
                targetConfiguration.getTypeAliasRegistry().registerAliases(packageToScan, this.typeAliasesSuperType == null ? Object.class : this.typeAliasesSuperType);
                LOGGER.debug(() -> {
                    return "Scanned package: '" + packageToScan + "' for aliases";
                });
            });
        }

        if (!ObjectUtils.isEmpty(this.typeAliases)) {
            Stream.of(this.typeAliases).forEach((typeAlias) -> {
                targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
                LOGGER.debug(() -> {
                    return "Registered type alias: '" + typeAlias + "'";
                });
            });
        }

        if (!ObjectUtils.isEmpty(this.plugins)) {
            Stream.of(this.plugins).forEach((plugin) -> {
                targetConfiguration.addInterceptor(plugin);
                LOGGER.debug(() -> {
                    return "Registered plugin: '" + plugin + "'";
                });
            });
        }

        if (StringUtils.hasLength(this.typeHandlersPackage)) {
            typeHandlersPackageArray = StringUtils.tokenizeToStringArray(this.typeHandlersPackage, ",; \t\n");
            Stream.of(typeHandlersPackageArray).forEach((packageToScan) -> {
                targetConfiguration.getTypeHandlerRegistry().register(packageToScan);
                LOGGER.debug(() -> {
                    return "Scanned package: '" + packageToScan + "' for type handlers";
                });
            });
        }

        if (!ObjectUtils.isEmpty(this.typeHandlers)) {
            Stream.of(this.typeHandlers).forEach((typeHandler) -> {
                targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
                LOGGER.debug(() -> {
                    return "Registered type handler: '" + typeHandler + "'";
                });
            });
        }

        if (this.databaseIdProvider != null) {
            try {
                targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
            } catch (SQLException var23) {
                throw new NestedIOException("Failed getting a databaseId", var23);
            }
        }

        Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);
        //如果XMLConfigBuilder不为空,就调用parse方法,这跟MyBatis里调用的一样
        if (xmlConfigBuilder != null) {
            try {
                xmlConfigBuilder.parse();
                LOGGER.debug(() -> {
                    return "Parsed configuration file: '" + this.configLocation + "'";
                });
            } catch (Exception var21) {
                throw new NestedIOException("Failed to parse config resource: " + this.configLocation, var21);
            } finally {
                ErrorContext.instance().reset();
            }
        }
        //创建environment,事务工厂(默认使用SpringManagedTransactionFactory)
        targetConfiguration.setEnvironment(new Environment(this.environment, (TransactionFactory)(this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory), this.dataSource));

        //根据配置的MapperLocation 使用XMLMapperBuilder解析mapper.xml,把接口和对应的MapperProxyFactory注册到MapperRegistry 中。
        if (!ObjectUtils.isEmpty(this.mapperLocations)) {
            Resource[] var24 = this.mapperLocations;
            int var4 = var24.length;

            for(int var5 = 0; var5 < var4; ++var5) {
                Resource mapperLocation = var24[var5];
                if (mapperLocation != null) {
                    try {
                        XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
                        xmlMapperBuilder.parse();
                    } catch (Exception var19) {
                        throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", var19);
                    } finally {
                        ErrorContext.instance().reset();
                    }

                    LOGGER.debug(() -> {
                        return "Parsed mapper file: '" + mapperLocation + "'";
                    });
                }
            }
        } else {
            LOGGER.debug(() -> {
                return "Property 'mapperLocations' was not specified or no matching resources found";
            });
        }
        //使用SqlSessionFactoryBuilder的build方法构建SqlSessionFactory
        return this.sqlSessionFactoryBuilder.build(targetConfiguration);
    }

创建SqlSession

Spring在创建SqlSession的时候不是直接使用MyBatis的DefaultSqlSession,而是自己封装了一个SqlSessionTemplate,这是因为DefaultSqlSession不是线程安全的,所以Spring特性封装了一个线程安全的SqlSessionTemplate(线程安全问题在web场景下不可避免)

SqlSessionTemplate里创建的SqlSession是使用的jdk动态代理,所有方法的调用实际都是通过这个proxy来调用的

 private final SqlSessionFactory sqlSessionFactory;
    private final ExecutorType executorType;
    private final SqlSession sqlSessionProxy;
    private final PersistenceExceptionTranslator exceptionTranslator;

    public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
    }

    public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
        this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
    }

    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;
        //通过JDK动态代理创建代理对象
        this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor());
    }


//创建代理对象的InvocationHandler
private class SqlSessionInterceptor implements InvocationHandler {
        private SqlSessionInterceptor() {
        }

        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //获得SqlSession对象,commit和close都是调用的该对象的方法,猜测SqlSessionTemplate线程安全也是因为这段代码
            SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);

            Object unwrapped;
            try {
                //调用目标对象方法,实际调用了DefaultSqlSession的方法
                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 {
                if (sqlSession != null) {
                    SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
                }

            }

            return unwrapped;
        }
    }

这一段分析线程安全的原因后面补充

知道Spring中是使用SqlSessionTemplate来保证线程安全的,那么我们怎么获取这个Template并使用呢?在spring的早期版本,想要使用SqlSessionTemplate,需要让我们的dao类去继承SqlSessionDaoSupport,它持有一个SqlSessionTemplate 对象,并提供了getSqlSession的方法

public abstract class SqlSessionDaoSupport extends DaoSupport {
    private SqlSessionTemplate sqlSessionTemplate;

    public SqlSessionDaoSupport() { }
    
    //在继承SqlSessionDaoSupport的时候需要调用setSqlSessionFactory把SqlSessionFactory注入(也可以通过xml注入)
    public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
        if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
            this.sqlSessionTemplate = this.createSqlSessionTemplate(sqlSessionFactory);
        }
    }

    protected SqlSessionTemplate createSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
   
    public final SqlSessionFactory getSqlSessionFactory() {
        return this.sqlSessionTemplate != null ? this.sqlSessionTemplate.getSqlSessionFactory() : null;
    }
    //对外提供的获取SqlSessionTemplate的方法(SqlSessionTemplate也实现了SqlSession接口)
    public SqlSession getSqlSession() {
        return this.sqlSessionTemplate;
    }
  ...
}

但是这样一来,我们的DAO层的接口,如果要操作数据库,就都需要实现SqlSessionDaoSupport去拿到一个SqlSessionTemplate

目前的spring早已经优化了使用SqlSessionTemplate的方式,可以直接使用@Autowired 在Service层自动注入的 Mapper 接口

接口的扫描注册

这些Mapper就是在applicationContext.xml配置MapperScannerConfigurer时注册上的

<bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.XXX.XXX"/>
 </bean>

MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor(该接口就是用于在BeanDefinition注册后做一些后置处理,比放说修改BeanDefinition等)接口的postProcessBeanDefinitionRegistry方法

//MapperScannerConfigurer
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        if (this.processPropertyPlaceHolders) {
            this.processPropertyPlaceHolders();
        }

        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();
        //扫描包路径,注册Mapper
        scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
    }

-----scanner.scan最后会调用ClassPathMapperScanner的doScan方法

  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
        if (beanDefinitions.isEmpty()) {
            LOGGER.warn(() -> {
                return "No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.";
            });
        } else {
            //对BeanDefinitions进行处理的方法
            this.processBeanDefinitions(beanDefinitions);
        }

        return beanDefinitions;
    }


 private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
        Iterator var3 = beanDefinitions.iterator();

        while(var3.hasNext()) {
            BeanDefinitionHolder holder = (BeanDefinitionHolder)var3.next();
            GenericBeanDefinition definition = (GenericBeanDefinition)holder.getBeanDefinition();
            String beanClassName = definition.getBeanClassName();
            LOGGER.debug(() -> {
                return "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName + "' mapperInterface";
            });
           //the mapper interface is the original class of the bean
           //but the actual class of the bean is  MapperFactoryBean    
           //构造方法参数添加为当前接口的类名  definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
            definition.setBeanClass(this.mapperFactoryBean.getClass());
            definition.getPropertyValues().add("addToConfig", this.addToConfig);
            boolean explicitFactoryUsed = false;
            //向BeanDefinition的属性加入SqlSessionFactory和sqlSessionTemplate
            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;
            }

            if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
                if (explicitFactoryUsed) {
                    LOGGER.warn(() -> {
                        return "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(() -> {
                        return "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.";
                    });
                }

                definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
                explicitFactoryUsed = true;
            }

            if (!explicitFactoryUsed) {
                LOGGER.debug(() -> {
                    return "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.";
                });
                //表示当前bean会自动填充setter方法的属性,容器中配置了SqlSessionFactory的bean,所以会调用setSqlSessionFactory()方法来注入属性。
                definition.setAutowireMode(2);
            }
        }

    }

从上面可以看到,我们扫描到的Mapper会把其BeanDefinition的BeanClass设置成MapperFactoryBean,所以在实例化的时候实际生成的也是MapperFactoryBean对象,而我们的MapperFactoryBean就继承了SqlSessionDaoSupport,而前面我们又向BeanDefinition里添加了SqlSessionTemplate和SqlSessionFactory的属性,所以生成的MapperFactoryBean实际就是一个继承了SqlSessionDaoSupport并且注入了SqlSessionTemplate和SqlSessionFactory的对象

接口注入的使用

前面说了,目前spring在使用Mapper的时候,直接在加了Service 注解的类里面使用@Autowired注入Mapper接口就好了

@Service
public class EmployeeService {

    @Autowired
    EmployeeMapper employeeMapper;

    //按id查询
    public Employee getEmp(Integer id) {
        Employee employee = employeeMapper.selectByPrimaryKey(id);
        return employee;
    }
}

spring在启动的时候会去实例化EmployeeService,而EmployeeService里又需要注入EmployeeMapper对象,
Spring会根据Mapper的名字从BeanFactory 中获取它的BeanDefination,再从BeanDefination 中 获 取 BeanClass ,此时其BeanClass已经被替换成MapperFactoryBean了

接下来就只需要创建MapperFactoryBean实例对象就可以了,因为MapperFactoryBean实现了FactoryBean接口,所以会通过getObject()来获取对应的实例对象

 //实际调用的是SqlSession(这里的返回的对象是SqlSessionTemplate)的getMapper方法
 public T getObject() throws Exception {
        return this.getSqlSession().getMapper(this.mapperInterface);
    }

----SqlSessionTemplate 这里调用了Configuration的getMapper
 public <T> T getMapper(Class<T> type) {
        return this.getConfiguration().getMapper(type, this);
    }
     
----Configuration 最终是通过configuration的mapperRegistry获得对应的Mapper,实际拿到的是
通过工厂类MapperProxyFactory获得的用MapperProxy增强的代理对象
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return this.mapperRegistry.getMapper(type, sqlSession);
    }

总结一下:

关键类 功能
SqlSessionTemplate Spring 中 线程安全的SqlSession实现类,通过代理的方式调用 DefaultSqlSession 的方法
SqlSessionInterceptor(内部类) 定义在在 SqlSessionTemplate ,用来增强代理 DefaultSqlSession
SqlSessionDaoSupport 用于获取 SqlSessionTemplate,需要继承它并且注入SqlSessionFactory
MapperFactoryBean 注册到 IOC 容器中的Mapper接口替换类,继承了 SqlSessionDaoSupport 可以用来获取SqlSessionTemplate,注入接口的时候,会调用用它的 getObject()方法 最终调用Configuration的getMapper方法
SqlSessionHolder 控制 SqlSession 和事务


 


 

dearfulan 发布了53 篇原创文章 · 获赞 16 · 访问量 6295 私信 关注

标签:sqlSessionFactory,SqlSessionTemplate,Spring,SqlSessionFactory,targetConfiguratio
来源: https://blog.csdn.net/qq_35448165/article/details/104451528

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有