Análisis de código fuente de Myabtis cinco: configuración de Mybatis que carga el diagrama completo, el uso del modo constructor

​¡Crear un hábito de escritura juntos! Este es el décimo día de mi participación en el "Nuggets Daily New Plan·Desafío de actualización de abril", [Haga clic para ver los detalles del evento]

1. Descripción general del proceso de ejecución de Mybatis

Para familiarizarse con el proceso de ejecución de Mybatis, primero veamos un fragmento de código

public class MybatisDemo {
	

	private SqlSessionFactory sqlSessionFactory;
	
	@Before
	public void init() throws IOException {
		//--------------------第一步:加载配置---------------------------
	    // 1.读取mybatis配置文件创SqlSessionFactory
		String resource = "mybatis-config.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
		// 1.读取mybatis配置文件创SqlSessionFactory
		sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
		inputStream.close();
	}
	
	@Test
	// 快速入门
	public void quickStart() throws IOException {
		//--------------------第二部,创建代理对象---------------------------
		// 2.获取sqlSession	
		SqlSession sqlSession = sqlSessionFactory.openSession();
		// 3.获取对应mapper
		TUserMapper mapper = sqlSession.getMapper(TUserMapper.class);
		
		//--------------------第三步:获取数据---------------------------
		// 4.执行查询语句并返回单条数据
		TUser user = mapper.selectByPrimaryKey(2);
		System.out.println(user);
		
		System.out.println("----------------------------------");
		
		// 5.执行查询语句并返回多条数据
//		List<TUser> users = mapper.selectAll();
//		for (TUser tUser : users) {
//			System.out.println(tUser);
//		}
	}
}
复制代码

Lo anterior es una demostración del uso de mybatis para acceder a los datos A través del análisis del código de inicio rápido, el proceso de ejecución de MyBatis se puede dividir en tres etapas:

  1. Etapa de inicialización : lea la información de configuración en el archivo de configuración XML y las anotaciones, cree un objeto de configuración y complete la inicialización de cada módulo;
  2. Etapa de encapsulación de proxy : encapsule el modelo de programación de iBatis y utilice el trabajo de inicialización desarrollado por la interfaz del mapeador;
  3. Etapa de acceso a datos : análisis de SQL completo, asignación de parámetros, ejecución de SQL y proceso de análisis de resultados a través de SqlSession;

Hoy presentaremos cómo Mybatis lee la configuración en la primera etapa a continuación.

En segundo lugar, la clase central de carga de configuración

2.1 Las tres clases principales del constructor

Hay tres clases principales responsables de cargar los archivos de configuración en MyBatis, el diagrama de clases es el siguiente:

  • BaseBuilder: la clase principal de todos los analizadores, incluidas las instancias de archivos de configuración y algunos métodos comunes para analizar archivos;
  • XMLConfigBuilder: Principalmente responsable de analizar mybatis-config.xml;
  • XMLMapperBuilder: Principalmente responsable de analizar el archivo Mapper.xml de configuración de mapeo;
  • XMLStatementBuilder: Principalmente responsable de analizar los nodos SQL en el archivo de configuración de mapeo;

Las tres clases XMLConfigBuilder, XMLMapperBuilder y XMLStatementBuilder son muy importantes en el proceso de carga del archivo de configuración. La división específica del trabajo se muestra en la siguiente figura:

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

居然这三个对象使用的是建造者模式,那么我们稍后介绍下什么是建造者模式

三、建造者模式

3.1 什么是建造者模式

建造者模式(BuilderPattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

建造者模式类图如下:

各要素如下:

  • Product:要创建的复杂对象
  • Builder:给出一个抽象接口,以规范产品对象的各个组成成分的建造。这个接口规定要实现复杂对象的哪些部分的创建,并不涉及具体的对象部件的创建;
  • ConcreteBuilder:实现 Builder 接口,针对不同的商业逻辑,具体化复杂对象的各部分的创建。 在建造过程完成后,提供产品的实例;
  • Director:调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建;

应用举例:红包的创建是个复杂的过程,可以使用构建者模式进行创建

代码示例:

1、红包对象RedPacket 


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;
    }

	public String getPublisherName() {
		return publisherName;
	}

	public void setPublisherName(String publisherName) {
		this.publisherName = publisherName;
	}

	public String getAcceptName() {
		return acceptName;
	}

	public void setAcceptName(String acceptName) {
		this.acceptName = acceptName;
	}

	public BigDecimal getPacketAmount() {
		return packetAmount;
	}

	public void setPacketAmount(BigDecimal packetAmount) {
		this.packetAmount = packetAmount;
	}

	public int getPacketType() {
		return packetType;
	}

	public void setPacketType(int packetType) {
		this.packetType = packetType;
	}

	public Date getPulishPacketTime() {
		return pulishPacketTime;
	}

	public void setPulishPacketTime(Date pulishPacketTime) {
		this.pulishPacketTime = pulishPacketTime;
	}

	public Date getOpenPacketTime() {
		return openPacketTime;
	}

	public void setOpenPacketTime(Date openPacketTime) {
		this.openPacketTime = openPacketTime;
	}

	@Override
	public String toString() {
		return "RedPacket [publisherName=" + publisherName + ", acceptName="
				+ acceptName + ", packetAmount=" + packetAmount
				+ ", packetType=" + packetType + ", pulishPacketTime="
				+ pulishPacketTime + ", openPacketTime=" + openPacketTime + "]";
	}
   
}
复制代码

