MyBatis整合Spring原理分析

1. MyBatis整合Spring实现

http://mybatis.org/spring/zh/index.html
我们先来实现MyBatis和Spring的整合操作。
MyBatis整合Spring原理分析_第1张图片

1.1添加相关的依赖
<dependency>
	<groupId>org.mybatisgroupId>
	<artifactId>mybatis-springartifactId>
	<version>2.0.4version>
dependency>
<dependency>
	<groupId>org.springframeworkgroupId>
	<artifactId>spring-contextartifactId>
	<version>5.1.6.RELEASEversion>
dependency>
<dependency>
	<groupId>org.springframeworkgroupId>
	<artifactId>spring-ormartifactId>
	<version>5.1.6.RELEASEversion>
dependency>
<dependency>
	<groupId>org.springframeworkgroupId>
	<artifactId>spring-testartifactId>
	<version>5.1.6.RELEASEversion>
dependency>
<dependency>
1.2 配置文件

我们将MyBatis整合到Spring中,那么原来在MyBatis的很多配置我们都可以在Spring的配置文件中 设置,我们可以给MyBatis的配置文件设置为空


DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

添加Spring的配置文件,并在该文件中实现和Spring的整合操作

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">

<context:property-placeholder location="classpath:db.properties"/>

<context:component-scan base-package="com.gupaoedu"/>


<bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource" >
<property name="driverClassName" value="${jdbc.driver}">property>
<property name="url" value="${jdbc.url}">property>
<property name="username" value="${jdbc.username}">property>
<property name="password" value="${jdbc.password}">property>
bean>

<bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sqlSessionFactoryBean" >

<property name="dataSource" ref="dataSource"/>

<property name="configLocation" value="classpath:mybatis-config- spring.xml"/>

<property name="mapperLocations" value="classpath:mapper/*.xml" />

<property name="typeAliasesPackage" value="com.gupaoedu.domain" />


bean>

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer" >
<property name="basePackage" value="com.gupaoedu.mapper"/>
bean>
beans>

2.整合Spring的原理

把MyBatis集成到Spring里面,是为了进一步简化MyBatis的使用,所以只是对MyBatis做了一些封装,并没有替换MyBatis的核心对象。也就是说:MyBatis jar包中的SqlSessionFactory、SqlSession、MapperProxy这些类都会用到。mybatis-spring.jar里面的类只是做了一些包装或者桥梁的工作。

只要我们弄明白了这三个对象是怎么创建的,也就理解了Spring继承MyBatis的原理。我们把它分成三步:

  • SqlSessionFactory在哪创建的。
  • SqlSession在哪创建的。
  • 代理类在哪创建的。
2.1 SqlSessionFactory

首先我们来看下在MyBatis整合Spring中SqlSessionFactory的创建过程,查看这步的入口在Spring 的配置文件中配置整合的标签中
MyBatis整合Spring原理分析_第2张图片
我们进入SqlSessionFactoryBean中查看源码发现,其实现了InitializingBean 、FactoryBean、ApplicationListener 三个接口
MyBatis整合Spring原理分析_第3张图片
对于这三个接口,学过Spring生命周期的小伙伴应该清楚他们各自的作用

项目 Value Value
接口 方法 作用
FactoryBean getObject() 返回由FactoryBean创建的Bean实例
InitializingBean afterPropertiesSet() bean属性初始化完成后添加操作
ApplicationListener onApplicationEvent() 对应用的事件进行监听
2.1.1 afterPropertiesSet

我们首先来看下 afterPropertiesSet 方法中的逻辑

 public SqlSessionFactory getObject() throws Exception {
        if (this.sqlSessionFactory == null) {
            this.afterPropertiesSet();
        }
        return this.sqlSessionFactory;
}

可以发现在afterPropertiesSet中直接调用了buildSqlSessionFactory方法来实现 sqlSessionFactory对象的创建
MyBatis整合Spring原理分析_第4张图片
方法小结一下:通过定义一个实现了InitializingBean接口的SqlSessionFactoryBean类,里面有一个afterPropertiesSet()方法会在bean的属性值设置完的时候被调用。Spring在启动初始化这个Bean的时候,完成了解析和工厂类的创建工作。

