天天看点

Mybatis源码分析 反射模块以及设计模式在mybatis中的应用Mybatis反射模块的解读与分析orm框架查询数据过程一 反射模块的大概分析二、MyBatis 流程概述总结

Mybatis反射模块的解读与分析

在写之前我们先滤清orm查询的大概过程,然后通过每个模块去滤清思路

思路 :先理解各个类的结构,然后在查看相对应的源码

目录

  • Mybatis反射模块的解读与分析
  • orm框架查询数据过程
  • 一 反射模块的大概分析
            • 如果我们的相关类如果没有get()set()方法的话我们的Mybatis会把他自动生成
            • **Mybatis大概的执行思路:**
  • 二、MyBatis 流程概述
          • 建造者模式的理解与使用Demo
            • 建造者模式示例:
        • 建造者模式在mybatis中的应用
      • 接着说mybatis的流程
        • XMLConfigBuilder的初始化的过程
            • XMl的配置加载过程
      • Mybatis 的接口层
        • SqlSessionFactory
  • 总结

orm框架查询数据过程

Mybatis源码分析 反射模块以及设计模式在mybatis中的应用Mybatis反射模块的解读与分析orm框架查询数据过程一 反射模块的大概分析二、MyBatis 流程概述总结

提示:以下是本篇文章正文内容,下面案例可供参考

一 反射模块的大概分析

在分析之前我们先看一下下面这几个类,以及作用

反射是Mybatis 模块中类最多的模块,通过反射实现了 POJO 对象的实例化和POJO 的属性赋

值,相对 JDK 自带的反射功能,MyBatis 的反射模块功能更为强大,性能更高;反射模块关

键的几个类如下:

  • ObjectFactory:MyBatis 每次创建结果对象的新实例时,它都会使用对象工厂(ObjectFactory)

    去构建POJO;

  • ReflectorFactory:创建Reflector 的工厂类,Reflector 是MyBatis 反射模块的基础,每个Reflector

    对象都对应一个类,在其中缓存了反射操作所需要的类元信息;

  • ObjectWrapper:对对象的包装,抽象了对象的属性信息,他定义了一系列查询对象属性信

    息的方法,以及更新属性的方法 是对我们的对象进行赋值的 ;

    这个里面最重要的方法就是 set方法 BeanWrapper 里面的set方法就是用来赋值的

    代码位置

