在Spring Boot中使用单数据源的配置很简单,我们简单回忆下:只需要在application.properties进行基本的连接配置,在pom.xml引入基本的依赖即可。

那么多数据源的原理呢?其实很简单,就是读取配置文件,根据配置文件中的配置的数据源数量,动态创建dataSource并注册到Spring中。在上一节我们介绍了使用Java代码将对象注册到Spring中,多数据源就是基于这儿基础进行动态创建的。本节大概需要这么几个步骤:

(1)新建maven java project;

(2)在pom.xml添加相关依赖;

(3)编写app.java启动类;

(4)编写application.properties配置文件;

(5)编写多数据源注册文件;

(6)编写测试类

(7)测试
接下来让我们按照这个步骤来进行编写我们的代码吧。

(1)新建maven java project;

我们新建一个maven project进行测试,取名为:spring-boot-multids
(2)在pom.xml添加相关依赖;
  在pom.xml文件中加入依赖的库文件,主要是spring boot基本的,数据库驱动,spring-jpa支持即可,具体pom.xml文件如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<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.kfit</groupId>
  <artifactId>spring-boot-multids</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>
 
  <name>spring-boot-multids</name>
  <url>http://maven.apache.org</url>
 
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <!-- jdk版本号,这里需要你本地进行的jdk进行修改,这里angel使用的是1.8的版本. -->
    <java.version>1.8</java.version>
  </properties>
 
 
   <!--
       spring boot 父节点依赖,
       引入这个之后相关的引入就不需要添加version配置,
       spring boot会自动选择最合适的版本进行添加。
       在这里使用的1.3.3版本,可能目前官方有最新的版本了,大家可以
       使用最新的版本。
     -->
    <parent>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-parent</artifactId>
       <version>1.3.3.RELEASE</version>
    </parent>
 
  <dependencies>
    <!-- 单元测试包,在这里没有使用到. -->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <scope>test</scope>
    </dependency>
   
    <!-- spring boot web支持:mvc,aop...
        这个是最基本的,基本每一个基本的demo都是需要引入的。
    -->
    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
   
    <!-- mysql驱动.
       我们的demo是多数据源,在这里使用Mysql数据库.
    -->
    <dependency>
       <groupId>mysql</groupId>
       <artifactId>mysql-connector-java</artifactId>
    </dependency>
   
   
    <!-- spring jpa
       spring jpa中带有自带的tomcat数据连接池;
       在代码中我们也需要用到.
     -->
    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
   
  </dependencies>
</project>
在上面的配置文件中都有相应的解释,大家可以自己解读下。

(3)编写app.java启动类;
编写spring boot的启动类:
com.kfit.App:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.kfit;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
 
/**
 *
 *
 * @version v.0.1
 */
@SpringBootApplication
publicclass App {
    publicstaticvoid main(String[] args) {
       SpringApplication.run(App.class, args);
    }
}

(4)编写application.properties配置文件;

在这里主要是多数据源的配置:
src/main/resources/application.properties:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
########################################################
###配置文件包括1个主数据源和多个数据源,
###其中主数据源在Spring中的beanName默认为dataSource,
###另外几个数据源的beanName分包为:ds1、ds2、ds3
###其中datasource的type属性可以具体指定到我们需要的数据源上面,
###不指定情况下默认为:org.apache.tomcat.jdbc.pool.DataSource
###当然你也可以把这些数据源配置到主dataSource数据库中,然后读取数据库生成多数据源。当然这样做的必要性并不大,难不成数据源还会经常变吗。
########################################################
 
# 主数据源,默认的
#spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=root
 
 
# 更多数据源
custom.datasource.names=ds1,ds2,ds3
#custom.datasource.ds1.type=com.zaxxer.hikari.HikariDataSource
custom.datasource.ds1.driverClassName =com.mysql.jdbc.Driver
custom.datasource.ds1.url=jdbc:mysql://localhost:3306/test1
custom.datasource.ds1.username=root
custom.datasource.ds1.password=root
 
