MyBatis拦截器的执行顺序引发的MyBatis源码分析

时间:2021-04-04 16:55:04

你猜一下哪个先执行?反正不要按常规来。

1 <plugins>
2     <plugin interceptor="com.Interceptor1"></plugin>
3     <plugin interceptor="com.Interceptor2"></plugin>
4 </plugins>

之前看有的博客分析源码,都没提到这一点。之前我只是用一下而已,这个顺序测试一下其实结论也很容易获得,但是我有一种看源码的屎命感。MyBatis还算人性化提供了拦截器,iBatis里面就没有了,不过也可以实现。这里要探究拦截器的源码就不得不提到MyBatis的源码,也就是执行流程了。这要是摊开说就有点大了,为了写好这篇,我决定今天晚上回去不打dota了,贡献真实够大的了。

MyBatis的作用

名义上来说MyBatis是一个半ORM框架,用了一个半字是因为MyBatis并没有完全起到一个ORM框架的作用(比如Hibernate),还有一半工作是需要我们参与进来——编写SQL语句。MyBatis替我们干的活是啥?帮我们把参数和配置化SQL语句映射成数据库中真正执行的SQL,然后把结果帮我们封装好,并返回回来。好处很容已说明,配置灵活,增强开发人员对SQL语句的控制,减少了冗余的对象封装工作。

官方的说法如下:

MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。

MyBatis的架构

沿着上面说到的,我们接下来可以看一下MyBatis用了什么架构来完成上面的工作。要注意,talk is cheap,实际上的话还要牵涉很多工作(比如Session,事务等)。在网上看了一些描述MyBatis架构的图,看来一晚上不打dota是不可能了呀。

功能上的架构:

MyBatis拦截器的执行顺序引发的MyBatis源码分析

源码中的结构:

MyBatis拦截器的执行顺序引发的MyBatis源码分析

架构看上去并不复杂,按三层来分的。之前看的很少,如果你是和我一样的小白话可以一起来看下,从头看起吧,接口和配置文件先开始。

最简单的方式开始MyBatis

这里最简单的意思是,我们先抛开Spring,只在一个简单的Maven项目中使用MyBatis,看看它是如何运行的。先起一个简单的Maven项目并加上MyBatis的依赖。

已经做好了但是,额,写起来估计可以新开一篇了。

单独使用MyBatis代码

其实核心代码只有两个,第一个是SessionUtils用于提供Session,第二个是使用Session进行CRUD操作的代码

 1 public class SessionUtils {
 2     private static SqlSessionFactory sessionFactory;
 3     static {
 4         try {
 5             // 使用MyBatis提供的Resources类加载mybatis的配置文件
 6             Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
 7             // 构建sqlSession的工厂
 8             sessionFactory = new SqlSessionFactoryBuilder().build(reader);
 9         } catch (Exception e) {
10             e.printStackTrace();
11         }
12     }
13 
14     /**
15      * 获取SqlSession
16      * @return SqlSession
17      */
18     public static SqlSession getSession() {
19         return sessionFactory.openSession();
20     }
21 }

 

 

 1 @Test
 2 public void testInsert() {
 3     SqlSession session = null;
 4     try {
 5         session = SessionUtils.getSession();
 6         StudentMapper studentMapper = session.getMapper(StudentMapper.class);
 7         Long affectedLines = studentMapper.insert(build());
 8         System.out.println("affectedLines = " + (affectedLines == null ? 0 : affectedLines));
 9         session.commit();
10     } catch (Exception e) {
11         e.printStackTrace();
12         if(session != null) {
13             session.rollback();
14         }
15     } finally {
16         if(session != null) {
17             session.close();
18         }
19     }
20 }

SqlSessionFactory的生成

观察这两段代码引出了两个核心的类:SqlSessionFactorySession

