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


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

    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

    这里最简单的意思是,我们先抛开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的简要步骤如下:

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

     

    承载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的类图是:

    从这个图中可以看出Configuration分为两个部分:

    • 和数据库相关的部分
    • 和文档解析有关的部分

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

    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>

     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 }

    SqlSessionFactory类图

    session中的主要几个类如下:

    下面则是session相关的类图:

    除了Closeable是java API中的一个接口之外,其他的都是MyBatis中的类或者接口。有好几个类我还不怎么熟悉,比如SqlSession,SqlSessionManager和Executor。但是这里先不管,直接来看session是如何获取的。

  • 相关阅读:
    nodeName,nodeValue未知 xml 入库方案 The ElementTree iterparse Function
    如何:执行大型 XML 文档的流式转换 大XML文件解析入库的一个方法
    python curl_get-pip.py Installing with get-pip.py
    iptables List the rules in a chain or all chains
    goroutine 分析 协程的调度和执行顺序 并发写 run in the same address space 内存地址 闭包 存在两种并发 确定性 非确定性的 Go 的协程和通道理所当然的支持确定性的并发方式(
    数据库业界
    The MEAN stack is a modern replacement for the LAMP (Linux, Apache, MySQL, PHP/Python) stack
    Using Groovy To Import XML Into MongoDB
    虚拟机网络模式 桥接 网桥 交换机
    防止有内存泄漏并尽可能快地释放所有内存是内存管理的重要组成部分 长时间运行进程的内存泄漏
  • 原文地址:https://www.cnblogs.com/tuhooo/p/9188019.html
Copyright © 2020-2023  润新知