BeanWrapper impl ObjectWarpper方法
org.apache.ibatis.reflection.wrapper.BeanWrapper#set
org.apache.ibatis.reflection.wrapper.BeanWrapper#setBeanProperty 
private void setBeanProperty(PropertyTokenizer prop, Object object, Object value) {
    try {
        //拿到set方法
      Invoker method = metaClass.getSetInvoker(prop.getName());
      Object[] params = {value};
      try {
        method.invoke(object, params);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    } catch (Throwable t) {
      throw new ReflectionException("Could not set property '" + prop.getName() + "' of '" + object.getClass() + "' with value '" + value + "' Cause: " + t.toString(), t);
    }
  }
           

源码分析之反射工具类的实例 理解上面说的,每一个是干什么的?对应着怎么使用

//反射工具类初始化
		ObjectFactory objectFactory = new DefaultObjectFactory();
		TUser user = objectFactory.create(TUser.class);
		ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
		ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
		MetaObject metaObject = MetaObject.forObject(user, objectFactory, objectWrapperFactory, reflectorFactory);
//
//		//使用Reflector读取类元信息
		Reflector findForClass = reflectorFactory.findForClass(TUser.class);
		Constructor<?> defaultConstructor = findForClass.getDefaultConstructor();
		String[] getablePropertyNames = findForClass.getGetablePropertyNames();
		String[] setablePropertyNames = findForClass.getSetablePropertyNames();
		System.out.println(defaultConstructor.getName());
		System.out.println(Arrays.toString(getablePropertyNames));
		System.out.println(Arrays.toString(setablePropertyNames));
  		//使用ObjectWrapper读取对象信息,并对对象属性进行赋值操作
		TUser userTemp = new TUser();
		ObjectWrapper wrapperForUser = new BeanWrapper(metaObject, userTemp);
		String[] getterNames = wrapperForUser.getGetterNames();
		String[] setterNames = wrapperForUser.getSetterNames();
		System.out.println(Arrays.toString(getterNames));
		System.out.println(Arrays.toString(setterNames));

		PropertyTokenizer prop = new PropertyTokenizer("userName");
		wrapperForUser.set(prop, "家哥哥");
		System.out.println(userTemp);
           
  • ObjectWrapperFactory: ObjectWrapper 的工厂类,用于创建 ObjectWrapper ;
  • MetaObject:封装了对象元信息,包装了MyBatis 中五个核心的反射类。也是提供给外部使用的反射工具类,可以利用它可以读取或者修改对象的属性信息;MetaObject 的类结构如

    跟反射相关的类都封装在了里面的 :

如果我们的相关类如果没有get()set()方法的话我们的Mybatis会把他自动生成

Mybatis源码分析 反射模块以及设计模式在mybatis中的应用Mybatis反射模块的解读与分析orm框架查询数据过程一 反射模块的大概分析二、MyBatis 流程概述总结

然后通过这两个方法来对当前的this进行赋值

Mybatis源码分析 反射模块以及设计模式在mybatis中的应用Mybatis反射模块的解读与分析orm框架查询数据过程一 反射模块的大概分析二、MyBatis 流程概述总结

当然我们可以看到,当前的对象中是没有id这个属性的 ,

在我们addFields方法中会自动给他赋值

debug的位置的

org.apache.ibatis.reflection.Reflector#Reflector  
           

里面的构造方法,由此我便知道了mybatis的反射功能是干什么的,也就是我们知道为什么mybatis在xml中可以根据配置的返回信息而生成的相对应的实例数据返回给我们的数据

Mybatis大概的执行思路:

//模拟数据库行数据转化成对象
        ObjectFactory objectFactory = new DefaultObjectFactory();
        TUser user = objectFactory.create(TUser.class);
        ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
        ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
        //生成MateObject的
        MetaObject metaObject = MetaObject.forObject(user, objectFactory, objectWrapperFactory, reflectorFactory);


        //1.模拟从数据库读取数据
        Map<String, Object> dbResult = new HashMap<>();
        dbResult.put("id", 1);
        dbResult.put("userName", "lison");
        dbResult.put("realName", "李晓宇");
        TPosition tp = new TPosition();
        tp.setId(1);
        dbResult.put("position_id", tp);
        //2.模拟映射关系
        Map<String, String> mapper = new HashMap<String, String>();
        mapper.put("id", "id");
        mapper.put("userName", "userName");
        mapper.put("realName", "realName");
        mapper.put("position", "position_id");

        //3.使用反射工具类将行数据转换成pojo

        //获取BeanWrapper,既包括类元数据,同时还能对对象的属性赋值
        BeanWrapper objectWrapper = (BeanWrapper) metaObject.getObjectWrapper();

        Set<Entry<String, String>> entrySet = mapper.entrySet();
        //遍历映射关系
        for (Entry<String, String> colInfo : entrySet) {
            String propName = colInfo.getKey();//获得pojo的字段名称
            Object propValue = dbResult.get(colInfo.getValue());//模拟从数据库中加载数据对应列的值
            PropertyTokenizer proTokenizer = new PropertyTokenizer(propName);
            objectWrapper.set(proTokenizer, propValue);//将数据库的值赋值到pojo的字段中
        }
        System.out.println(metaObject.getOriginalObject());
           

下所示:

Mybatis源码分析 反射模块以及设计模式在mybatis中的应用Mybatis反射模块的解读与分析orm框架查询数据过程一 反射模块的大概分析二、MyBatis 流程概述总结

可以打开相关源码去查看每一个里面的注释地址我会放到github地址

Reflector :里面储存了每一个对象的类型,如每个属性,还有get,set方法等等 ,也就是说我们每实例化一个类,mybatis都会生成一个对应的Reflector来存储这个对象相对应的方法 而在DefaultReflectorFactory 中有一个map是用来缓存每个class相对应的Reflector的

//用于缓存生成的Reflector的元数据,避免反射性能较低
  private final ConcurrentMap<Class<?>, Reflector> reflectorMap = new ConcurrentHashMap<>();
           

如果我们的实体类没有get set 方法的话它会根据我们的实体类去自动生成get,set方法

我们可以发现mybatis的反射类是非常强大的,我们如果项目中遇到需要反射的地方我们可以拿来即用

二、MyBatis 流程概述

通过对快速入门代码的分析,可以把MyBatis 的运行流程分为三大阶段:

  1. 初始化阶段:读取 XML 配置文件和注解中的配置信息,创建配置对象,并完成各个模

    块的初始化的工作;

  2. 代理封装阶段:封装 iBatis 的编程模型,使用 mapper 接口开发的初始化工作;
  3. 数据访问阶段:通过 SqlSession 完成SQL 的解析,参数的映射、SQL 的执行、结果的解析过程;

    快速入门代码阶段划分图示:

    Mybatis源码分析 反射模块以及设计模式在mybatis中的应用Mybatis反射模块的解读与分析orm框架查询数据过程一 反射模块的大概分析二、MyBatis 流程概述总结
建造者模式的理解与使用Demo

什么是建造者模式 ?

在配置加载阶段大量的使用了建造者模式,首先学习建造者模式。建造者模式Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。建造者模式类图如下:

Mybatis源码分析 反射模块以及设计模式在mybatis中的应用Mybatis反射模块的解读与分析orm框架查询数据过程一 反射模块的大概分析二、MyBatis 流程概述总结

各要素如下:

 Product:要创建的复杂对象

 Builder:给出一个抽象接口,以规范产品对象的各个组成成分的建造。这个接口规定要实现复杂对象的哪些部分的创建,并不涉及具体的对象部件的创建;

 ConcreteBuilder:实现 Builder 接口,针对不同的商业逻辑,具体化复杂对象的各部分的创建。 在建造过程完成后,提供产品的实例;

 Director:调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建;

关于建造器模式的扩展知识:流式编程风格越来越流行,如 zookeeper 的 Curator、JDK8 的流式编程等等都是例子。流式编程的优点在于代码编程性更高、可读性更好,缺点在于对程序员编码要求更高、不太利于调试。建造者模式是实现流式编程风格的一种方式;

与工厂模式区别

建造者模式应用场景如下:

 需要生成的对象具有复杂的内部结构,实例化对象时要屏蔽掉对象内部的细节,让上层代码与复杂对象的实例化过程解耦,可以使用建造者模式;简而言之,如果“遇到多个构造器参数时要考虑用构建器”;

 对象的实例化是依赖各个组件的产生以及装配顺序,关注的是一步一步地组装出目标对象,可以使用建造器模式;

建造者模式与工厂模式的区别在于:

Mybatis源码分析 反射模块以及设计模式在mybatis中的应用Mybatis反射模块的解读与分析orm框架查询数据过程一 反射模块的大概分析二、MyBatis 流程概述总结

建造者模式示例:

比如我们有一个发红包的程序这是

红包的接口(示例):

public interface RedPacketBuilder {
	
	RedPacketBuilder setPublisherName(String publishName);

	RedPacketBuilder setAcceptName(String acceptName);

	RedPacketBuilder setPacketAmount(BigDecimal packetAmount);

	RedPacketBuilder setPacketType(int packetType);

	RedPacketBuilder setPulishPacketTime(Date pushlishPacketTime);

	RedPacketBuilder setOpenPacketTime(Date openPacketTime);

    RedPacket build();
}
           

接口的实现类以及建造者模式的设计(示例):

public class RedPacketBuilderImpl implements RedPacketBuilder {
	
	private String publisherName;

    private String acceptName;

    private BigDecimal packetAmount;
    
    private int packetType;

    private Date pulishPacketTime;

    private Date openPacketTime;

    public static RedPacketBuilderImpl getBulider(){
        return new RedPacketBuilderImpl();
    }


    @Override
    public RedPacketBuilder setPublisherName(String publishName) {
        this.publisherName = publishName;
        return this;
    }

    @Override
    public RedPacketBuilder setAcceptName(String acceptName) {
       this.acceptName = acceptName;
       return this;
    }

    @Override
    public RedPacketBuilder setPacketAmount(BigDecimal packetAmount) {
       this.packetAmount = packetAmount;
       return this;
    }

    @Override
    public RedPacketBuilder setPacketType(int packetType) {
        this.packetType = packetType;
        return this;
    }

    @Override
    public RedPacketBuilder setPulishPacketTime(Date pushlishPacketTime) {
       this.pulishPacketTime = pushlishPacketTime;
        return this;
    }

    @Override
    public RedPacketBuilder setOpenPacketTime(Date openPacketTime) {
      this.openPacketTime = openPacketTime;
        return this;
    }


    public RedPacket build() {
        return new RedPacket(publisherName,acceptName,packetAmount,packetType,pulishPacketTime,openPacketTime);
    }
}
           

红包pojo类(示例):

public class RedPacket {
	
	private String publisherName; //发包人

    private String acceptName; //收包人

    private BigDecimal packetAmount; //红包金额

    private int packetType; //红包类型

    private Date pulishPacketTime; //发包时间

    private Date openPacketTime; //抢包时间

    public RedPacket(String publisherName, String acceptName, BigDecimal packetAmount, int packetType, Date pulishPacketTime, Date openPacketTime) {
        this.publisherName = publisherName;
        this.acceptName = acceptName;
        this.packetAmount = packetAmount;
        this.packetType = packetType;
        this.pulishPacketTime = pulishPacketTime;
        this.openPacketTime = openPacketTime;
    }

    //get set 
	public String toString() {
		return "RedPacket [publisherName=" + publisherName + ", acceptName="
				+ acceptName + ", packetAmount=" + packetAmount
				+ ", packetType=" + packetType + ", pulishPacketTime="
				+ pulishPacketTime + ", openPacketTime=" + openPacketTime + "]";
	}
           

使用(示例):

public static void main(String[] args) {
		RedPacket redPacket = RedPacketBuilderImpl.getBulider().setPublisherName("aaa")
				                                               .setAcceptName("bbb")
                                                               .setPacketAmount(new BigDecimal("888"))
                                                               .setPacketType(1)
                                                               .setOpenPacketTime(new Date())
                                                               .setPulishPacketTime(new Date()).build();

		System.out.println(redPacket);
	}
           

对象是怎么实例化的我完全不知道,但是我只关注里面的构造过程的某一些数据的过程

建造者模式在mybatis中的应用

Mybatis源码分析 反射模块以及设计模式在mybatis中的应用Mybatis反射模块的解读与分析orm框架查询数据过程一 反射模块的大概分析二、MyBatis 流程概述总结

接着说mybatis的流程

在MyBatis 中负责加载配置文件的核心类有三个,类图如下:

Mybatis源码分析 反射模块以及设计模式在mybatis中的应用Mybatis反射模块的解读与分析orm框架查询数据过程一 反射模块的大概分析二、MyBatis 流程概述总结

建造器三个核心类 :

 BaseBuilder:所有解析器的父类,包含配置文件实例,为解析文件提供的一些通用的方

法;

 XMLConfigBuilder: 主要负责解析mybatis-config.xml;

 XMLMapperBuilder: 主要负责解析映射配置 Mapper.xml 文件;

 XMLStatementBuilder: 主要负责解析映射配置文件中的SQL 节点;

XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder 这三个类在配置文件加载

过程中非常重要,具体分工如下图所示:

Mybatis源码分析 反射模块以及设计模式在mybatis中的应用Mybatis反射模块的解读与分析orm框架查询数据过程一 反射模块的大概分析二、MyBatis 流程概述总结

这三个类使用了建造者模式对configuration 对象进行初始化,但是没有使用建造者模式的“肉体”(流式编程风格),只用了灵魂(屏蔽复杂对象的创建过程),把建造者模式演绎成了工厂模式;后面还会对这三个类源码进行分析;

我们可以理解为 3个Xml是为了填充Configuartion对象而使用的

XMLConfigBuilder的初始化的过程

分析过程 – >

// 1.读取mybatis配置文件创SqlSessionFactory
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 
-->org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream, java.lang.String, java.util.Properties)
-->org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration
//这个方法就是读取xml各种配置,每一个元素都对应着一个节点  XMLConfigBuilder 是继承BaseBuilder 的在 BaseBuilder中存在Configuration对象的 
           

大概看一些Configuration中比较重要几个数据结构 :

/*mapper接口的动态代理注册中心*/
  protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
  /*mapper文件中增删改查操作的注册中心*/
  protected final Map<String, MappedStatement> mappedStatements = new StrictMap<>("Mapped Statements collection");
  /*mapper文件中配置cache节点的 二级缓存*/
  protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
  
  /*mapper文件中配置的所有resultMap对象  key为命名空间+ID*/
  protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
  protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
  
  /*mapper文件中配置KeyGenerator的insert和update节点,key为命名空间+ID*/
  protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");
           

关于 Configuration 对象:实例化并初始化Configuration 对象是第一个阶段的最终目的,所以熟悉configuration 对象是理解第一个阶段代码的核心;configuration 对象的关键属性解析如下:

  • MapperRegistry:mapper 接口动态代理工厂类的注册中心。在 MyBatis 中,通过mapperProxy 实现 InvocationHandler 接口MapperProxyFactory 用于生成动态代理的实例对象;
  • ResultMap:用于解析 mapper.xml 文件中的resultMap 节点,使用ResultMapping 来封装id,result 等子元素;
  • MappedStatement:用于存储mapper.xml 文件中的 select、insert、update 和 delete 节点,同时还包含了这些节点的很多重要属性;
  • SqlSource:用于创建 BoundSql,mapper.xml 文件中的 sql 语句会被解析成 BoundSql 对象,经过解析 BoundSql 包含的语句最终仅仅包含?占位符,可以直接提交给数据库执行;
  • 需要特别注意的是 Configuration 对象在MyBatis 中是单例的,生命周期是应用级的,换句话说只要 MyBatis 运行 Configuration 对象就会独一无二的存在;在 MyBatis 中仅在org.apache.ibatis.builder.xml.XMLConfigBuilder.XMLConfigBuilder(XPathParser, String, Properties)

    中有实例化configuration 对象的代码,如下代码

private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }
           

