Mybatis 原理之启动阶段

news2024/10/5 15:04:15

文章目录

      • 1.MyBatis 核心流程
      • 2.启动准备阶段流程
      • 3.创建 SQlSessionFactory
      • 4.创建XMLConfigBuilder
      • 5.创建 XPathParser
      • 6.解析并设置 configuration 中的属性
      • 7.解析Mappers标签

1.MyBatis 核心流程

Mybatis的核心流程氛围两个阶段,启动准备阶段和执行SQL阶段。

  1. 加载配置XMl文件。
  2. 读取mybatis的dtd描述文件,并且解析xml标签
  3. 通过读取的XMl配置信息生成对应的全局配置对象,以及生成mapper不同方法的SQL映射。
  4. 创建 SqlSessionFactory 完成,使用 SqlSessionFactory 创建 Session。
  5. 根据方法签名,获得mapper对应的代理对象。
  6. 通过JDK动态代理找到实现类,获得数据库连接,然后执行SQL。
  7. 拿到执行结果,并且根据映射关系处理为Java对象。
  8. 执行结束,关闭资源。

其中重点在于5个点:解析xml、生产代理对象、获得代理对象、执行SQL、处理结果集。

2.启动准备阶段流程

  1. 加载配置XMl文件
  2. 读取mybatis的dtd描述文件,并且解析xml标签
  3. 通过读取的XMl配置信息生成对应的全局配置对象,以及生成需要mapper的SQL映射。
  4. 创建 SqlSessionFactory 完成,使用 SqlSessionFactory 创建 Session。

在这里插入图片描述

  • congfiguration:是Mybatis初始化过程的核心对象,mybatis中几乎全部的配置信息会保存到Configuration中,全局生效。
  • XMLConfigBuilder:用于创建configuration,解析MyBatis配置文件中 configuration 节点的配置信息并填充到 Configuration 对象中
  • XMLMapperEntityResolver :包含于 XMLConfigBuilder中,用于读取本地DTD文件。
  • XPathParser :XPath解析器,对JDK的类做了封装,包含于 XMLConfigBuilder中。XPath即为XML路径语言(XML Path Language),它是一种用来确定XML文档中某部分位置的语言。
  • document :包含于XPathParser 中,方便后续结合 XPathParser 读取配置文件内容。

3.创建 SQlSessionFactory

建立Session就需要工厂类,建立SqlSessionFactory就需要SqlSessionFactoryBuilder,但是建立Factory就需要先读取并解析XML,因为可以看到build方法依赖于全局配置 configuration。

InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
}

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    // 准备阶段:将配置文件加载到内存中并生成document对象,然后创建初始化Configuration对象
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
  // 解析document对象并生成 SqlSessionFactory
    return build(parser.parse());
}

public SqlSessionFactory build(Configuration config) {
  return new DefaultSqlSessionFactory(config);
}

4.创建XMLConfigBuilder

创建 SqlSessionFactory 需要依赖XMLConfigBuilder 来读取并且解析配置文件,将配置文件转化为Document对象,初始化configuration 对象并且根据配置文件填充属性。

public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
   this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}

private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
   // 调用父类初始化configuration
   super(new Configuration());
   ErrorContext.instance().resource("SQL Mapper Configuration");    
   // 将Properties全部设置到configuration里面去
   this.configuration.setVariables(props);
   this.parsed = false;
   this.environment = environment;
  // 绑定 XPathParser
   this.parser = parser;
}

初始化 Configuration 对象

初始化 Configuration 时,会将要用到的 class 和其别名注册到 typeAliases 这个map中。

private final Map<String, Class<?>> typeAliases = new HashMap<>();
public Configuration() {
  typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
  typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);

  typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
  typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
  typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);

  typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
  typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
  typeAliasRegistry.registerAlias("LRU", LruCache.class);
  typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
  typeAliasRegistry.registerAlias("WEAK", WeakCache.class);

  typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);

  typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
  typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);

  typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
  typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
  typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
  typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
  typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
  typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
  typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);

  typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
  typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);

  languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
  languageRegistry.register(RawLanguageDriver.class);
}

