修改了mybatis的xml中的sql不重启服务器如何动态加载更新

news2024/9/22 21:36:16

目录

一、背景

二、注意

三、代码

四、使用示例

五、其他参考博客


一、背景

开发一个报表功能,好几百行sql,每次修改完想自测下都要重启服务器,启动一次服务器就要3分钟,重启10次就要半小时,耗不起时间呀。于是在网上找半天,没发现能直接用的, 最后还是乖乖用了自己的业余时间,参考了网上内容写了个合适自己的类。

二、注意

1.本类在mybatis-plus-boot-starter 3.4.0, mybatis3.5.5下有效,其他版本没试过

2.部分idea版本修改了xml中的sql后,并不会直接写入到硬盘中,而是保留在内存中,需要手动ctrl+s或者切换到其他窗口才能触发写入新内容到硬盘,所以使用本类时要确认你修改的sql确实已经保存进硬盘里了

3.xml所在文件夹的绝对位置,需要你修改下,再使用本类
 

三、代码

用一个类就能实现开发阶段sql热更新

这个类可以配置启动一个线程,每10秒重新加载最近有修改过的sql

也可以调用一下接口重新修改过的sql

代码如下:

package com.gree;


import com.baomidou.mybatisplus.core.MybatisMapperRegistry;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.builder.xml.XMLMapperEntityResolver;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.keygen.SelectKeyGenerator;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.parsing.XPathParser;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.*;

/**
 * 本类用于热部署mybatis xml中修改的sql
 * 注意:
 *  1.本类在mybatis-plus-boot-starter 3.4.0 和 mybatis3.5.5 下有效,其他版本没试过
 *  2.部分idea版本修改了xml中的sql后,并不会直接写入到硬盘中,而是保留在内存中,需要手动ctrl+s或者切换到其他窗口才能触发写入新内容到硬盘,所以使用本类时要确认你修改的sql确实已经保存进硬盘里了
 *  3.xml所在文件夹的绝对位置,需要你修改下,再使用本类
 */
@RestController
public class MybatisMapperRefresh {


    //xml所在文件夹的绝对位置(这里改成你的位置)
    private String mapperPath = "D:\\wjh\\Mome\\openGitCode\\mybatisRefreshDemo\\src\\main\\resources\\mapper";
    //是否需要启动一个线程,每隔一段时间就刷新下
    private boolean needStartThread = false;
    //刷新间隔时间(秒)
    private int sleepSeconds = 10;




    //项目启动时间(或加载本class的时间)
    private long startTime = new Date().getTime();
    //上次执行更新xml的时间
    private long lasteUpdateTime = 0;

    private static final Log logger = LogFactory.getLog(MybatisMapperRefresh.class);

    private SqlSessionFactory sqlSessionFactory;
    private Configuration configuration;


    /**
     * 构造函数,由spring调用生成bean
     * @param sqlSessionFactory
     */
    public MybatisMapperRefresh(
            SqlSessionFactory sqlSessionFactory
    ) {
        this.sqlSessionFactory = sqlSessionFactory;
        this.configuration = sqlSessionFactory.getConfiguration();
        if (needStartThread) {
            this.startThread();
        }
    }