Configuration 对象的初始化(属性复制),是在建造SqlSessionfactory 的过程中进行的,接下来分析第一个阶段的内部流程;

XMl的配置加载过程

可以把第一个阶段配置加载过程分解为四个步骤,四个步骤如下图:

Mybatis源码分析 反射模块以及设计模式在mybatis中的应用Mybatis反射模块的解读与分析orm框架查询数据过程一 反射模块的大概分析二、MyBatis 流程概述总结

第一步:通过 SqlSessionFactoryBuilder 建造 SqlSessionFactory,并创建 XMLConfigBuilder 对象 读 取 MyBatis 核 心 配 置 文 件 ,

//方法:org.apache.ibatis.session.SqlSessionFactoryBuilder.build(Reader, String, Properties)
 try {
       //读取配置文件
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      //解析配置文件得到Configuartion 对象并且返回SqlSessionFactory
      return build(parser.parse());
     .....
           

第二步:进入XMLConfigBuilder 的 parseConfiguration 方法,对MyBatis 核心配置文件的各个元素进行解析,读取元素信息后填充到 configuration 对象。在 XMLConfigBuildermapperElement()方法中通过 XMLMapperBuilder 读取所有 mapper.xml 文件;见方法

使用(示例):

org.apache.ibatis.builder.xml.XMLConfigBuilder.parseConfiguration(XNode);
public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }
	//第三步:XMLMapperBuilder 的核心方法为 configurationElement (XNode ),该方法对