5.创建 XPathParser

XPathParser 里面包含了很多重要的对象,包括用于读取本地DTD文件XMLMapperEntityResolver 对象、Xpath对象、存储xml文件的document对象以及properties标签定义的键值对集合对象variables

在这里插入图片描述


public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
  this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
  // 初始化属性
  commonConstructor(validation, variables, entityResolver);
  // 读取xml文件,保存为 document
  this.document = createDocument(new InputSource(inputStream));
}
public class XPathParser {
  private final Document document;
  private EntityResolver entityResolver;
  private Properties variables;
  private XPath xpath;
}
  • document
  • entityResolver
  • variables
  • xpath

6.解析并设置 configuration 中的属性

MyBatis的配置解析都是基于 <configuration> 标签下

XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());

// XMLConfigBuilder
public Configuration parse() {
  // 根据parsed变量的值判断是否已经完成了对mybatis-config.xml配置文件的解析
  if (parsed) {
    throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  }
  parsed = true;
  // 在mybatis-config.xml配置文件中查找<configuration>节点,并开始解析
  parseConfiguration(parser.evalNode("/configuration"));
  return configuration;
}

配置文件的解析需要 xpath的表达式和 document 对象, 根据标签名称匹配到节点, 取出对于的value, 找到节点返回XNode对象, 然后初始化Configuration

//XPathParser
public XNode evalNode(String expression) {
  return evalNode(document, expression);
}

解析 Configuration 中每个节点

一切配置都在这里解析并完成属性的设置, 比如设置properties, 创建PooledDataSourceFactory, 解析插件, 处理mapper映射。

private void parseConfiguration(XNode root) {
  // issue #117 read properties first
  // 解析properties,并设置variables属性,同时设置在parser和configuration中
  propertiesElement(root.evalNode("properties"));
  // 解析settings
  Properties settings = settingsAsProperties(root.evalNode("settings"));
  // 设置vfsImpl字段
  loadCustomVfs(settings);
  loadCustomLogImpl(settings);
  // 解析类型别名
  typeAliasesElement(root.evalNode("typeAliases"));
  // 解析插件
  pluginElement(root.evalNode("plugins"));
  // 对象工厂
  objectFactoryElement(root.evalNode("objectFactory"));
  // 对象包装工厂
  objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
  // 反射工厂
  reflectorFactoryElement(root.evalNode("reflectorFactory"));
  settingsElement(settings);//设置具体的属性到configuration对象
  // read it after objectFactory and objectWrapperFactory issue #631
  // 环境 设置transactionManager 、dataSource
  environmentsElement(root.evalNode("environments"));
  // databaseIdProvider
  databaseIdProviderElement(root.evalNode("databaseIdProvider"));
  // 类型处理器
  typeHandlerElement(root.evalNode("typeHandlers"));
    // 映射器 最重要!
  mapperElement(root.evalNode("mappers"));
}

解析 envirmonent 节点

由于我们的XML文件中配置了environment 节点,所以会对其进行解析

<environments default="development">
    <environment id="development">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="${driver}"/>
            <property name="url" value="${url}"/>
            <property name="username" value="${username}"/>
            <property name="password" value="${password}"/>
        </dataSource>
    </environment>
