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

前面梳理了下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,其类图如下所示

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

其中的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>

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

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 和事务


 


 

MyBatis(五)MyBatis整合Spring原理分析MyBatis(五)MyBatis整合Spring原理分析 dearfulan 发布了53 篇原创文章 · 获赞 16 · 访问量 6295 私信 关注
上一篇:Spring整合Mybatis


下一篇:MyBatis---环境搭建及简单的SQL映射