//mapper.xml 配置文件的各个元素进行解析,读取元素信息后填充到 configuration 对象 
  private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
     //解析<properties>节点
      propertiesElement(root.evalNode("properties"));
      //解析<settings>节点
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      //解析<typeAliases>节点
      typeAliasesElement(root.evalNode("typeAliases"));
      //解析<plugins>节点
      pluginElement(root.evalNode("plugins"));
      //解析<objectFactory>节点
      objectFactoryElement(root.evalNode("objectFactory"));
      //解析<objectWrapperFactory>节点
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      //解析<reflectorFactory>节点
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);//将settings填充到configuration
      // read it after objectFactory and objectWrapperFactory issue #631
      //解析<environments>节点
      environmentsElement(root.evalNode("environments"));
      //解析<databaseIdProvider>节点
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      //解析<typeHandlers>节点
      typeHandlerElement(root.evalNode("typeHandlers"));
      //解析<mappers>节点
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }
           

第三步:XMLMapperBuilder 的核心方法为 configurationElement (XNode ),该方法对mapper.xml 配置文件的各个元素进行解析,读取元素信息后填充到 configuration 对象。

org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement 
   -->org.apache.ibatis.builder.xml.XMLMapperBuilder#parse
    public void parse() {
        //判断是否已经加载该配置文件
        if (!configuration.isResourceLoaded(resource)) {
            //处理mapper节点
            configurationElement(parser.evalNode("/mapper"));
            /*将mapper文件添加到configuration.loadedResources中*/
            configuration.addLoadedResource(resource);
            /*注册mapper接口 找到Class类与 xml文件所处的位置 重点分析 */
            bindMapperForNamespace();
        }
        //处理解析失败的ResultMap节点
        parsePendingResultMaps();
        //处理解析失败的CacheRef节点
        parsePendingCacheRefs();
        //处理解析失败的Sql语句节点
        parsePendingStatements();
    } 
           