</environments>
private void environmentsElement(XNode context) throws Exception {
  if (context != null) {
    // 未指定XMLConfigBuilder.environment字段,则使用default属性
    if (environment == null) {
      environment = context.getStringAttribute("default");
    }
    // 遍历子节点
    for (XNode child : context.getChildren()) {
      String id = child.getStringAttribute("id");
      // 与XmlConfigBuilder.environment字段匹配
      if (isSpecifiedEnvironment(id)) {
        // 创建TransactionFactory
        TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
        // 创建DataSourceFactory和DataSource
        DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
        DataSource dataSource = dsFactory.getDataSource();
        // 创建Environment
        Environment.Builder environmentBuilder = new Environment.Builder(id)
            .transactionFactory(txFactory)
            .dataSource(dataSource);
        // 将Environment对象记录到Configuration.environment字段中
        configuration.setEnvironment(environmentBuilder.build());
        break;
      }
    }
  }
}

7.解析Mappers标签

<mappers>
    <package name="com.daley.dao"/>
</mappers>

如果节点名称等于package

  1. 会自动扫描包路径下的所以mapper,
  2. configuration中的mapperRegistry会add对应的package路径
  3. 将最终mapper对应的MapperProxyFactory 添加到knowMappers中
private void mapperElement(XNode parent) throws Exception {
  if (parent != null) {
    // 处理mapper子节点
    for (XNode child : parent.getChildren()) {
      // package子节点
      if ("package".equals(child.getName())) {
        // 自动扫描包下所有映射器
        String mapperPackage = child.getStringAttribute("name");
        // 扫描指定的包,并向mapperRegistry注册mapper接口
        configuration.addMappers(mapperPackage);
      } else {
        
        // 直接指定Mapper属性
        // 获取mapper节点的resource、url、class属性,三个属性互斥
        String resource = child.getStringAttribute("resource");
        String url = child.getStringAttribute("url");
        String mapperClass = child.getStringAttribute("class");
        // 如果mapper节点指定了resource或者url属性,则创建XmlMapperBuilder对象,并通过该对象解析resource或者url属性指定的mapper配置文件
        if (resource != null && url == null && mapperClass == null) {
          // 使用类路径
          ErrorContext.instance().resource(resource);
          try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
            // 创建XMLMapperBuilder对象,解析映射配置文件
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();
          }
        } else if (resource == null && url != null && mapperClass == null) {
          // 使用绝对url路径
          ErrorContext.instance().resource(url);
          try(InputStream inputStream = Resources.getUrlAsStream(url)){
            // 创建XMLMapperBuilder对象,解析映射配置文件
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
          }
        } else if (resource == null && url == null && mapperClass != null) {
          // 如果mapper节点指定了class属性,则向MapperRegistry注册该mapper接口
          Class<?> mapperInterface = Resources.classForName(mapperClass);
          // 直接把这个映射加入配置
          configuration.addMapper(mapperInterface);
        } else {
          throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
        }
      }
    }
  }
}

在这里插入图片描述

//MapperRegistry
public void addMappers(String packageName) {
    addMappers(packageName, Object.class);
  }
public void addMappers(String packageName, Class<?> superType) {
  // 查找包下所有父类是 Objects.class 的类
  ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
  resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
  Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
  for (Class<?> mapperClass : mapperSet) {
    addMapper(mapperClass);
  }
}
// MapperRegistry.addMapper()
public <T> void addMapper(Class<T> type) {
 ……
    boolean loadCompleted = false;
    // 将Mapper接口对应的Class对象和MapperProxyFactory对象添加到knownMappers集合
    knownMappers.put(type, new MapperProxyFactory<>(type));
    
    // 对被注解接口的解析逻辑
    MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
    parser.parse();
    loadCompleted = true;
}

在添加完mapper映射关系后,会开始对Mapper接口中相关注解的解析工作

  1. 会读取mapper class对应的mapper xml文件, 并且在读取后对文件中的内容进行解析
  2. 变量mapper class每一个方法, 读取标识的注解进行解析, 如resultMap