SqlSessionFactoryBuilder:单纯的就是为了创建SqlSessionFactory,功能很单一。

 1 // 这是一个普类, 而不是接口, 这里把方法都隐去了
 2 public class SqlSessionFactoryBuilder {
 3   public SqlSessionFactory build(Reader reader);
 4   public SqlSessionFactory build(Reader reader, String environment);
 5   public SqlSessionFactory build(Reader reader, Properties properties);
 6   public SqlSessionFactory build(Reader reader, String environment, Properties properties);
 7   public SqlSessionFactory build(InputStream inputStream);
 8   public SqlSessionFactory build(InputStream inputStream, String environment);
 9   public SqlSessionFactory build(InputStream inputStream, Properties properties);
10   public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties);
11   public SqlSessionFactory build(Configuration config);
12 }

 通过阅读源码发现生成SqlSessionFactory的简要步骤如下:

MyBatis拦截器的执行顺序引发的MyBatis源码分析

通过ReaderXMLConfigBuilderConfigurationDefaultSqlSessionFactory共同协作把SqlSessionFactory弄出来了。如果正式一点,用时序图画出来就是这样的:

 MyBatis拦截器的执行顺序引发的MyBatis源码分析

承载MySql所有的配置Configuration类

下面重点关注的类是Configuration。在org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration中解析Configuration的代码如下,看完之后你就会觉得很亲切:

 1 private void parseConfiguration(XNode root) {
 2   try {
 3     //issue #117 read properties first
 4     propertiesElement(root.evalNode("properties"));
 5     Properties settings = settingsAsProperties(root.evalNode("settings"));
 6     loadCustomVfs(settings);
 7     typeAliasesElement(root.evalNode("typeAliases"));
 8     pluginElement(root.evalNode("plugins"));
 9     objectFactoryElement(root.evalNode("objectFactory"));
10     objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
11     reflectorFactoryElement(root.evalNode("reflectorFactory"));
12     settingsElement(settings);
13     // read it after objectFactory and objectWrapperFactory issue #631
14     environmentsElement(root.evalNode("environments"));
15     databaseIdProviderElement(root.evalNode("databaseIdProvider"));
16     typeHandlerElement(root.evalNode("typeHandlers"));
17     mapperElement(root.evalNode("mappers"));
18   } catch (Exception e) {
19     throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
20   }
21 }

 话说Java的这个着色也太稀烂了。typeAliases,typeHandlers和mappers是不是很熟悉。然后Configuration的类图是:

MyBatis拦截器的执行顺序引发的MyBatis源码分析

再返回Configuration的时候,mybatis-config.xml中的各个元素都已经解析出来了:

MyBatis拦截器的执行顺序引发的MyBatis源码分析

 

Environment:数据源和事务管理器

Environment就是配置数据源的地方,在mybatis-config.xml中是这样的:

 1 <!-- 配置mybatis运行环境 -->
 2 <environments default="development">
 3     <environment id="development">
 4         <!-- type="JDBC" 代表使用JDBC的提交和回滚来管理事务 -->
 5         <transactionManager type="JDBC" />
 6         <!-- mybatis提供了3种数据源类型,分别是:POOLED,UNPOOLED,JNDI -->
 7         <!-- POOLED 表示支持JDBC数据源连接池 -->
 8         <!-- UNPOOLED 表示不支持数据源连接池 -->
 9         <!-- JNDI 表示支持外部数据源连接池 -->
10         <dataSource type="POOLED">
11             <property name="driver" value="${jdbc.driver}" />
12             <property name="url" value="${jdbc.url}" />
13             <property name="username" value="${jdbc.username}" />
14             <property name="password" value="${jdbc.password}" />
15         </dataSource>
16     </environment>
17 </environments>

 

MyBatis拦截器的执行顺序引发的MyBatis源码分析

 1 private void environmentsElement(XNode context) throws Exception {
 2   if (context != null) {
 3     if (environment == null) {
 4       environment = context.getStringAttribute("default");
 5     }
 6     for (XNode child : context.getChildren()) {
 7       String id = child.getStringAttribute("id");
 8       if (isSpecifiedEnvironment(id)) {
 9         TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
10         DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
11         DataSource dataSource = dsFactory.getDataSource();
12         Environment.Builder environmentBuilder = new Environment.Builder(id)
13             .transactionFactory(txFactory)
14             .dataSource(dataSource);
15         configuration.setEnvironment(environmentBuilder.build());
16       }
17     }
18   }
19 }