#custom.datasource.ds2.type=com.zaxxer.hikari.HikariDataSource
custom.datasource.ds2.driverClassName =com.mysql.jdbc.Driver
custom.datasource.ds2.url=jdbc:mysql://localhost:3306/test
custom.datasource.ds2.username=root
custom.datasource.ds2.password=root
 
#custom.datasource.ds3.type=com.zaxxer.hikari.HikariDataSource
custom.datasource.ds3.driverClassName =com.mysql.jdbc.Driver
custom.datasource.ds3.url=jdbc:mysql://localhost:3306/test
custom.datasource.ds3.username=root
custom.datasource.ds3.password=root
 
 
# 下面为连接池的补充设置,应用到上面所有数据源中
spring.datasource.maximum-pool-size=100
spring.datasource.max-idle=10
spring.datasource.max-wait=10000
spring.datasource.min-idle=5
spring.datasource.initial-size=5
spring.datasource.validation-query=SELECT 1
spring.datasource.test-on-borrow=false
spring.datasource.test-while-idle=true
spring.datasource.time-between-eviction-runs-millis=18800

(5)编写多数据源注册文件;

这个注入是最核心的部分,我们先看代码:
com.kfit.config.datasource.multids.MultipleDataSourceBeanDefinitionRegistryPostProcessor:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
package com.kfit.config.datasource.multids;
 
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
 
import javax.sql.DataSource;
 
import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.context.annotation.AnnotationScopeMetadataResolver;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ScopeMetadata;
import org.springframework.context.annotation.ScopeMetadataResolver;
import org.springframework.core.env.Environment;
 
/**
 * 动态创建多数据源注册到Spring中
 *
  接口:BeanDefinitionRegistryPostProcessor只要是注入bean,
  在上一节介绍过使用方式;
 
 接口:接口 EnvironmentAware 重写方法 setEnvironment
 可以在工程启动时,获取到系统环境变量和application配置文件中的变量。
这个第24节介绍过.
 
 方法的执行顺序是:
 
 setEnvironment()-->postProcessBeanDefinitionRegistry() --> postProcessBeanFactory()
 
 
 *
 *
 * @version v.0.1
 */