先来查看缓存的方法

org.apache.ibatis.builder.xml.XMLMapperBuilder#cacheElement
	->org.apache.ibatis.builder.MapperBuilderAssistant#useNewCache 
	/通过builderAssistant创建缓存对象,并添加至configuration
  public Cache useNewCache(Class<? extends Cache> typeClass,
      Class<? extends Cache> evictionClass,
      Long flushInterval,
      Integer size,
      boolean readWrite,
      boolean blocking,
      Properties props) {
	//经典的建造起模式,创建一个cache对象
    Cache cache = new CacheBuilder(currentNamespace)
        .implementation(valueOrDefault(typeClass, PerpetualCache.class))
        .addDecorator(valueOrDefault(evictionClass, LruCache.class))
        .clearInterval(flushInterval)
        .size(size)
        .readWrite(readWrite)
        .blocking(blocking)
        .properties(props)
        .build();
    //将缓存添加至configuration,注意二级缓存以命名空间为单位进行划分
    configuration.addCache(cache);
    currentCache = cache;
    return cache;
  }
  //建造者的模式关注点 build方法,把一些细节都藏在了方法里对外隐藏了创建的细节  
  //org.apache.ibatis.mapping.CacheBuilder#build  
  // --> org.apache.ibatis.mapping.CacheBuilder#setStandardDecorators 
  	//装饰器模式的应用 对cache赋值上各种功能 
  	  /*设置读写属性*/
      if (readWrite) {
        cache = new SerializedCache(cache);
      }
      /*日志能力*/
      cache = new LoggingCache(cache);
      /*同步*/
      cache = new SynchronizedCache(cache);
      /*加上阻塞*/
      if (blocking) {
        cache = new BlockingCache(cache);
      }
           