2.1.2 getObject

另外SqlSessionFactoryBean实现了FactoryBean接口。
FactoryBean的作用是让用户可以自定义实例化Bean的逻辑。如果从BeanFactory中根据Bean的ID获取一个Bean,它获取的其实是FactoryBean的getObject()返回的对象。
也就是说,我们获取SqlSessionFactoryBean的时候,就会调用它的getObject()方法。

public SqlSessionFactory getObject() throws Exception {
 if (this.sqlSessionFactory == null) {
		this.afterPropertiesSet();
 }
 return this.sqlSessionFactory;

getObject方法中的逻辑就非常简单,返回SqlSessionFactory对象,如果SqlSessionFactory对象为 空的话就又调用一次afterPropertiesSet来解析和创建一次。

2.1.3 onApplicationEvent

实现ApplicationListener接口让SqlSessionFactoryBean有能力监控应用发出的一些事件通知。比如 这里监听了ContextRefreshedEvent(上下文刷新事件),会在Spring容器加载完之后执行。这里做的 事情是检查ms是否加载完毕。

public void onApplicationEvent(ApplicationEvent event) {
if (this.failFast && event instanceof ContextRefreshedEvent) { 
	this.sqlSessionFactory.getConfiguration().getMappedStatementNames();
	}
}

2.2SqlSession

2.2.1 DefaultSqlSession的问题

在前面介绍MyBatis的使用的时候,通过SqlSessionFactory的open方法获取的是
DefaultSqlSession,但是在Spring中我们不能直接使用DefaultSqlSession,因为DefaultSqlSession是 线程不安全的。所以直接使用会存在数据安全问题,针对这个问题的,在整合的MyBatis-Spring的插件包中给我们提供了一个对应的工具SqlSessionTemplate。
MyBatis整合Spring原理分析_第5张图片
https://mybatis.org/mybatis-3/zh/getting-started.html
MyBatis整合Spring原理分析_第6张图片
也就是在我们使用SqlSession的时候都需要使用try catch 块来处理

try (SqlSession session = sqlSessionFactory.openSession()) {
	// 你的应用逻辑代码
}
	// 或者
SqlSession session = null;
 try {
	session = sqlSessionFactory.openSession();
	// 你的应用逻辑代码
}finally{
	session.close();
}
2.2.2 SqlSessionTemplate

在mybatis-spring的包中,提供了一个线程安全的SqlSession的包装类,用来替代SqlSession,这个类就是SqlSessionTemplate。因为它是线程安全的,所以可以在所有的DAO层共享一个实例(默认是单例的)。
MyBatis整合Spring原理分析_第7张图片
SqlSessionTemplate虽然跟DefaultSqlSession一样定义了操作数据的selectOne()、selectList()、insert()、update()、delete()等所有方法,但是没有自己的实现,全部调用了一个代理对象的方法。
MyBatis整合Spring原理分析_第8张图片
那么SqlSessionProxy是怎么来的呢?在SqlSessionTemplate的构造方法中有答案

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;
        // 创建了一个 SqlSession 接口的代理对象, 调用SqlSessionTemplate中的 selectOne()方法,其实就是调用
		// SqlSessionProxy的 selectOne() 方法,然后执行的是 SqlSessionInterceptor里面的invoke方法
        this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor());
    }

通过上面的介绍那么我们应该进入到 SqlSessionInterceptor 的 invoke 方法中。
MyBatis整合Spring原理分析_第9张图片
上面的代码虽然看着比较复杂,但是本质上就是下面的操作

SqlSession session = null; 
try {
	session = sqlSessionFactory.openSession();
	// 你的应用逻辑代码
}finally{
	session.close();
}

getSqlSession方法中的关键代码:
MyBatis整合Spring原理分析_第10张图片