@Configuration
publicclass MultipleDataSourceBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor,EnvironmentAware{
 
   
    //作用域对象.
    private ScopeMetadataResolver scopeMetadataResolver = new AnnotationScopeMetadataResolver();
    //bean名称生成器.
    private BeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator();
      
    //如配置文件中未指定数据源类型,使用该默认值
    privatestaticfinal Object DATASOURCE_TYPE_DEFAULT = "org.apache.tomcat.jdbc.pool.DataSource";
    //  private static final Object DATASOURCE_TYPE_DEFAULT = "com.zaxxer.hikari.HikariDataSource";
      
    // 存放DataSource配置的集合;
    private Map<String, Map<String, Object>> dataSourceMap = new HashMap<String, Map<String, Object>>();
   
   
    @Override
    publicvoid postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    System.out.println("MultipleDataSourceBeanDefinitionRegistryPostProcessor.postProcessBeanFactory()");
       //设置为主数据源;
       beanFactory.getBeanDefinition("dataSource").setPrimary(true);
      
       if(!dataSourceMap.isEmpty()){
           //不为空的时候.
           BeanDefinition bd = null;
            Map<String, Object> dsMap = null;
            MutablePropertyValues mpv = null;
            for (Entry<String, Map<String, Object>> entry : dataSourceMap.entrySet()) {
                 bd = beanFactory.getBeanDefinition(entry.getKey());
                 mpv = bd.getPropertyValues();
                 dsMap = entry.getValue();
                 mpv.addPropertyValue("driverClassName", dsMap.get("driverClassName"));
                 mpv.addPropertyValue("url", dsMap.get("url"));
                 mpv.addPropertyValue("username", dsMap.get("username"));
                 mpv.addPropertyValue("password", dsMap.get("password"));
            }
       }
    }
   
 
    @SuppressWarnings("unchecked")
    @Override
    publicvoid postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
    System.out.println("MultipleDataSourceBeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry()");
       try {
           if(!dataSourceMap.isEmpty()){
              //不为空的时候,进行注册bean.
              for(Entry<String,Map<String,Object>> entry:dataSourceMap.entrySet()){
                  Object type = entry.getValue().get("type");//获取数据源类型,没有设置为默认的数据源.
                  if(type == null){
                     type= DATASOURCE_TYPE_DEFAULT;
                  }
                  registerBean(registry, entry.getKey(),(Class<? extends DataSource>)Class.forName(type.toString()));
              }
           }
       } catch (ClassNotFoundException  e) {
           //异常捕捉.
           e.printStackTrace();
       }
    }
   
   
    /**
     * 注意重写的方法 setEnvironment 是在系统启动的时候被执行。
     * 这个方法主要是:加载多数据源配置
     * 从application.properties文件中进行加载;
     */
    @Override
    publicvoid setEnvironment(Environment environment) {
    System.out.println("MultipleDataSourceBeanDefinitionRegistryPostProcessor.setEnvironment()");
      
       /*
        * 获取application.properties配置的多数据源配置,添加到map中,之后在postProcessBeanDefinitionRegistry进行注册。
        */
       //获取到前缀是"custom.datasource." 的属性列表值.
       RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(environment,"custom.datasource.");
       //获取到所有数据源的名称.
       String dsPrefixs = propertyResolver.getProperty("names");
       String[] dsPrefixsArr = dsPrefixs.split(",");
       for(String dsPrefix:dsPrefixsArr){
           /*
            * 获取到子属性,对应一个map;
            * 也就是这个map的key就是
            *
            * type、driver-class-name等;
            *
            *
            */
           Map<String, Object> dsMap = propertyResolver.getSubProperties(dsPrefix + ".");
           //存放到一个map集合中,之后在注入进行使用.
           dataSourceMap.put(dsPrefix, dsMap);
       }
    }
 
   
   
    /**
     * 注册Bean到Spring
     *
     * @param registry
     * @param name
     * @param beanClass
     * @author SHANHY
     * @create 2016年1月22日
     */
    privatevoid registerBean(BeanDefinitionRegistry registry, String name, Class<?> beanClass) {
        AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
       
        //单例还是原型等等...作用域对象.
        ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
        abd.setScope(scopeMetadata.getScopeName());
        // 可以自动生成name
        String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, registry));
 
        AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
 
        BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
        BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);
    }
}
在代码中已经加入了,注释,大家可以好好看,在这里简单说明下。

以上代码的执行顺序是:
setEnvironment()-->postProcessBeanDefinitionRegistry() --> postProcessBeanFactory()
在setEnvironment()方法中主要是读取了application.properties的配置;

在postProcessBeanDefinitionRegistry()方法中主要注册为spring的bean对象;

在postProcessBeanFactory()方法中主要是注入从setEnvironment方法中读取的application.properties配置信息。
需要注意的是这里并没有读取其它相同的数据源公共配置,这里我们不做介绍,在下节介绍,主要是因为这节在实际中我们并不会这么使用,这里只是过渡下,方便下节进行讲解。

(6)编写测试类

我们编写一个简单的类进行测试下,到底我们的多数据源是否注入成功了。
com.kfit.controller.TestController:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
package com.kfit.controller;
 
import java.sql.ResultSet;
import java.sql.SQLException;
 
import javax.sql.DataSource;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
/**
 * 测试;
 *
 * @version v.0.1
 */