在XMLMapperBuilder 解析过程中,有四个点需要注意:

  1. resultMapElements(List)方法用于解析 resultMap 节点,这个方法非常重要,一定要跟源码理解;解析完之后数据保存在 configuration 对象的 resultMaps 属性中;
//解析resultMap节点,实际就是解析sql查询的字段与pojo属性之间的转化规则
    private void resultMapElements(List<XNode> list) throws Exception {
        //遍历所有的resultmap节点
        for (XNode resultMapNode : list) {
            try {
                //解析具体某一个resultMap节点
                resultMapElement(resultMapNode);
            } catch (IncompleteElementException e) {
                // ignore, it will be retried
            }
        }
    }
    org.apache.ibatis.builder.xml.XMLMapperBuilder#resultMapElement(org.apache.ibatis.parsing.XNode, java.util.List<org.apache.ibatis.mapping.ResultMapping>) 
    
           

resultMapping 的数据结构

/*引用的configuration对象*/
  private Configuration configuration;
  private String property;//对应节点的property属性
  private String column;//对应节点的column属性
  private Class<?> javaType;//对应节点的javaType属性
  private JdbcType jdbcType;//对应节点的jdbcType属性
  private TypeHandler<?> typeHandler;//对应节点的typeHandler属性
  private String nestedResultMapId;//对应节点的resultMap属性,嵌套结果时使用
  private String nestedQueryId;//对应节点的select属性,嵌套查询时使用
  private Set<String> notNullColumns;//对应节点的notNullColumn属性
  private String columnPrefix;//对应节点的columnPrefix属性
  private List<ResultFlag> flags;//标志,id 或者 constructor
  private List<ResultMapping> composites;
  private String resultSet;//对应节点的resultSet属性
  private String foreignColumn;//对应节点的foreignColumn属性
  private boolean lazy;//对应节点的fetchType属性,是否延迟加载
           
