前言:
目前兄弟们对“jdk18流式编程”大概比较注重,各位老铁们都需要剖析一些“jdk18流式编程”的相关文章。那么小编同时在网上搜集了一些对于“jdk18流式编程””的相关知识,希望大家能喜欢,姐妹们一起来了解一下吧!一、Mybatis运行流程概述
为了熟悉Mybatis的运行流程,我们先看一段代码
java复制代码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);// } }}
以上是我们一个使用mybatis访问数据的demo,通过对快速入门代码的分析,可以把 MyBatis 的运行流程分为三大阶段:
初始化阶段:读取 XML 配置文件和注解中的配置信息,创建配置对象,并完成各个模块的初始化的工作;代理封装阶段:封装 iBatis 的编程模型,使用 mapper 接口开发的初始化工作;数据访问阶段:通过 SqlSession 完成 SQL 的解析,参数的映射、SQL 的执行、结果的解析过程;
今天我们就介绍以下第一个阶段中,Mybatis是如何读取配置的
二、配置加载的核心类2.1 建造器三个核心类
在 MyBatis 中负责加载配置文件的核心类有三个,类图如下:
BaseBuilder:所有解析器的父类,包含配置文件实例,为解析文件提供的一些通用的方法;XMLConfigBuilder: 主要负责解析 mybatis-config.xml;XMLMapperBuilder: 主要负责解析映射配置 Mapper.xml 文件;XMLStatementBuilder: 主要负责解析映射配置文件中的 SQL 节点;
XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder 这三个类在配置文件加载过程中非常重要,具体分工如下图所示:
这三个类使用了建造者模式对 configuration 对象进行初始化,但是没有使用建造者模式 的“肉体”(流式编程风格),只用了灵魂(屏蔽复杂对象的创建过程),把建造者模式演绎 成了工厂模式;后面还会对这三个类源码进行分析;
居然这三个对象使用的是建造者模式,那么我们稍后介绍下什么是建造者模式
三、建造者模式3.1 什么是建造者模式
建造者模式(BuilderPattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
建造者模式类图如下:
各要素如下:
Product:要创建的复杂对象Builder:给出一个抽象接口,以规范产品对象的各个组成成分的建造。这个接口规定要实现复杂对象的哪些部分的创建,并不涉及具体的对象部件的创建;ConcreteBuilder:实现 Builder 接口,针对不同的商业逻辑,具体化复杂对象的各部分的创建。 在建造过程完成后,提供产品的实例;Director:调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建;
应用举例:红包的创建是个复杂的过程,可以使用构建者模式进行创建
代码示例:
1、红包对象RedPacket
typescript复制代码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、构建对象
typescript复制代码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):
typescript复制代码 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);
scss复制代码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 对象。
csharp复制代码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 解析过程中,有四个点需要注意:
resultMapElements(List)方法用于解析 resultMap 节点,这个方法非常重要, 一定要跟源码理解;解析完之后数据保存在 configuration 对象的 resultMaps 属性中;如下图2XMLMapperBuilder 中在实例化二级缓存(见 cacheElement(XNode))、实例化 resultMap (见 resultMapElements(List))过程中都使用了建造者模式,而且是建造者模 式的典型应用;XMLMapperBuilder 和 XMLMapperStatmentBuilder 有 自 己 的 “ 秘 书 ” MapperBuilderAssistant。XMLMapperBuilder 和 XMLMapperStatmentBuilder 负责解析 读取配置文件里面的信息,MapperBuilderAssistant 负责将信息填充到 configuration。 将文件解析和数据的填充的工作分离在不同的类中,符合单一职责原则;在 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的配置即加载完毕,整个加载流程图如下:
标签: #jdk18流式编程