public void parse() {
  String resource = type.toString();
  // 检测是否已经加载过该接口
  if (!configuration.isResourceLoaded(resource)) {
    // 检测是否加载过对应的映射配置文件,如果未加载,则创建XMLMapperBuilder对象解析对应的mapper映射xml文件
    loadXmlResource();
    configuration.addLoadedResource(resource);
    assistant.setCurrentNamespace(type.getName());
    // 解析@CacheNamespace注解
    parseCache();
    // 解析@CacheNamespaceRef注解
    parseCacheRef();
    //开始解析每个方法
    for (Method method : type.getMethods()) {
      if (!canHaveStatement(method)) {
        continue;
      }
      if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
          && method.getAnnotation(ResultMap.class) == null) {
        parseResultMap(method);
      }
      try {
        // 解析@SelectKey,@ResultMap等注解,并创建MappedStatement对象
        parseStatement(method);
      } catch (IncompleteElementException e) {
        // 如果解析过程出现IncompleteElementException异常,可能是引用了未解析的注解,此处将出现异常的方法添加到incompleteMethod集合中保存
        configuration.addIncompleteMethod(new MethodResolver(this, method));
      }
    }
  }
  // 遍历incompleteMethods集合中记录的未解析的方法,并重新进行解析
  parsePendingMethods();
}

loadXmlResource方法会调用parse方法对象xml的mapper中内容进行解析。

public void parse() {
  // 判断是否已经加载过该映射文件
  if (!configuration.isResourceLoaded(resource)) {
    // 处理mapper节点
    configurationElement(parser.evalNode("/mapper"));
    // 将resource添加到Configuration.loadedResources集合中保存,他是hashset类型的集合,其中记录了已经加载过的映射文件
    configuration.addLoadedResource(resource);
    // 绑定映射器到namespace
    bindMapperForNamespace();
  }

  // 处理ConfigurationElement方法中解析失败的resultMap节点
  parsePendingResultMaps();
  // 处理ConfigurationElement方法中解析失败的cache-ref节点
  parsePendingCacheRefs();
  // 处理ConfigurationElement方法中解析失败的SQL语句节点
  parsePendingStatements();
}

configurationElement方法: 对sql, resultMap, parameterMap等节点进行解析

private void configurationElement(XNode context) {
  try {
    // 获取mapper节点的namespace属性
    String namespace = context.getStringAttribute("namespace");
    if (namespace == null || namespace.isEmpty()) {
      throw new BuilderException("Mapper's namespace cannot be empty");
    }
    // 设置MapperBuilderAssistant的currentNamespace字段,记录当前命名空间
    builderAssistant.setCurrentNamespace(namespace);
    // 解析cache-ref节点
    cacheRefElement(context.evalNode("cache-ref"));
    // 解析cache节点
    cacheElement(context.evalNode("cache"));
    // 解析parameterMap节点
    parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    // 解析resultMap节点
    resultMapElements(context.evalNodes("/mapper/resultMap"));
    // 解析sql节点
    sqlElement(context.evalNodes("/mapper/sql"));
    // 解析select、update、insert、delete等SQL节点
    // 就在这里开始创建sql与方法的映射关系并且保存
    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);
  }
}

Configuration 创建完毕, 返回创建好的SqlSessionFactory, 然后建后续需要的Transaction、Executor和DefaultSqlSession