@RestController
publicclass TestController {
   
    //没有指定为主数据源.
    @Autowired
    private DataSource dataSource;
   
    @Autowired
    @Qualifier("ds1")
    private DataSource dataSource1;
   
    @Autowired
    @Qualifier("ds2")
    private DataSource dataSource2;
   
    private JdbcTemplate jdbcTemplate;
   
   
    @Autowired
    publicvoid setJdbcTemplate(JdbcTemplate jdbcTemplate) {
       System.out.println("TestController.setJdbcTemplate()");
       jdbcTemplate.setDataSource(dataSource1);//设置dataSource
       this.jdbcTemplate = jdbcTemplate;
    }
 
    @RequestMapping("/get")
    public String get(){
       //观察控制台的打印信息.
       System.out.println(dataSource);
       return"ok";
    }
   
    @RequestMapping("/get1")
    public String get1(){
       //观察控制台的打印信息.
       System.out.println(dataSource1);
       return"ok.1";
    }
   
    @RequestMapping("/get2")
    public String get2(){
       //观察控制台的打印信息.
       System.out.println(dataSource2);
       return"ok.2";
    }
   
    @RequestMapping("/get3")
    public String get3(){
       //观察控制台的打印信息.
       JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource1);
       System.out.println(jdbcTemplate.getDataSource());
       System.out.println(jdbcTemplate);
        
       /*
         * Demo1只在test1中存在,test并没有此数据库;
         * 需要自己自己进行复制,不然会报错:Table 'test1.demo1' doesn't exist
         */
       String sql = "select *from Demo1";
        jdbcTemplate.query(sql, new RowMapper<String>(){
 
            @Override
            public String mapRow(ResultSet rs, introwNum) throws SQLException {
                System.out.println(rs.getLong("id")+"---"+rs.getString("name"));
                return"";
            }
 
        });
      
       return"ok.3";
    }
   
   
    @RequestMapping("/get4")
    public String get4(){
       //观察控制台的打印信息.
       System.out.println(jdbcTemplate.getDataSource());
       System.out.println(jdbcTemplate);
        
       /*
         * Demo1只在test1中存在,test并没有此数据库;
         * 需要自己自己进行复制,不然会报错:Table 'test1.demo1' doesn't exist
         */
       String sql = "select *from Demo1";
        jdbcTemplate.query(sql, new RowMapper<String>(){
 
            @Override
            public String mapRow(ResultSet rs, introwNum) throws SQLException {
                System.out.println(rs.getLong("id")+"---"+rs.getString("name"));
                return"";
            }
 
        });
      
       return"ok.4";
    }
}
以上代码在实际开发中,是绝对不能这么编写的,这里只是为了方便进行测试。

(7)测试

Run As 运行app.java进行测试:
访问:

http://127.0.0.1:8080/get
http://127.0.0.1:8080/get1
http://127.0.0.1:8080/get2
http://127.0.0.1:8080/get3
http://127.0.0.1:8080/get4
查看控制台的打印信息。
然而我们在项目中不一定需要直接使用dataSource的,大家都习惯使用JDBC的jdbcTemplate、Mybatis的sqlSessionTemplate,再或者就是以Mybatis为例直接动态代理到Mapper接口上。

