目录
一、Mybatis和Spring集成项目准备
1.pom文件改动
2.Spring配置文件
3.Java测试类
二、UML图和流程分析
1.UML类图
2.流程分析
三、源码分析
1.SqlSessionFactoryBean配置
2.MapperFactoryBean配置
2.1 DaoSupport类
2.2 SqlSessionDaoSupport
3.SqlSessionTemplate
在第一篇配置的基础上增加扩展基础的Spring集成,Mybatis基础配置篇见此(一)Mybatis持久化框架原理之项目搭建。同时,要想看懂这篇mybatis-spring的集成原理,首先需要搞懂mybatis核心的处理过程,具体原理分析见下列几篇源码解读:
(二)Mybatis持久化框架原理之架构组成;(三)Mybatis持久化框架原理之启动源码分析;(四)Mybatis持久化框架原理之运行源码流程。下图是此方法的UML类图结构:
上面的UML类图涉及了Spring的接口使用,有兴趣可以去翻看一系列的Spring接口。
由上面的UML类图可以看出来,mybatis-spring集成包最重要的为四个类,但基于上面的实例我们此次只分析以下三个类,MapperScannerConfigurer类不在此例子中做分析:
SqlSessionFactoryBean:实现了Spring的FactoryBean接口类,将其变成和Spring相关联的SqlSession的工厂类。SqlSessionTemplate:Mybatis中SqlSession的实现类,内部实际上封装了SqlSessionFactory和SqlSession,经常看到,但是在本例子中没有使用;MapperFactoryBean:类似于SqlSessionFactoryBean,只是该类针对的是Mapper接口形成的FactoryBean,里面封装了mapperInterface和SqlSessionTemplate,以便于和传统Mybatis一样从SqlSession中拿取Mapper代理类,进而操作数据库。其中,mybatis-spring包集成spring采用的是实现spring的通用接口,后续再说一下此集成过程中源码流程。
流程如下:
大致流程如图中所述,都是mybatis利用Spring的Bean初始化机制来集成到Spring中的,接下来我们看看其具体的实现细节。
该类是Spring上下文创建SqlSessionFactory的FactoryBean,并在依赖注入时将SqlSession-Factory传递给Mybatis的mapper代理工厂。首先看到其实现的三个接口:
FactoryBean:Spring中有BeanFactory也有FactoryBean,很容易把这个搞混,BeanFactory指的是Spring工厂的最高接口,而FactoryBean即是为某一类Bean创建一个工厂,这个工厂只产生这一类的Bean。SqlSessionFactoryBean实现的接口是FactoryBean<SqlSessionFactory>,指定了SqlSessionFactory,因此这个Bean只会产生SqlSessionFactory;InitializingBean:实现该接口的类,将在类的成员属性被spring框架初始化之后调用其中唯一的接口方法afterPropertiesSet;ApplicationListener:spring的发布/订阅模式(也叫观察者模式),当ApplicationEvent被发布的时候,将会调用ApplicationListener的实现类,SqlSessionFactoryBean的事件触发为ContextRefreshedEvent,当spring容器初始化之后将会调用此方法。SqlSessionFactoryBean该类关键源码如下:
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> { // config.xml文件对应的资源对象 private Resource configLocation; // mybatis最重要的配置对象,里面有mybatis运行时所需要的所有数据配置 private Configuration configuration; // mapper.xml文件对应的资源对象数组 private Resource[] mapperLocations; // 数据源 private DataSource dataSource; // 熟悉的SqlSessionFactory构造器 private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); // SqlSessionFactory获取SqlSession的工厂 private SqlSessionFactory sqlSessionFactory; // 其它的成员对象都忽略,都是在Configuration中可以找到的 // 暂时看到这几个重要的对象,同时中间忽略这些成员对象的setter和getter方法 @Override public void afterPropertiesSet() throws Exception { // 刚刚说过,当这个Bean初始化之后Spring工厂将会调用这个方法 // 忽略判断数据源dataSource和sqlSessionFactoryBuilder为空语句 // 这里也会判断configuration和configLocation这两个对象不能同时存在,否则 // 否则将会报错 // 确定必要对象的值不为空后,进入构造sqlSessionFactory方法 this.sqlSessionFactory = buildSqlSessionFactory(); } protected SqlSessionFactory buildSqlSessionFactory() throws Exception { // 这个方法比较长,但总体而言和原生mybatis解析config.xml文件流程还是差不多的 final Configuration targetConfiguration; // 熟悉的解析config.xml文件的XMLConfigBuilder构造器 XMLConfigBuilder xmlConfigBuilder = null; // 如果configuration对象不为空 if (this.configuration != null) { // 直接将configuration赋值给targetConfiguration变量 // configuration不为空则意味着至少config.xml文件那些流程已经解析过了 targetConfiguration = this.configuration; // 为variables属性再赋值 if (targetConfiguration.getVariables() == null) { targetConfiguration.setVariables(this.configurationProperties); } else if (this.configurationProperties != null) { targetConfiguration.getVariables() .putAll(this.configurationProperties); } } else if (this.configLocation != null) { // 如果configuration为空且configLocation配置了config.xml文件位置 // 则实例化XMLConfigBuilder构造器,注意这里只是实例化了XMLConfigBuilder // 还没有调用parse方法,因此config.xml文件还没有被解析 xmlConfigBuilder = new XMLConfigBuilder(this.configLocation .getInputStream(), null, this.configurationProperties); // 将里面实例化的configuration赋值给targetConfiguration targetConfiguration = xmlConfigBuilder.getConfiguration(); } else { // 如果configLocation和configuration都为空,则创建一个默认的 // Configuration对象 targetConfiguration = new Configuration(); Optional.ofNullable(this.configurationProperties) .ifPresent(targetConfiguration::setVariables); } // 设置setObjectFactory、setObjectWrapperFactory和setVfsImpl对象,略 ... // 别名包属性不为空 if (hasLength(this.typeAliasesPackage)) { // 如果不为空则获取包下的class类 // 再将其注册进别名注册中心typeAliasRegistry scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType) .stream().filter(clazz -> !clazz.isAnonymousClass()) .filter(clazz -> !clazz.isInterface()) .filter(clazz -> !clazz.isMemberClass()) .forEach(targetConfiguration .getTypeAliasRegistry()::registerAlias); } // 如果显示的配置了别名类,则直接将这些类配置到别名注册中心typeAliasRegistry if (!isEmpty(this.typeAliases)) { Stream.of(this.typeAliases).forEach(typeAlias -> { targetConfiguration.getTypeAliasRegistry() .registerAlias(typeAlias); }); } // 如果插件不为空的话,则设置添加插件到configuration对象中 if (!isEmpty(this.plugins)) { Stream.of(this.plugins).forEach(plugin -> { targetConfiguration.addInterceptor(plugin); }); } // 类似别名一样,如果设置了TypeHandler类型处理器包,则搜索出来再注册 // 到typeHandlerRegistr注册中心 if (hasLength(this.typeHandlersPackage)) { scanClasses(this.typeHandlersPackage, TypeHandler.class).stream() .filter(clazz -> !clazz.isAnonymousClass()) .filter(clazz -> !clazz.isInterface()) .filter(clazz -> !Modifier.isAbstract(clazz.getModifiers())) .filter(clazz -> ClassUtils.getConstructorIfAvailable(clazz) != null) .forEach(targetConfiguration.getTypeHandlerRegistry()::register); } // 类似别名 if (!isEmpty(this.typeHandlers)) { Stream.of(this.typeHandlers).forEach(typeHandler -> { targetConfiguration.getTypeHandlerRegistry() .register(typeHandler); }); } // 配置语言驱动器languageDriver if (!isEmpty(this.scriptingLanguageDrivers)) { Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> { targetConfiguration.getLanguageRegistry() .register(languageDriver); }); } // 如果手动配置了defaultDriverClass则将手动配置的值赋值给configuration Optional.ofNullable(this.defaultScriptingLanguageDriver) .ifPresent(targetConfiguration::setDefaultScriptingLanguage); // 设置databaseIdProvider if (this.databaseIdProvider != null) { try { targetConfiguration.setDatabaseId( this.databaseIdProvider.getDatabaseId(this.dataSource)); } catch (SQLException e) { throw new NestedIOException(); } } // 如果配置了cache缓存对象则添加到configuration对象中 Optional.ofNullable(this.cache) .ifPresent(targetConfiguration::addCache); // 如果解析config.xml文件的构造器不为空(即配置了config.xml文件) if (xmlConfigBuilder != null) { try { // 调用构造器的parse,执行解析config.xml文件流程 xmlConfigBuilder.parse(); } catch (Exception ex) { throw new NestedIOException(); } finally { ErrorContext.instance().reset(); } } // 如果手动配置了mapperLocations(即配置了mapper.xml文件位置) if (this.mapperLocations != null) { if (this.mapperLocations.length == 0) { // 为空不进行操作,只会打印debug日志告诉mapperLocations为空 } else { for (Resource mapperLocation : this.mapperLocations) { // 过滤为null的资源对象 if (mapperLocation == null) { continue; } try { // 和解析<mapper/>标签引入的mapper.xml文件一样 // 使用XMLMapperBuilder构造器解析mapper.xml文件 XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments()); // 这里需要注意的是,如果统一个mapper在config.xml中被引入了 // 在mapperLocations对象也指定了,会导致mapper.xml文件被解析两次 // 添加ResultMap时判断存在重复值将会抛出异常 // 即要么采用在config.xml中配置mapper.xml文件,要么采用 // 只配置mapperLocations对象值,两者混合用容易出错 xmlMapperBuilder.parse(); } catch (Exception e) { throw new NestedIOException(); } finally { ErrorContext.instance().reset(); } } } } // 前面的流程已经解析完,可以把这个方法分为两个流程,第一个流程则是添加 // 在Spring配置中配置的属性值,第二个流程则是接入原生mybatis配置方式 // 在平常的使用中,原生的mybatis配置方式基本不适用,而是全部使用Spring // 方式管理配置,因此建议不配置config.xml文件,直接配置mapperLocations // 这些属性,可以更直观的看到自己的配置; // 像原生Mybatis一样,解析完XML文件则使用configuration对象构造 // SqlSessionFactory对象,默认是DefaultSqlSessionFactory类型 return this.sqlSessionFactoryBuilder.build(targetConfiguration); } @Override public void onApplicationEvent(ApplicationEvent event) { if (failFast && event instanceof ContextRefreshedEvent) { // 当上下文刷新完毕后,将会再次加载mybatis前一次失败的配置 this.sqlSessionFactory.getConfiguration().getMappedStatementNames(); } } }到此,我们已经达到了原生mybatis使用时获得加载完所有信息的sqlSessionFactory工厂类,接下来看一下该集成方式的下一步。
我们使用原生的Mybatis时都要从sqlSessionFactory调用openSession获得SqlSession对象,然后再用这个SqlSession对象调用getMapper方法从configuration对象中的mapperRegistry获得已经被MapperProxyFactory工厂反向代理过的mapper接口对象。接着调用mapper接口的方法即可。
那么Spring集成的MapperFactoryBean使用方式和原生的有何不同?接下来一起看看。
applicationContext.xml配置:
<bean id="demoMapper" class="org.mybatis.spring.mapper.MapperFactoryBean"> <property name="mapperInterface" value="com.iboxpay.mapper.DemoMapper"/> <property name="sqlSessionFactory" ref="sqlSessionFactory"/> </bean>可以看到该配置将刚刚构造完成的sqlSessionFactory赋值给MapperFactoryBean,并且指定了对应的mapper。接下来看到其关键源码部分:
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> { // 被封装的接口类型,FactoryBean调用getObject将会返回这个类型Bean private Class<T> mapperInterface; private boolean addToConfig = true; @Override protected void checkDaoConfig() { // 可以看到这个方法是重写的,因此父类肯定有操作,父类的流程我们等下再去分析 super.checkDaoConfig(); // 封装的接口类型不能为空 notNull(this.mapperInterface, "..."); // 在父类中已经设置过了sqlSession,因此这里的sqlSession是必有值的 Configuration configuration = getSqlSession().getConfiguration(); // 默认会把mapperInterface添加到configuration对象中的mapperRegistry对象中 if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) { try { // 同时添加到configuration中,保持一致 configuration.addMapper(this.mapperInterface); } catch (Exception e) { throw new IllegalArgumentException(e); } finally { ErrorContext.instance().reset(); } } } // 其它方法略过,只看这个方法 @Override public T getObject() throws Exception { // 实际还是会调用configuration的getMapper方法,从mapperRegistry中获取 // mapper接口的代理对象,类型是MapperProxy return getSqlSession().getMapper(this.mapperInterface); } }看到这个类肯定一脸懵逼,其父类中的操作将会解除我们的疑惑。
这是Spring为持久化框架设置的一个抽象类,这个类实现了InitializingBean接口,其关键源码如下:
public abstract class DaoSupport implements InitializingBean { @Override public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException { // 检查Dao类的配置 checkDaoConfig(); // 交给子类来实现初始化dao的操作 try { // 方法交给子类去实现 initDao(); } catch (Exception ex) { throw new BeanInitializationException(); } } protected abstract void checkDaoConfig() throws IllegalArgumentException; protected void initDao() throws Exception { } }这个类集成了Spring为持久化框架实现的抽象类DaoSupport,让我们看看该类的具体实现源码:
public abstract class SqlSessionDaoSupport extends DaoSupport { private SqlSessionTemplate sqlSessionTemplate; public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { // 这个方法是搭配配置sqlSessionFactory使用的,如果配置了sqlSessionFactory // 那么这个方法将一定会被调用,因为Spring工厂初始化Bean的时候会递归其setter // 方法,如果工厂里有这个类型的Bean,将会被赋值进来; // 如果本类的sqlSessionTemplate为空或者sqlSessionTemplate中的 // sqlSessionFactory对象和传入的不一致,则调用createSqlSessionTemplate方法 // 创建sqlSessionTemplate对象 if (this.sqlSessionTemplate == null || sqlSessionFactory != this .sqlSessionTemplate.getSqlSessionFactory()) { this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory); } } protected SqlSessionTemplate createSqlSessionTemplate( SqlSessionFactory sqlSessionFactory) { // sqlSessionFactory是Spring工厂传入的Bean return new SqlSessionTemplate(sqlSessionFactory); } public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate){ // 如果Spring工厂中创建了sqlSessionTemplate对象,则会调用进来并赋值 this.sqlSessionTemplate = sqlSessionTemplate; } @Override protected void checkDaoConfig() { // 实例化之后,将会判断sqlSessionTemplate不为空 notNull(this.sqlSessionTemplate, "Property 'sqlSessionFactory' or"+ " 'sqlSessionTemplate' are required"); } }详细说明看代码中的注释。截止到这,其实Spring工厂已经获得了目标mapper接口的代理对象工厂,并且也已经交给了Spring工厂管理,等使用@Autowired等注解时便会自动调用MapperFactory-Bean工厂Bean的getObject方法,像原生Mybatis使用方法一样,调用getMapper获得mapper接口的代理对象MapperProxy。
前面我们已经看到了,在SqlSessionDaoSupport类中,Mybatis根据Spring工厂初始化Bean的流程中,使用setter机制设置了sqlSessionFactory对象,并初始化了一个SqlSessionTemplate对象,并且如果Spring工厂中有这个对象,又再会调用settter方法设置SqlSessionTemplate。
因此这个类在mybatis集成到spring中是绝对需要分析一下的,接下来看看SqlSessionTemplate的关键源码部分是如何实现的:
public class SqlSessionTemplate implements SqlSession, DisposableBean { // 配置的sqlSessionFactory对象 private final SqlSessionFactory sqlSessionFactory; // 配置的执行类型,有Simple,有Batch等 private final ExecutorType executorType; // 内部实际代理的SqlSession对象,调用selectOne和selectList这些方法 // 实际将会调用到这个对象来,会使用内部类SqlSessionInterceptor增加方法 private final SqlSession sqlSessionProxy; public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { this.sqlSessionFactory = sqlSessionFactory; this.executorType = executorType; this.exceptionTranslator = exceptionTranslator; // 根据SqlSessionFactory和SqlSession接口使用SqlSessionInterceptor // 代理对象增加 this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[] { SqlSession.class }, new SqlSessionInterceptor()); } // 下面是一系列SqlSession的接口方法,都是调用sqlSessionProxy的相应方法,略 ... // 加强sqlSessionProxy对象的内部代理类,使用JDK动态代理增强,因为SqlSession // 本身就是一个接口 private class SqlSessionInterceptor implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 根据sqlSessionFactory和executorType对象获取session,方法内部实际上 // 也调用了sqlSessionFactory的openSession方法,只是前面会判断当前 // 是否含有sqlSession对象,因此这里的SqlSession对象还是DefaultSqlSession SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this .sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this .exceptionTranslator); try { // 调用原代理方法,即外部的mapper接口方法实际上调用的还是sqlSession // 对象的方法 Object result = method.invoke(sqlSession, args); // 如果事务没有被Spring事务管理器管理则调用sqlSession.commit手动提交 // 否则交给Spring事务管理去操作事务流程 if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) { // 手动提交事务 sqlSession.commit(true); } // 返回结果 return result; } catch (Throwable t) { Throwable unwrapped = unwrapThrowable(t); if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) { // 出错则关闭会话 closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); sqlSession = null; Throwable translated = SqlSessionTemplate .this.exceptionTranslator.translateExceptionIfPossible( (PersistenceException) unwrapped); if (translated != null) { unwrapped = translated; } } throw unwrapped; } finally { // 关闭会话 if (sqlSession != null) { closeSqlSession(sqlSession, SqlSessionTemplate .this.sqlSessionFactory); } } } } }在spring框架使用具体的mapper类时,将会通过MapperFactoryBean的getObject来获得具体的mapper对象,并注入使用的类中,getSqlSession().getMapper(this.mapperInterface)是该方法中的具体代码,到此,基本上就完成了mybatis原生过程的getMapper,接下来只需要调用mapper中的方法即可。
也可以看到SqlSessionTemplate类中实现了SqlSession接口,而具体的实现方法则是直接调用sqlSessionProxy的方法,并且为SqlSessionTemplate的每次方法调用也添加了一层代理。
到此,使用MapperFactoryBean方式和spring集成原理便结束了。