MyBatis整合Spring原理分析_第11张图片
总结一下:因为DefaultSqlSession自己做不到每次请求调用产生一个新的实例,我们干脆创建一个代理 类,也实现SqlSession,提供跟DefaultSqlSession一样的方法,在任何一个方法被调用的时候都先创建 一个DefaultSqlSession实例,再调用被代理对象的相应方法。
MyBatis还自带了一个线程安全的SqlSession实现:SqlSessionManager,实现方式一样,如果不集成到Spring要保证线程安全,就用SqlSessionManager。

2.2.3 SqlSessionDaoSupport

通过上面的介绍我们清楚了在Spring项目中我们应该通过SqlSessionTemplate来执行数据库操作,那么我们就应该首先将SqlSessionTemplate添加到IoC容器中,然后我们在Dao通过@Autowired来获取具体步骤参考官网:
http://mybatis.org/spring/zh/sqlsession.html
MyBatis整合Spring原理分析_第12张图片
然后我们可以看看SqlSessionDaoSupport中的代码
MyBatis整合Spring原理分析_第13张图片
如此一来在Dao层我们就只需要继承 SqlSessionDaoSupport就可以通过getSqlSession方法来直接操作了。

public abstract class SqlSessionDaoSupport extends DaoSupport {

private SqlSessionTemplate sqlSessionTemplate;

public SqlSession getSqlSession() { 
	return this.sqlSessionTemplate;
}
// 其他代码省略

也就是说我们让DAO层(实现类)继承抽象类SqlSessionDaoSupport,就自动拥有了getSqlSession() 方法。调用getSqlSession()就能拿到共享的SqlSessionTemplate。
在DAO层执行SQL格式如下

getSqlSession().selectOne(statement, parameter); 
getSqlSession().insert(statement); 
getSqlSession().update(statement);
getSqlSession().delete(statement);

还是不够简洁。为了减少重复的代码,我们通常不会让我们的实现类直接去继承
SqlSessionDaoSupport,而是先创建一个BaseDao继承SqlSessionDaoSupport。在BaseDao里面封装 对数据库的操作,包括selectOne()、selectList()、insert()、delete()这些方法,子类就可以直接调用。

public	class BaseDao extends SqlSessionDaoSupport {
	//使用sqlSessionFactory @Autowired
	private SqlSessionFactory sqlSessionFactory;

	@Autowired
	public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory){ 
		super.setSqlSessionFactory(sqlSessionFactory);
	}

	public Object selectOne(String statement, Object parameter) { 
		return getSqlSession().selectOne(statement, parameter);
	}
// 后面省略
2.2.4 MapperScannerConfigurer

上面我们介绍了SqlSessionTemplate和SqlSessionDaoSupport,也清楚了他们的作用,但是我们在实际开发的时候,还是能够直接获取到 Mapper 的代理对象,并没有创建Mapper的实现类,这个到底是怎么实现的呢?这个我们就要注意在整合MyBatis的配置文件中除了SqlSessionFactoryBean以外我们还设置了一个MapperScannerConfigurer,我们来分析下这个类
首先是MapperScannerConfigurer的继承结构
MyBatis整合Spring原理分析_第14张图片
MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口。
BeanDefinitionRegistryPostProcessor 是BeanFactoryPostProcessor的子类,里面有一个postProcessBeanDefinitionRegistry()方法。
实现了这个接口,就可以在Spring创建Bean之前,修改某些Bean在容器中的定义。Spring创建Bean之 前会调用这个方法。

 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.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
        if (StringUtils.hasText(this.lazyInitialization)) {
            scanner.setLazyInitialization(Boolean.valueOf(this.lazyInitialization));
        }
        // 根据上面的配置生成对应的 过滤器
        scanner.registerFilters();
        // 开始扫描basePackage字段中指定的包及其子包
        scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
    }

上面代码的核心是 scan方法

 public int scan(String... basePackages) {
        int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
        this.doScan(basePackages);
        if (this.includeAnnotationConfig) {
            AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
        }

        return this.registry.getBeanDefinitionCount() - beanCountAtScanStart;
    }