Spring Boot 多数据源自动切换的更多相关文章

  1. 四、Spring Boot 多数据源 自动切换

    实现案例场景: 某系统除了需要从自己的主要数据库上读取和管理数据外,还有一部分业务涉及到其他多个数据库,要求可以在任何方法上可以灵活指定具体要操作的数据库.为了在开发中以最简单的方法使用,本文基于注解 ...

  2. (43). Spring Boot动态数据源(多数据源自动切换)【从零开始学Spring Boot】

    在上一篇我们介绍了多数据源,但是我们会发现在实际中我们很少直接获取数据源对象进行操作,我们常用的是jdbcTemplate或者是jpa进行操作数据库.那么这一节我们将要介绍怎么进行多数据源动态切换.添 ...

  3. Spring Boot之实现自动配置

    GITHUB地址:https://github.com/zhangboqing/springboot-learning 一.Spring Boot自动配置原理 自动配置功能是由@SpringBootA ...

  4. Spring Boot多数据源配置(二)MongoDB

    在Spring Boot多数据源配置(一)durid.mysql.jpa 整合中已经讲过了Spring Boot如何配置mysql多数据源.本篇文章讲一下Spring Boot如何配置mongoDB多 ...

  5. Spring Boot 项目学习 (四) Spring Boot整合Swagger2自动生成API文档

    0 引言 在做服务端开发的时候,难免会涉及到API 接口文档的编写,可以经历过手写API 文档的过程,就会发现,一个自动生成API文档可以提高多少的效率. 以下列举几个手写API 文档的痛点: 文档需 ...

  6. 关于Spring Boot 多数据源的事务管理

    自己的一些理解:自从用了Spring Boot 以来,这近乎零配置和"约定大于配置"的设计范式用着确实爽,其实对零配置的理解是:应该说可以是零配置可以跑一个简单的项目,因为Spri ...

  7. 43. Spring Boot动态数据源(多数据源自动切换)【从零开始学Spring Boot】

    [视频&交流平台] àSpringBoot视频 http://study.163.com/course/introduction.htm?courseId=1004329008&utm ...

  8. 【Spring Boot】Spring Boot之使用AOP实现数据库多数据源自动切换

    一.添加maven坐标 <!-- aop --> <dependency> <groupId>org.springframework.boot</groupI ...

  9. Spring Boot 动态数据源(多数据源自动切换)

    本文实现案例场景: 某系统除了需要从自己的主要数据库上读取和管理数据外,还有一部分业务涉及到其他多个数据库,要求可以在任何方法上可以灵活指定具体要操作的数据库. 为了在开发中以最简单的方法使用,本文基 ...

随机推荐

  1. SQL 组内排序

    SELECT t_time, code, name, CL, row_number () OVER (partition BY t_time ORDER BY cl) AS 组内排名1, --T_ti ...

  2. spring jpa 创建时间和更新时间自动更新

    @Entity @Table(name="RS_SIGNUPUSER") public class RsSignUpUser { @Id @GenericGenerator(nam ...

  3. ActiveMQ集群整体认识

    出自:https://segmentfault.com/a/1190000014592517 前言 最终需要掌握 Replicated LevelDB Store部署方式,这种部署方式是基于ZooKe ...

  4. 阿里巴巴Java开发规约扫描插件-Alibaba Java Coding Guidelines 在idea上安装使用教程

    经过247天的持续研发,阿里巴巴于10月14日在杭州云栖大会上,正式发布众所期待的<阿里巴巴Java开发规约>扫描插件!该插件由阿里巴巴P3C项目组研发.P3C是世界知名的反潜机,专门对付 ...

  5. apply-register-acl 参数允许FreeSWITCH分机注册/拨打不验证密码

    今天调试 发现 注册的分机 的 `Auth-User` 居然是 `unknown` !!! 怎么回事? 仔细对比检查 发现, internal profile 指定了 `apply-register- ...

  6. 第五章 大数据平台与技术 第12讲 大数据处理平台Spark

    Spark支持多种的编程语言 对比scala和Java编程上节课的计数程序.相比之下,scala简洁明了. Hadoop的IO开销大导致了延迟高,也就是说任务和任务之间涉及到I/O操作.前一个任务完成 ...

  7. spring+springmvc+mybatis+redis实现缓存

    先搭建好redis环境 需要的jar如下: jdbc.driverClassName=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:330 ...

  8. Hibernate入门级实例

    一.开发环境 Win8 + jdk1.7 + MyEclipse + Tomcat5.0 + MySQL 说明:其实Hibernate是非常独立的框架,根本不需要MyEclipse,Eclipse,T ...

  9. Spring在代码中获取bean的几种方式(转)

    获取spring中bean的方式总结: 方法一:在初始化时保存ApplicationContext对象 ApplicationContext ac = new FileSystemXmlApplica ...

  10. code2039 骑马修栏杆

    欧拉通路: find_circuit(结点i){ 当结点i有邻居时 选择任意一个邻居j: 删除边(i,j)或者做访问标记: find_circuit(结点j): 输出或存储节点i: } 本题注意点的编 ...