SqlSession sqlSession = sqlSessionFactory.openSession();
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  Transaction tx = null;
  try {
    // 获取mybatis-config.xml配置文件中配置的Environment对象,
    final Environment environment = configuration.getEnvironment();
    // 获取TransactionFactory对象
    final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
    // 创建Transaction对象
    tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
    // 根据配置创建Executor对象
    final Executor executor = configuration.newExecutor(tx, execType);
    return new DefaultSqlSession(configuration, executor, autoCommit);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/161039.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Day858.高性能网络应用框架Netty -Java 并发编程实战

高性能网络应用框架Netty Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于高性能网络应用框架Netty的内容。 Netty 是一个高性能网络应用框架&#xff0c;应用非常普遍&#xff0c;目前在 Java 领域里&#xff0c;Netty 基本上成为网络程序的标配了。 Netty 框架功…

win10录屏软件哪款比较好用?一款不限时长的录屏软件

现在大部分人的电脑都是win10系统的电脑&#xff0c;也有许多小伙伴会经常会问&#xff1a;“win10电脑怎么录屏&#xff1f;”录制电脑屏幕&#xff0c;需要使用到录屏软件&#xff0c;那win10录屏软件哪款比较好用&#xff1f;小编今天给大家分享一款试用版即可不限录制时长的…

【区间合并】洛谷 P1496 火烧赤壁

P1496 火烧赤壁 文章目录题目背景题目描述输入格式&#xff1a;输出格式&#xff1a;数据范围输入样例输出样例方法&#xff1a;区间合并解题思路代码复杂度分析&#xff1a;题目背景 曹操平定北方以后&#xff0c;公元 208 年&#xff0c;率领大军南下&#xff0c;进攻刘表。…

部分时变离散系统中的稳定性判据

部分时变离散系统中的稳定性判据 1.Lyapunov稳定性理论 下面先给出Lyapunov稳定性的一些基本理论&#xff08;网上资源较多这里不再过多赘述&#xff09;&#xff1a; 2.一类时变离散系统的稳定性 定理 ​ 对于离散时变系统x(k1)A(k)x(k)x(k1)A(k)x(k)x(k1)A(k)x(k)&#x…

Java EE|多线程代码实例之单例模式与阻塞队列

文章目录前言设计模式介绍&#x1f534;单例模式什么是单例模式单例模式实现方式饿汉模式懒汉模式基于上述单例模式实现线程安全问题讨论重点回顾&#x1f534;阻塞队列阻塞队列是什么标准库中的阻塞队列典型应用场景&#xff1a;生产者消费者模型利用系统提供的BlockingQueue实…

osg fbo(三),将颜色缓冲区图片通过shader变绿

这个其实很简单&#xff0c; 一&#xff0c;写顶点着色器和片元着色器 static const char * vertexShader { “void main(void)\n” “{\n” " gl_Position ftransform();\n" “}\n” }; static const char *psShader { “uniform float alpha;” “void main(vo…

12、ThingsBoard-如何配置发送邮件

1、概述 ThingsBoard提供了系统层设置邮件配置和租户层通过设置邮件规则节点,对规则引擎产生的告警进行分发这两种邮件配置,其中系统层设置邮件配置主要是针对用于向用户分发激活和密码重置电子邮件;租户层通过设置邮件规则节点是针对告警通知的;一定要区别开这两个邮件配…

SpringBoot整合SpringSecurity实现进行认证和授权。

目录 2.在子工程通过easyCode创建项目相关包和文件 3.子项目新建Controllter层&#xff0c;并建立BlogLoginController.java 4.在servic 层定义login 方法&#xff0c;并new UsernamePasswordAuthenticationToken对象&#xff0c;传入对应用户名&#xff0c;密码 5.自定义实…

Java集合(进阶)

Java集合Collection集合体系结构CollectionCollection系列集合三种遍历方式List泛型泛型类泛型方法泛型接口泛型的继承和通配符SetHashSetTreeSet总结&#xff1a;Map&#xff08;双列集合&#xff09;HashMapLinkedHashMapTreeMap可变参数集合工具类Collections集合嵌套案例不…

打破应用孤岛,iPaaS连接全域新协作

“据全球知名的咨询平台Garner分析&#xff0c;集成平台将在企业数字化转型过程中扮演重要的角色&#xff0c;企业内外应用的打通成为推动企业快速实现数字化转型的重要因素之一。SaaS 的井喷式发展也带来了新的机遇与挑战&#xff0c;企业亟需新的集成方法和手段帮助解决自身问…

吴恩达【神经网络和深度学习】Week4——深层神经网络

文章目录Deep Neural Network1、Deep L-layer Neural Network2、Forward Propagation in a Deep Network3、Getting your matrix dimensions right4、Why deep representations?5、 Building blocks of deep neural networks6、 Forward and Backward Propagation7、Parameter…

【Ctfer训练计划】——(十一)

作者名&#xff1a;Demo不是emo主页面链接&#xff1a; 主页传送门创作初心&#xff1a; 舞台再大&#xff0c;你不上台&#xff0c;永远是观众&#xff0c;没人会关心你努不努力&#xff0c;摔的痛不痛&#xff0c;他们只会看你最后站在什么位置&#xff0c;然后羡慕或鄙夷座右…

最新版wifi营销分销流量主前后端+小程序源码+搭建教程

前端后端数据库搭建教程&#xff0c;无任何密码&#xff0c;亲测能用&#xff0c;避免踩坑&#xff0c;v&#xff1a;JZ716888 教程如下&#xff1a; 安装源码到根目录 1、网站运行目录public 2、PHP7.2&#xff0c;开通SSL 3、导入数据库文件 4、修改数据库文件里applic…

【十一】Netty UDP协议栈开发

Netty UDP协议栈开发介绍协议简介伪首部UDP协议的特点开发jar依赖UDP 服务端启动类服务端业务处理类客户端启动类客户端业务处理类代码说明测试服务端打印截图&#xff1a;客户端打印截图:测试结果总结介绍 UDP 是用户数据报协议(User Datagram Protocol) 的简称&#xff0c;其…

【Azure 架构师学习笔记】-Azure Logic Apps(4)-演示2

本文属于【Azure 架构师学习笔记】系列。 本文属于【Azure Logic Apps】系列。 接上文[【Azure 架构师学习笔记】-Azure Logic Apps&#xff08;3&#xff09;-演示1] (https://blog.csdn.net/DBA_Huangzj/article/details/128542539) 前言 上文做了简单的演示&#xff0c;这一…

【Flutter】关于Button 的那些知识ElevatedButton等,以及Buttonstyle

文章目录前言一、Button是什么&#xff1f;二、开始使用button1.ElevatedButton1.无style 的ElevatedButton2.基础功能的处理之后的button3.利用buttonstyle 来美化下button2.IconButton&#xff0c;TextButton基础功能都是一样的三、做几个好看点的按键总结前言 一、Button是什…

【设计模式】七大设计原则

设计模式学习之旅(二) 查看更多可关注后查看主页设计模式DayToDay专栏 在软件开发中&#xff0c;为了提高软件系统的可维护性和可复用性&#xff0c;增加软件的可扩展性和灵活性&#xff0c;程序员要尽量根据7条原则来开发程序&#xff0c;从而提高软件开发效率、节约软件开发成…

SAP 详细解析在建工程转固定资产

由固定资产归口采购部门或业务部门提交购置固定资产/在建工程的申请&#xff0c;经审批后&#xff0c;若是需要安装调试&#xff0c;则由财务部固定资产会计建立内部订单收集成本&#xff0c;月末结转在建工程。项目完工后&#xff0c;相关部门&#xff08;公司装备部、分公司装…

数据库设计之三范式

写在前面 很多数据库设计者&#xff0c;都是按照自己的性子和习惯来设计数据库数据表&#xff0c;其实不然。 其实&#xff0c;数据库的设计也有要遵循的原则。 范式&#xff0c;就是规范&#xff0c;就是指设计数据库需要&#xff08;应该&#xff09;遵循的原则。 每个范…

智慧变频中的数据监测、下发控制以及告警推送

[小 迪 导读]&#xff1a;在智能制造的推动下&#xff0c;制造商对于变频器在绿色节能、智能运行、远程维护以及大数据等方面的需求也日趋凸显。针对传统变频器无法满足智能时代的需求问题&#xff0c;dgiot可适配多种DTU/网关对变频器进行数据监测、下发控制以及告警推送。概述…