Mybatis源码分析 反射模块以及设计模式在mybatis中的应用Mybatis反射模块的解读与分析orm框架查询数据过程一 反射模块的大概分析二、MyBatis 流程概述总结
//实例化resultMap并将其注册到configuration对象
  public ResultMap addResultMap(
      String id,
      Class<?> type,
      String extend,
      Discriminator discriminator,
      List<ResultMapping> resultMappings,
      Boolean autoMapping) {
	 //完善id,id的完整格式是"namespace.id"
    id = applyCurrentNamespace(id, false);
    //获得父类resultMap的完整id
    extend = applyCurrentNamespace(extend, true);

    //针对extend属性的处理
    if (extend != null) {
      if (!configuration.hasResultMap(extend)) {
        throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
      }
      ResultMap resultMap = configuration.getResultMap(extend);
      List<ResultMapping> extendedResultMappings = new ArrayList<>(resultMap.getResultMappings());
      extendedResultMappings.removeAll(resultMappings);
      // Remove parent constructor if this resultMap declares a constructor.
      boolean declaresConstructor = false;
      for (ResultMapping resultMapping : resultMappings) {
        if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
          declaresConstructor = true;
          break;
        }
      }
      if (declaresConstructor) {
        Iterator<ResultMapping> extendedResultMappingsIter = extendedResultMappings.iterator();
        while (extendedResultMappingsIter.hasNext()) {
          if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) {
            extendedResultMappingsIter.remove();
          }
        }
      }
      //添加需要被继承下来的resultMapping对象结合
      resultMappings.addAll(extendedResultMappings);
    }
    //通过建造者模式实例化resultMap,并注册到configuration.resultMaps中
    ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
        .discriminator(discriminator)
        .build();
    configuration.addResultMap(resultMap);
    return resultMap;
  }
           
  1. XMLMapperBuilder 中在实例化二级缓存(见cacheElement(XNode))、实例化 resultMap(见resultMapElements(List))过程中都使用了建造者模式,而且是建造者模式的典型应用;
  2. XMLMapperBuilder 和 XMLMapperStatmentBuilder 有 自 己 的 “ 秘 书 ”MapperBuilderAssistant。XMLMapperBuilder 和XMLMapperStatmentBuilder 负责解析读取配置文件里面的信息,MapperBuilderAssistant 负责将信息填充到 configuration。将文件解析和数据的填充的工作分离在不同的类中,符合单一职责原则;
  3. 在 buildStatementFromContext(List)方法中,创建 XMLStatmentBuilder 解析Mapper.xml 中select、insert、update、delete 节点
org.apache.ibatis.builder.xml.XMLMapperBuilder#buildStatementFromContext(java.util.List<org.apache.ibatis.parsing.XNode>) 
 //处理所有的sql语句节点并注册至configuration对象
    private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
        for (XNode context : list) {
            //创建XMLStatementBuilder 专门用于解析sql语句节点
            final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
            try {
                //解析sql语句节点
                statementParser.parseStatementNode();
            } catch (IncompleteElementException e) {
                configuration.addIncompleteStatement(statementParser);
            }
        }
    } 
   .....
           

第四步:在 XMLStatmentBuilder 的 parseStatementNode()方法中,对 Mapper.xml 中 select,insert、update、delete 节点进行解析,并调用 MapperBuilderAssistant 负责将信息填充到configuration。在理解 parseStatementNod()方法之前,有必要了解 MappedStatement,这个

类 用 于 封 装 select 、 insert 、 update 、 delete 节 点 的 信 息 ; 如 下 图 所 示 :

Mybatis源码分析 反射模块以及设计模式在mybatis中的应用Mybatis反射模块的解读与分析orm框架查询数据过程一 反射模块的大概分析二、MyBatis 流程概述总结

大概的流程图

Mybatis 的接口层

第二个阶段使用到的第一个对象就是 SqlSession,SqlSession 是MyBaits 对外提供的最关键的核心接口,通过它可以执行数据库读写命令、获取映射器、管理事务等;SqlSession意味着客户端与数据库的一次连接,客户端对数据库的访问请求都是由SqlSession 来处理SqlSession 由SqlSessionFactory 创建,每个SqlSession 都会引用SqlSessionFactory 中全局唯一单例存在的configuration 对象;如下图所示:

