轻松拿到 20k?吃透 Spring 整合 MyBatis 的原理,面试官瞬间被征服
今日分享开始啦,请大家多多指教~
在 MyBatis 篇内容我们来给大家详细介绍下 Spring 是如何整合 MyBatis 的。让大家彻底掌握 MyBatis 的底层设计原理及实现。
MyBatis 整合 Spring 原理
把 MyBatis 集成到 Spring 里面,是为了进一步简化 MyBatis 的使用,所以只是对 MyBatis 做了一些封装,并没有替换 MyBatis 的核心对象。也就是说:MyBatis jar 包中的 SqlSessionFactory、SqlSession、MapperProxy 这些类都会用到。mybatis-spring.jar 里面的类只是做了一些包装或者桥梁的工作。
只要我们弄明白了这三个对象是怎么创建的,也就理解了 Spring 继承 MyBatis 的原理。我们把它分成三步:
SqlSessionFactory 在哪创建的。
SqlSession 在哪创建的。
代理类在哪创建的。
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 继承进去的?
提供了 SqlSession 的替代品 SqlSessionTemplate,里面有一个实现了实现了 InvocationHandler 的内部 SqlSessionInterceptor,本质是对 SqlSession 的代理。
提供了获取 SqlSessionTemplate 的抽象类 SqlSessionDaoSupport。
扫描 Mapper 接口,注册到容器中的是 MapperFactoryBean,它继承了 SqlSessionDaoSupport,可以获得 SqlSessionTemplate。
把 Mapper 注入使用的时候,调用的是 getObject()方法,它实际上是调用了 SqlSessionTemplate 的 getMapper()方法,注入了一个 JDK 动态代理对象。
执行 Mapper 接口的任意方法,会走到触发管理类 MapperProxy,进入 SQL 处理流程。
核心对象:
最后来总结下在 MyBatis 的源码中使用到的相关设计模式:
好了,Spring 整合 MyBatis 的原理分析就给大家介绍到这里。
今日份分享已结束,请大家多多包涵和指点!
评论