2、构建对象

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

		System.out.println(redPacket);
	}

}
复制代码

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

3.2 与工厂模式区别

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

  • 需要生成的对象具有复杂的内部结构,实例化对象时要屏蔽掉对象代码与复杂对象的实例化过程解耦,可以使用建造者模式;简而言之,如果“遇到多个构造器参数时要考虑用构建器”;
  • 对象的实例化是依赖各个组件的产生以及装配顺序,关注的是一步一步地组装出目标对
  • 象,可以使用建造器模式;

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

设计模式 形象比喻 对象复杂度 客户端参与程度
工厂模式 生产大众版 关注的是一个产品整体,无须关心产品的各部分是如何创建出来的; 客户端对产品的创建过程参与度低,对象实例化时属性值相对比较固定;
建造者模式 生产定制版 建造的对象更加复杂,是一个复合产品,它由各个部件复合而成,部件不同产品对象不同,生成的产品粒度细; 客户端参与了产品的创建,决定了产品的类型和内容,参与度高;适合实例化对象时属性变化频繁的场景;

四、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对象图解:

需要特别注意的是 Configuration 对象在 MyBatis 中是单例的,生命周期是应用级的,换句话说只要 MyBatis 运行 Configuration 对象就会独一无二的存在;在 MyBatis 中仅在
org.apache.ibatis.builder.xml.XMLConfigBuilder.XMLConfigBuilder(XPathParser, String, Properties)中有实例化 configuration 对象的代码,如下图:

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

 五、配置加载流程解析

5.1 配置加载过程

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


第一步:通过 SqlSessionFactoryBuilder 建造 SqlSessionFactory,并创建 XMLConfigBuilder 对
象 读 取 MyBatis 核 心 配 置 文 件 , 见 源码方 法 :
org.apache.ibatis.session.SqlSessionFactoryBuilder.build(Reader, String, Properties):

  public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      //读取配置文件
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      return build(parser.parse());//解析配置文件得到configuration对象,并返回SqlSessionFactory
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }
复制代码

第二步:进入 XMLConfigBuilder 的 parseConfiguration 方法,对 MyBatis 核心配置文件的各个
元素进行解析,读取元素信息后填充到 configuration 对象。在 XMLConfigBuilder 的
mapperElement()方法中通过 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;
  }

  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 对象。

private void configurationElement(XNode context) {
    try {
    	//获取mapper节点的namespace属性
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      //设置builderAssistant的namespace属性
      builderAssistant.setCurrentNamespace(namespace);
      //解析cache-ref节点
      cacheRefElement(context.evalNode("cache-ref"));
      //重点分析 :解析cache节点----------------1-------------------
      cacheElement(context.evalNode("cache"));
      //解析parameterMap节点(已废弃)
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      //重点分析 :解析resultMap节点----------------2-------------------
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      //解析sql节点
      sqlElement(context.evalNodes("/mapper/sql"));
      //重点分析 :解析select、insert、update、delete节点 ----------------3-------------------
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }
复制代码

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

  1. resultMapElements(List)方法用于解析 resultMap 节点,这个方法非常重要, 一定要跟源码理解;解析完之后数据保存在 configuration 对象的 resultMaps 属性中;如下图
  2. 2XMLMapperBuilder 中在实例化二级缓存(见 cacheElement(XNode))、实例化 resultMap (见 resultMapElements(List))过程中都使用了建造者模式,而且是建造者模 式的典型应用;
  3. XMLMapperBuilder 和 XMLMapperStatmentBuilder 有 自 己 的 “ 秘 书 ” MapperBuilderAssistant。XMLMapperBuilder 和 XMLMapperStatmentBuilder 负责解析 读取配置文件里面的信息,MapperBuilderAssistant 负责将信息填充到 configuration。 将文件解析和数据的填充的工作分离在不同的类中,符合单一职责原则;
  4. 在 buildStatementFromContext(List)方法中,创建 XMLStatmentBuilder 解析 Mapper.xml 中 select、insert、update、delete 节点

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

 至此,整个Mybatis的配置即加载完毕,整个加载流程图如下:

\

Supongo que te gusta

Origin juejin.im/post/7085375589627985934
Recomendado
Clasificación