    /**
     * 调用这个接口刷新你的sql,接口会返回刷新了哪些xml
     * @return
     */
    @RequestMapping("/sql/refresh")
    public List<String> refreshMapper() {
        List<String> refreshedList = new ArrayList<>();
        try {
            refreshedList = refreshDir();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return refreshedList;
    }


    /**
     * 启动一个线程,每间隔一段时间就更新下xml中的sql
     */
    public void startThread() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    logger.warn("线程循环中!");
                    try {
                        refreshDir();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    try {
                        Thread.sleep(sleepSeconds * 1000);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "mybatis-plus MapperRefresh").start();
    }


    /**
     * 刷新指定目录下所有xml文件
     *
     * @throws Exception
     */
    private List<String> refreshDir() throws Exception {
        List<String> refreshedList = new ArrayList<>();
        try {
            //获取指定目录下,修改时间大于上次刷新时间,并且修改时间大于项目启动时间的xml
            List<File> fileList = FileUtil.getAllFiles(mapperPath);
            ArrayList<File> needUpdateFiles = new ArrayList<>();
            for (File file : fileList) {
                long lastModified = file.lastModified();
                if (file.isFile() && startTime <= lastModified && lasteUpdateTime <= lastModified) {
                    needUpdateFiles.add(file);
                    continue;
                }
            }
            //逐个xml刷新
            if (needUpdateFiles.size() != 0) {
                lasteUpdateTime = new Date().getTime();
            }
            for (File file : needUpdateFiles) {
                Resource refresh = refresh(new FileSystemResource(file));
                if(refresh != null){
                    refreshedList.add(refresh.getFile().getAbsolutePath());
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        //返回已刷新的文件
        return refreshedList;
    }


    /**
     * 刷新mapper
     */
    private Resource refresh(Resource resource) throws Exception {


        //打印一下流,看看有没有获取到更新的内容
        /*InputStream inputStream = resource.getInputStream();
        InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
        BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
        String line;
        while ((line = bufferedReader.readLine()) != null) {
            System.out.println(line);
        }
        bufferedReader.close();*/


        boolean isSupper = configuration.getClass().getSuperclass() == Configuration.class;
        try {
            //清理loadedResources
            //loadedResources:用于注册所有 Mapper XML 配置文件路径
            Field loadedResourcesField = isSupper
                    ? configuration.getClass().getSuperclass().getDeclaredField("loadedResources")
                    : configuration.getClass().getDeclaredField("loadedResources");
            loadedResourcesField.setAccessible(true);
            Set<String> loadedResourcesSet = ((Set<String>) loadedResourcesField.get(configuration));
            loadedResourcesSet.remove(resource.toString());

            //分析需要刷新的xml文件
            XPathParser xPathParser = new XPathParser(resource.getInputStream(), true, configuration.getVariables(),
                    new XMLMapperEntityResolver());
            //得到xml中的mapper节点
            XNode xNode = xPathParser.evalNode("/mapper");
            String xNodeNamespace = xNode.getStringAttribute("namespace");

            //清理mapperRegistry中的knownMappers
            //mapperRegistry:用于注册 Mapper 接口信息,建立 Mapper 接口的 Class 对象和 MapperProxyFactory 对象之间的关系,其中 MapperProxyFactory 对象用于创建 Mapper 动态代理对象
            Field knownMappersField = MybatisMapperRegistry.class.getDeclaredField("knownMappers");
            knownMappersField.setAccessible(true);
            Map knownMappers = (Map) knownMappersField.get(configuration.getMapperRegistry());
            knownMappers.remove(Resources.classForName(xNodeNamespace));

            //清理caches
            //caches:用于注册 Mapper 中配置的所有缓存信息,其中 Key 为 Cache 的 id,也就是 Mapper 的命名空间,Value 为 Cache 对象
            configuration.getCacheNames().remove(xNodeNamespace);

            //其他清理操作
            cleanParameterMap(xNode.evalNodes("/mapper/parameterMap"), xNodeNamespace);
            cleanResultMap(xNode.evalNodes("/mapper/resultMap"), xNodeNamespace);
            cleanKeyGenerators(xNode.evalNodes("insert|update|select|delete"), xNodeNamespace);
            cleanMappedStatements(xNode.evalNodes("insert|update|select|delete"), xNodeNamespace);
            cleanSqlElement(xNode.evalNodes("/mapper/sql"), xNodeNamespace);

            //重新加载xml文件
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(resource.getInputStream(),
                    configuration, resource.toString(),
                    configuration.getSqlFragments());
            xmlMapperBuilder.parse();


            logger.warn("重新加载成功: " + resource );
            return resource;
        } catch (IOException e) {
            logger.error("重新加载失败 :" ,e);
        } finally {
            ErrorContext.instance().reset();
        }
        return null;
    }

    /**
     * 清理parameterMap
     * parameterMap用于注册 Mapper 中通过 标签注册的参数映射信息。Key 为 ParameterMap 的 id,由 Mapper 命名空间和 标签的 id 属性构成,Value 为解析 标签后得到的 ParameterMap 对象
     *
     * @param list
     * @param namespace
     */
    private void cleanParameterMap(List<XNode> list, String namespace) {
        for (XNode parameterMapNode : list) {
            String id = parameterMapNode.getStringAttribute("id");
            configuration.getParameterMaps().remove(namespace + "." + id);
        }
    }

    /**
     * 清理resultMap
     * resultMap用于注册 Mapper 配置文件中通过 标签配置的 ResultMap 信息,ResultMap 用于建立 Java 实体属性与数据库字段之间的映射关系,其中 Key 为 ResultMap 的 id,该 id 是由 Mapper 命名空间和 标签的 id 属性组成的,Value 为解析 标签后得到的 ResultMap 对象
     *
     * @param list
     * @param namespace
     */
    private void cleanResultMap(List<XNode> list, String namespace) {
        for (XNode resultMapNode : list) {
            String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier());
            configuration.getResultMapNames().remove(id);
            configuration.getResultMapNames().remove(namespace + "." + id);
            clearResultMap(resultMapNode, namespace);
        }
    }


    /**
     * 清理ResultMap
     * ResultMap用于注册 Mapper 配置文件中通过 标签配置的 ResultMap 信息,ResultMap 用于建立 Java 实体属性与数据库字段之间的映射关系,其中 Key 为 ResultMap 的 id,该 id 是由 Mapper 命名空间和 标签的 id 属性组成的,Value 为解析 标签后得到的 ResultMap 对象
     */
    private void clearResultMap(XNode xNode, String namespace) {
        for (XNode resultChild : xNode.getChildren()) {
            if ("association".equals(resultChild.getName()) || "collection".equals(resultChild.getName())
                    || "case".equals(resultChild.getName())) {
                if (resultChild.getStringAttribute("select") == null) {
                    configuration.getResultMapNames()
                            .remove(resultChild.getStringAttribute("id", resultChild.getValueBasedIdentifier()));
                    configuration.getResultMapNames().remove(namespace + "."
                            + resultChild.getStringAttribute("id", resultChild.getValueBasedIdentifier()));
                    if (resultChild.getChildren() != null && !resultChild.getChildren().isEmpty()) {
                        clearResultMap(resultChild, namespace);
                    }
                }
            }
        }
    }

    /**
     * 清理keyGenerators
     * keyGenerators:用于注册 KeyGenerator,KeyGenerator 是 MyBatis 的主键生成器,MyBatis 提供了三种KeyGenerator,即 Jdbc3KeyGenerator(数据库自增主键)、NoKeyGenerator(无自增主键)、SelectKeyGenerator(通过 select 语句查询自增主键,例如 oracle 的 sequence)
     *
     * @param list
     * @param namespace
     */
    private void cleanKeyGenerators(List<XNode> list, String namespace) {
        for (XNode xNode : list) {
            String id = xNode.getStringAttribute("id");
            configuration.getKeyGeneratorNames().remove(id + SelectKeyGenerator.SELECT_KEY_SUFFIX);
            configuration.getKeyGeneratorNames().remove(namespace + "." + id + SelectKeyGenerator.SELECT_KEY_SUFFIX);
        }
    }


    /**
     * 清理MappedStatements
     * MappedStatement 对象描述 <insert|selectlupdateldelete> 等标签或者通过 @Select|@Delete|@Update|@Insert 等注解配置的 SQL 信息。MyBatis 将所有的 MappedStatement 对象注册到该属性中,其中 Key 为 Mapper 的 Id, Value 为 MappedStatement 对象
     *
     * @param list
     * @param namespace
     */
    private void cleanMappedStatements(List<XNode> list, String namespace) {
        Collection<MappedStatement> mappedStatements = configuration.getMappedStatements();
        List<MappedStatement> objects = new ArrayList<>();
        for (XNode xNode : list) {
            String id = xNode.getStringAttribute("id");
            Iterator<MappedStatement> it = mappedStatements.iterator();
            while (it.hasNext()) {
                Object object = it.next();
                if (object instanceof org.apache.ibatis.mapping.MappedStatement) {
                    MappedStatement mappedStatement = (MappedStatement) object;
                    if (mappedStatement.getId().equals(namespace + "." + id)) {
                        objects.add(mappedStatement);
                    }
                }
            }
        }
        mappedStatements.removeAll(objects);
    }


    /**
     * 清理sql节点缓存
     * 用于注册 Mapper 中通过 标签配置的 SQL 片段,Key 为 SQL 片段的 id,Value 为 MyBatis 封装的表示 XML 节点的 XNode 对象
     *
     * @param list
     * @param namespace
     */
    private void cleanSqlElement(List<XNode> list, String namespace) {
        for (XNode context : list) {
            String id = context.getStringAttribute("id");
            configuration.getSqlFragments().remove(id);
            configuration.getSqlFragments().remove(namespace + "." + id);
        }
    }


    public static class FileUtil {
        /**
         * 列出执行文件夹下的所有文件,包含子目录文件
         */
        public static List<File> getAllFiles(String folderPath) {
            List<File> fileList = new ArrayList<>();
            File folder = new File(folderPath);
            if (!folder.exists() || !folder.isDirectory()) {
                return fileList;
            }
            File[] files = folder.listFiles();
            for (File file : files) {
                if (file.isFile()) {
                    fileList.add(file);
                } else if (file.isDirectory()) {
                    fileList.addAll(getAllFiles(file.getAbsolutePath()));
                }
            }
            return fileList;
        }
    }
}

pom.xml文件也贴出来给大家参考

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.greetree</groupId>
    <artifactId>mybatisRefreshDemo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>


    <properties>
        <java.version>1.8</java.version>
    </properties>


    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.0</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.25</version>
        </dependency>

        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.3.0</version>
        </dependency>


    </dependencies>


</project>

四、使用示例

1. 将类MybatisMapperRefresh粘贴到可以被spring扫描到的任意目录

2. 修改类中的mapperPath,改成你的xml文件所在目录

3. 启动服务器

4. 修改你的xml文件里的sql

5.ctrl+s保存文件,确保idea将修改内容写入到硬盘,而不是在内存中

6.调用接口http://localhost:{你项目端口号}/sql/refresh 更新sql,接口会返回刷新了哪些xml

7.验证你的sql是否热更新了

五、其他参考博客

IDEA的热部署【MyBatis XML热部署 】_怎么配热部署实现更新ibatis的xml文件-CSDN博客

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

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

相关文章

获取欧洲时报中国板块前新闻数据-scrapy

这里写目录标题 1.创建项目文件二.爬虫文件编写三.管道存储四.settings文件 1.创建项目文件 创建scrapy项目的命令&#xff1a;scrapy startproject <项目名字> 示例&#xff1a; scrapy startproject myspiderscrapy genspider <爬虫名字> <允许爬取的域名>…

tinymce富文本支持word内容同时粘贴文字图片上传 vue2

效果图 先放文件 文件自取tinymce: tinymce富文本简单配置及word内容粘贴图片上传 封装tinymce 文件自取&#xff1a;tinymce: tinymce富文本简单配置及word内容粘贴图片上传 页面引用组件 <TinymceSimplify refTinymceSimplify v-model"knowledgeBlockItem.content…

vue使用audio 音频实现播放与关闭(可用于收到消息给提示音效)

这次项目中因为对接了即时通讯 IM&#xff0c;有个需求就是收到消息需要有个提示音效&#xff0c;所以这里就想到了用HTML5 提供的Audio 标签&#xff0c;用起来也是很方便&#xff0c;首先让产品给你个提示音效&#xff0c;然后你放在项目中&#xff0c;使用Audio 标签&#x…

HardeningMeter:一款针对二进制文件和系统安全强度的开源工具

关于HardeningMeter HardeningMeter是一款针对二进制文件和系统安全强度的开源工具&#xff0c;该工具基于纯Python开发&#xff0c;经过了开发人员的精心设计&#xff0c;可以帮助广大研究人员全面评估二进制文件和系统的安全强化程度。 功能特性 其强大的功能包括全面检查各…

【BUG】已解决:WslRegisterDistribution failed with error: 0x800701bc

已解决&#xff1a;WslRegisterDistribution failed with error: 0x800701bc 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&#xff0c;我是博主英杰&#xff0c;211科班出身&#xff0c;就职于医疗科技公司&#xff0c;热衷分享知识&#xff0c;武…

Ubuntu22.04安装CUDA+CUDNN+Conda+PyTorch

步骤&#xff1a; 1、安装显卡驱动&#xff1b; 2、安装CUDA&#xff1b; 3、安装CUDNN&#xff1b; 4、安装Conda&#xff1b; 5、安装Pytorch。 一、系统和硬件信息 1、Ubuntu 22.04 2、显卡&#xff1a;4060Ti 二、安装显卡驱动 &#xff08;已经安装的可以跳过&a…

通过SchedulingConfigurer 接口完成动态定时任务

通过SchedulingConfigurer 接口完成动态定时任务 一.背景 在Spring中&#xff0c;除了使用Scheduled注解外&#xff0c;还可以通过实现SchedulingConfigurer接口来创建定时任务。它们之间的主要区别在于灵活性和动态性。Scheduled注解适用于固定周期的任务&#xff0c;一旦任…

【STM32 HAL库】I2S的使用

使用CubeIDE实现I2S发数据 1、配置I2S 我们的有效数据是32位的&#xff0c;使用飞利浦格式。 2、配置DMA **这里需要注意&#xff1a;**i2s的DR寄存器是16位的&#xff0c;如果需要发送32位的数据&#xff0c;是需要写两次DR寄存器的&#xff0c;所以DMA的外设数据宽度设置16…

JavaWeb服务器-Tomcat(Tomcat概述、Tomcat的下载、安装与卸载、启动与关闭、常见的问题)

Tomcat概述 Tomcat服务器软件是一个免费的开源的web应用服务器。是Apache软件基金会的一个核心项目。由Apache&#xff0c;Sun和其他一些公司及个人共同开发而成。 由于Tomcat只支持Servlet/JSP少量JavaEE规范&#xff0c;所以是一个开源免费的轻量级Web服务器。 JavaEE规范&…

Vscode中Github copilot插件无法使用(出现感叹号)解决方案

1、击扩展或ctrl shift x ​​​​​​​ 2、搜索查询或翻找到Github compilot 3、点击插件并再左侧点击登录github 点击Sign up for a ... 4、跳转至github登录页&#xff0c;输入令牌完成登陆后返回VScode 5、插件可以正常使用

uni-app:文字竖直排列,并且在父级view中水平竖直对齐

一、效果 二、代码 <template><view class"parent"><text class"child">这是竖直排列的文字</text></view> </template> <script>export default {data() {return {}},methods: {},}; </script> <sty…

FATE Flow 源码解析 - 日志输出机制

背景介绍 在 之前的文章 中介绍了 FATE 的作业处理流程&#xff0c;在实际的使用过程中&#xff0c;为了查找执行中的异常&#xff0c;需要借助运行生成的日志&#xff0c;但是 FATE-Flow 包含的流程比较复杂&#xff0c;对应的日志也很多&#xff0c;而且分散在不同的文件中&…

windows和linux的等保加固测评的经验分享

一头等保加固测评的牛马&#xff0c;需要能做到一下午测评n个服务器 接下来就讲讲如何当一头xxxxxxxxx》严肃的等保测评加固的经验分享&#xff08; 一、window等保 首先你要自己按着教程在虚拟机做过一遍&#xff08;win2012和win2008都做过一遍&#xff0c;大概windows的…

抖音短视频seo矩阵系统源码(搭建技术开发分享)

#抖音矩阵系统源码开发 #短视频矩阵系统源码开发 #短视频seo源码开发 一、 抖音短视频seo矩阵系统源码开发&#xff0c;需要掌握以下技术&#xff1a; 网络编程&#xff1a;能够使用Python、Java或其他编程语言进行网络编程&#xff0c;比如使用爬虫技术从抖音平台获取数据。…

How to integrate GPT-4 model hosted on Azure with the gptstudio package

题意&#xff1a;怎样将托管在Azure上的GPT-4模型与gptstudio包集成&#xff1f; 问题背景&#xff1a; I am looking to integrate the OpenAI GPT-4 model into my application. Here are the details I have: Endpoint: https://xxxxxxxxxxxxxxx.openai.azure.com/Locatio…

Gradio从入门到精通(8)---基础组件介绍2

文章目录 一、基础组件详解1.Dropdown2.Image3.Audio4.File 总结 一、基础组件详解 1.Dropdown 用途&#xff1a; 提供下拉菜单选项。 初始化参数&#xff1a; choices: 可选择的选项列表。 value: 默认选中的值。可以是字符串、列表或是一个可调用对象。 type: 组件返回…

【有效验证】解决SQLyog连接MYSQL的错误 1251 - Client does not support

目录 一、原因分析&#xff1a; 二、进入到mysql 三、查看当前加密方式 四、更改加密方式 五、查看是否成功 前言&#xff1a;使用一个开源软件使用sqlyog、navcat都报1251错误&#xff0c;网上都是提示升级客户端&#xff0c;还有一种就是修改mysql配置。本文就是修改配置…

逆向案例二十三——请求头参数加密,某区块链交易逆向

网址&#xff1a;aHR0cHM6Ly93d3cub2tsaW5rLmNvbS96aC1oYW5zL2J0Yy90eC1saXN0L3BhZ2UvNAo 抓包分析&#xff0c;发现请求头有X-Apikey参数加密&#xff0c;其他表单和返回内容没有加密。 直接搜索关键字&#xff0c;X-Apikey&#xff0c;找到疑似加密位置&#xff0c;注意这里…

R语言实现SVM算法——分类与回归

### 11.6 基于支持向量机进行类别预测 ### # 构建数据子集 X <- iris[iris$Species! virginica,2:3] # 自变量&#xff1a;Sepal.Width, Petal.Length y <- iris[iris$Species ! virginica,Species] # 因变量 plot(X,col y,pch as.numeric(y)15,cex 1.5) # 绘制散点图…

【DGL系列】DGLGraph.out_edges简介

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 目录 函数说明 用法示例 示例 1: 获取所有边的源节点和目标节点 示例 2: 获取特定节点的出边 示例 3: 获取所有边的边ID 示例 4: 获取所有信息&a…