Mybatis源码分析 反射模块以及设计模式在mybatis中的应用Mybatis反射模块的解读与分析orm框架查询数据过程一 反射模块的大概分析二、MyBatis 流程概述总结

要 深 入 理 解 SqlSession 就 得 深 入 到 源 码 进 行 学 习 , SqlSession 默 认 实 现 类 为org.apache.ibatis.session.defaults.DefaultSqlSession,解读如下:

(1) SqlSession 是MyBatis 的门面,是MyBatis 对外提供数据访问的主要API,实例代码

@Test
    // ibatis编程模型 本质分析
    public void originalOperation() throws IOException {
        // 2.获取sqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 3.执行查询语句并返回结果 通过nameSpace 加id来确认接口
        TUser user = sqlSession.selectOne("com.enjoylearning.mybatis.mapper.TUserMapper.selectByPrimaryKey", 2);
        System.out.println(user.toString());
    }
           

(2) 实际上Sqlsession 的功能都是基于Executor 来实现的,遵循了单一职责原则,例如 在 SqlSession 中的各种查询形式,最终会把请求转发到 Executor.query 方法,如下图所

示:

Mybatis源码分析 反射模块以及设计模式在mybatis中的应用Mybatis反射模块的解读与分析orm框架查询数据过程一 反射模块的大概分析二、MyBatis 流程概述总结

策略模式 : 策略模式(Strategy Pattern)策略模式定义了一系列的算法,并将每一个算法封装起来,而且使他们可以相互替换,让算法独立于使用它的客户而独立变化。Spring 容器中使用配置可以灵活的替换掉接口的实现类就是策略模式最常见的应用。类图如下:

  • Context:算法调用者,使用 setStrategy 方法灵活的选择策略(strategy);
  • Strategy:算法的统一接口;
  • ConcreteStrategy:算法的具体实现;
    Mybatis源码分析 反射模块以及设计模式在mybatis中的应用Mybatis反射模块的解读与分析orm框架查询数据过程一 反射模块的大概分析二、MyBatis 流程概述总结

    策略模式的使用场景:

    (1) 针对同一类型问题的多种处理方式,仅仅是具体行为有差别时;

    (2) 出现同一抽象类有多个子类,而又需要使用 if-else 或者 switch-case 来选择具体子类时。

SqlSessionFactory

sqlSessionFactory 使 用 工 厂 模 式 创 建 SqlSession , 其 默 认 的 实 现 类 为DefaultSqlSessionFactory , 其 中 获 取 SqlSession 的 核 心 方 法 为openSessionFromDataSource(ExecutorType, TransactionIsolationLevel, boolean),在这个方法中从 configuration 中获取的 TransactionFactory 是典型的策略模式的应用。运行期,TransactionFactory 接口的实现,是由配置文件配置决定的,可配置选项包括:JDBC、Managed,可根据需求灵活的替换TransactionFactory 的实现

//从数据源获取数据库连接
  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
    	//获取mybatis配置文件中的environment对象
      final Environment environment = configuration.getEnvironment();
      //从environment获取transactionFactory对象
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      //创建事务对象 拿到数据库连接池 重点分析  不管是Pull 还是UnPull都是实现了DataSource  而且可以通过配置文件来配置
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      //根据配置创建executor
      final Executor executor = configuration.newExecutor(tx, execType);
      //创建DefaultSqlSession
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
           
<environments default="development">
		<!-- 环境配置1,每个SqlSessionFactory对应一个环境 -->
		<environment id="development">
			<transactionManager type="JDBC" />
<!--            灵活配置根据需要替换实现-->
			<dataSource type="POOLED">
				<property name="driver" value="${jdbc_driver}" />
				<property name="url" value="${jdbc_url}" />
				<property name="username" value="${jdbc_username}" />
				<property name="password" value="${jdbc_password}" />
			</dataSource>
		</environment>


	</environments>
           

总结

今天写了mybatis的反射模块的分析与源码解析,了解了几个设计模式,并且mybatis是怎么使用它的,mybatis是怎么生成相关的信息的resultMap的相关信息的,怎么指向sql的,与ibatis的区别等等