轻松拿到20k?吃透Spring整合MyBatis的原理,面试官瞬间被征服

今日分享开始啦,请大家多多指教~

在MyBatis篇内容我们来给大家详细介绍下Spring是如何整合MyBatis的。让大家彻底掌握MyBatis的底层设计原理及实现。

MyBatis整合Spring原理

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

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

  1. SqlSessionFactory在哪创建的。
  2. SqlSession在哪创建的。
  3. 代理类在哪创建的。

1 SqlSessionFactory

首先我们来看下在MyBatis整合Spring中SqlSessionFactory的创建过程,查看这步的入口在Spring的配置文件中配置整合的标签中

我们进入SqlSessionFactoryBean中查看源码发现,其实现了InitializingBean 、FactoryBean、ApplicationListener 三个接口

1.1 afterPropertiesSet

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

可以发现在afterPropertiesSet中直接调用了buildSqlSessionFactory方法来实现 sqlSessionFactory 对象的创建

在afterPropertiesSet方法中完成了SqlSessionFactory对象的创建,已经相关配置文件和映射文件的解析操作。

方法小结一下:通过定义一个实现了InitializingBean接口的SqlSessionFactoryBean类,里面有一个afterPropertiesSet()方法会在bean的属性值设置完的时候被调用。Spring在启动初始化这个Bean的时候,完成了解析和工厂类的创建工作。

1.2 getObject

另外SqlSessionFactoryBean实现了FactoryBean接口。

FactoryBean的作用是让用户可以自定义实例化Bean的逻辑。如果从BeanFactory中根据Bean的ID获取一个Bean,它获取的其实是FactoryBean的getObject()返回的对象。

也就是说,我们获取SqlSessionFactoryBean的时候,就会调用它的getObject()方法。

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

1.3 onApplicationEvent

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

2 SqlSession

2.1 DefaultSqlSession的问题

在前面介绍MyBatis的使用的时候,通过SqlSessionFactory的open方法获取的是DefaultSqlSession,但是在Spring中我们不能直接使用DefaultSqlSession,因为DefaultSqlSession是线程不安全的。所以直接使用会存在数据安全问题,针对这个问题的,在整合的MyBatis-Spring的插件包中给我们提供了一个对应的工具SqlSessionTemplate。

也就是在我们使用SqlSession的时候都需要使用try catch 块来处理

在整合Spring中通过提供的SqlSessionTemplate来简化了操作,提供了安全处理。

2.2 SqlSessionTemplate

在mybatis-spring的包中,提供了一个线程安全的SqlSession的包装类,用来替代SqlSession,这个类就是SqlSessionTemplate。因为它是线程安全的,所以可以在所有的DAO层共享一个实例(默认是单例的)。

SqlSessionTemplate虽然跟DefaultSqlSession一样定义了操作数据的selectOne()、selectList()、insert()、update()、delete()等所有方法,但是没有自己的实现,全部调用了一个代理对象的方法。

那么SqlSessionProxy是怎么来的呢?在SqlSessionTemplate的构造方法中有答案

通过上面的介绍那么我们应该进入到 SqlSessionInterceptor 的 invoke 方法中。

上面的代码虽然看着比较复杂,但是本质上就是下面的操作

getSqlSession方法中的关键代码:

执行流程

总结一下:因为DefaultSqlSession自己做不到每次请求调用产生一个新的实例,我们干脆创建一个代理类,也实现SqlSession,提供跟DefaultSqlSession一样的方法,在任何一个方法被调用的时候都先创建一个DefaultSqlSession实例,再调用被代理对象的相应方法。

MyBatis还自带了一个线程安全的SqlSession实现:SqlSessionManager,实现方式一样,如果不集成到Spring要保证线程安全,就用SqlSessionManager。

2.3 SqlSessionDaoSupport

通过上面的介绍我们清楚了在Spring项目中我们应该通过SqlSessionTemplate来执行数据库操作,那么我们就应该首先将SqlSessionTemplate添加到IoC容器中,然后我们在Dao通过@Autowired来获取:

然后我们可以看看SqlSessionDaoSupport中的代码

如此一来在Dao层我们就只需要继承 SqlSessionDaoSupport就可以通过getSqlSession方法来直接操作了。

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

在DAO层执行SQL格式如下:

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

然后让我们的DAO层实现类继承BaseDao并且实现我们的Mapper接口。实现类需要加上@Repository的注解。

在实现类的方法里面,我们可以直接调用父类(BaseDao)封装的selectOne()方法,那么它最终会调用sqlSessionTemplate的selectOne()方法。

然后在需要使用的地方,比如Service层,注入我们的实现类,调用实现类的方法就行了。我们这里直接在单元测试类DaoSupportTest.java里面注入:

最终会调用到DefaultSqlSession的方法。

2.4 MapperScannerConfigurer

上面我们介绍了SqlSessionTemplate和SqlSessionDaoSupport,也清楚了他们的作用,但是我们在实际开发的时候,还是能够直接获取到 Mapper 的代理对象,并没有创建Mapper的实现类,这个到底是怎么实现的呢?这个我们就要注意在整合MyBatis的配置文件中除了SqlSessionFactoryBean以外我们还设置了一个MapperScannerConfigurer,我们来分析下这个类。

首先是MapperScannerConfigurer的继承结构

MapperScannerConfigurer实现了
BeanDefinitionRegistryPostProcessor接口。
BeanDefinitionRegistryPostProcessor 是BeanFactoryPostProcessor的子类,里面有一个
postProcessBeanDefinitionRegistry()方法。

实现了这个接口,就可以在Spring创建Bean之前,修改某些Bean在容器中的定义。Spring创建Bean之前会调用这个方法。

上面代码的核心是 scan方法

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

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

2.5 MapperFactoryBean

为什么要注册成它呢?那注入使用的时候,也是这个对象,这个对象有什么作用?首先来看看他们的类图结构

从类图中我们可以看到MapperFactoryBean继承了SqlSessionDaoSupport,那么每一个注入Mapper的地方,都可以拿到SqlSessionTemplate对象了。然后我们还发现MapperFactoryBean实现了 FactoryBean接口,也就意味着,向容器中注入MapperFactoryBean对象的时候,本质上是把getObject方法的返回对象注入到了容器中,

它并没有直接返回一个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处理流程。

核心对象:

最后来总结下在MyBatis的源码中使用到的相关设计模式:

好了,Spring整合MyBatis的原理分析就给大家介绍到这里。

今日份分享已结束,请大家多多包涵和指点!

声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2021年5月1日
下一篇 2021年5月1日

相关推荐