然后会调用子类ClassPathMapperScanner 中的 doScan方法

 public Set<BeanDefinitionHolder> doScan(String... basePackages) {
 // 调用父类中的 doScan方法 扫描所有的接口,把接口全部添加到beanDefinitions中。
        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的时候,BeanClass被改为MapperFactoryBean 
            this.processBeanDefinitions(beanDefinitions);
        }

        return beanDefinitions;
    }

MyBatis整合Spring原理分析_第15张图片
因为一个接口是没法创建实例对象的,这时我们就在创建对象之前将这个接口类型指向了一个具体 的普通Java类型,MapperFactoryBean .也就是说,所有的Mapper接口,在容器里面都被注册成一个支持泛型的MapperFactoryBean了。然后在创建这个接口的对象时创建的就是MapperFactoryBean 对象。

2.2.5 MapperFactoryBean

为什么要注册成它呢?那注入使用的时候,也是这个对象,这个对象有什么作用?首先来看看他们的 类图结构
MyBatis整合Spring原理分析_第16张图片
从类图中我们可以看到MapperFactoryBean继承了SqlSessionDaoSupport,那么每一个注入Mapper的地方,都可以拿到SqlSessionTemplate对象了。然后我们还发现MapperFactoryBean实现了FactoryBean接口,也就意味着,向容器中注入MapperFactoryBean对象的时候,本质上是把getObject方法的返回对象注入到了容器中

 public T getObject() throws Exception {
        return this.getSqlSession().getMapper(this.mapperInterface);
 }

它并没有直接返回一个MapperFactoryBean。而是调用了SqlSessionTemplate的getMapper()方法。SqlSessionTemplate的本质是一个代理,所以它最终会调用DefaultSqlSession的getMapper()方法。后 面的流程我们就不重复了。也就是说,最后返回的还是一个JDK的动态代理对象。
所以最后调用Mapper接口的任何方法,也是执行MapperProxy的invoke()方法,后面的流程就跟编程式 的工程里面一模一样了

总结一下,Spring是怎么把MyBatis继承进去的?

  1. 提供了SqlSession的替代品SqlSessionTemplate,里面有一个实现了实现了InvocationHandler的内部SqlSessionInterceptor,本质是对SqlSession的代理。
  2. 提供了获取SqlSessionTemplate的抽象类SqlSessionDaoSupport。
  3. 扫描Mapper接口,注册到容器中的是MapperFactoryBean,它继承了SqlSessionDaoSupport,可 以获得SqlSessionTemplate。
  4. 把Mapper注入使用的时候,调用的是getObject()方法,它实际上是调用了SqlSessionTemplate的getMapper()方法,注入了一个JDK动态代理对象。
  5. 执行Mapper接口的任意方法,会走到触发管理类MapperProxy,进入SQL处理流程。

核心对象:

对象 生命周期
SqlSessionTemplate Spring中SqlSession的替代品,是线程安全的
SqlSessionDaoSupport 用于获取SqlSessionTemplate
SqlSessionInterceptor(内部类) 代理对象,用来代理DefaultSqlSession,在SqlSessionTemplate中使用
MapperFactoryBean 代理对象,继承了SqlSessionDaoSupport用来获取SqlSessionTemplate
SqlSessionHolder 控制SqlSession和事务
设计模式总结
设计模式
工厂模式 SqlSessionFactory、ObjectFactory、MapperProxyFactory
建造者模式 XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuidler
单例模式 SqlSessionFactory、Configuration、ErrorContext
代理模式 绑定:MapperProxy延迟加载:ProxyFactory 插件:PluginSpring集成MyBaits: SqlSessionTemplate的内部SqlSessionInterceptorMyBatis自带连接池:PooledConnection日志打印:ConnectionLogger、StatementLogger
适配器模式 Log,对于Log4j、JDK logging这些没有直接实现slf4j接口的日志组件,需要适配器
模板方法 BaseExecutor、SimpleExecutor、BatchExecutor、ReuseExecutor
装饰器模式 LoggingCache、LruCache对PerpetualCacheCachingExecutor对其他Executor
责任链模式 Interceptor、InterceptorChain

你可能感兴趣的:(持久层框架,mybatis,spring,java)