Spring Boot Admin的使用笔记

Spring Boot Admin的使用笔记

当前Spring Boot Admin的最新版本为1.5.0。本文是相关的使用笔记。

集成方法

方法一

Spring Boot Admin服务端作为单独的组件,微服务组件使用客户端通信库向Spring Boot Admin服务端上报数据。

  1. Spring Boot Admin服务端的软件依赖。修改pom.xml,增加如下内容。
    <dependency>
        <groupId>de.codecentric</groupId>
        <artifactId>spring-boot-admin-server</artifactId>
        <version>1.5.0</version>
    </dependency>
    <dependency>
        <groupId>de.codecentric</groupId>
        <artifactId>spring-boot-admin-server-ui</artifactId>
        <version>1.5.0</version>
    </dependency>
    
  2. Spring Boot Admin服务端的启动类。
    @EnableAdminServer
    @SpringBootApplication
    public class Application {
        public static void main(String[] args) {
            SpringApplication.run(Application.class, args);
        }
    }
    
  3. Spring Boot Admin服务端的配置文件application.yml
    server:
        port: 11002
    
  4. Spring Boot Admin客户端的软件依赖。修改pom.xml,增加如下内容。
    <dependency>
        <groupId>de.codecentric</groupId>
        <artifactId>spring-boot-admin-starter-client</artifactId>
        <version>1.5.0</version>
    </dependency>
    
  5. Spring Boot Admin客户端的配置。修改application.yml,增加如下内容。
    spring:
        boot:
            admin:
                url: http://localhost:11002
    management:
        security: 
            enabled: false
    

方法二

Spring Boot Admin服务端作为单独的组件,使用Eureka来主动发现微服务组件,并获取数据。

  1. Spring Boot Admin服务端的软件依赖。修改pom.xml,增加如下内容。
    <dependency>
        <groupId>de.codecentric</groupId>
        <artifactId>spring-boot-admin-server</artifactId>
        <version>1.5.0</version>
    </dependency>
    <dependency>
        <groupId>de.codecentric</groupId>
        <artifactId>spring-boot-admin-server-ui</artifactId>
        <version>1.5.0</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!-- 向Eureka注册 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-eureka</artifactId>
    </dependency>
    
  2. Spring Boot Admin服务端的启动类。
    @EnableDiscoveryClient
    @EnableAdminServer
    @SpringBootApplication
    public class Application {
        public static void main(String[] args) {
            SpringApplication.run(Application.class, args);
        }
    }
    
  3. Spring Boot Admin服务端的配置文件application.yml
    server:
        port: 11002
    eureka:
        client:
            serviceUrl:
                defaultZone: http://localhost:8761/eureka/
    

方法三

Spring Boot Admin服务端与Eureka集成,使用Eureka来主动发现微服务组件,并获取数据。

  1. Spring Boot Admin服务端的软件依赖。修改pom.xml,增加如下内容。
    <dependency>
        <groupId>de.codecentric</groupId>
        <artifactId>spring-boot-admin-server</artifactId>
        <version>1.5.0</version>
    </dependency>
    <dependency>
        <groupId>de.codecentric</groupId>
        <artifactId>spring-boot-admin-server-ui</artifactId>
        <version>1.5.0</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-eureka-server</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    
  2. Spring Boot Admin服务端的启动类。
    @EnableEurekaServer
    @EnableAdminServer
    @SpringBootApplication
    public class Application {
        public static void main(String[] args) {
            SpringApplication.run(Application.class, args);
        }
    }
    
  3. Spring Boot Admin服务端的配置文件application.yml
    server:
        port: 11001
    eureka:
        client:
            serviceUrl:
                defaultZone: http://localhost:8761/eureka/
    spring:
        boot:
            admin:
                context-path: /admin
    

总结

方案一,需要为每个微服务组件增加客户端依赖,虽然Jar文件不大,不会占用太多空间,但毕竟多了一些配置,使用相对不那么方便。
方案二,基于Eureka提供的服务发现能力获取当前活跃的微服务实例清单,各类微服务上不需要增加软件依赖以及配置,相对方案一似乎要简单一些。
方案三,在方案二的基础上,Eureka和Spring Boot Admin Server合并为同一个组件,减少了组件的数量,同时没有引入新的依赖,应该也是一种比较好的方案。

高级特性

版本号

默认情况下,构建插件spring-boot-maven-plugin只有一个执行目标repackage,通常情况下这已经足够达成目标。为了在Spring Boot Admin的管理界面看到微服务组件的版本号,可以利用插件spring-boot-maven-plugin提供的另外一个目标build-info达成这一目的。如下是配置样例。

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <executions>
                <execution>
                    <goals>
                        <goal>build-info</goal>
                        <goal>repackage</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

JMS特性

修改pom.xml,增加如下依赖。

<dependency>
    <groupId>org.jolokia</groupId>
    <artifactId>jolokia-core</artifactId>
</dependency>

日志级别的控制

编辑logback-spring.xml,增加<jmxConfigurator/>,如下是完整的样例。

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <include resource="org/springframework/boot/logging/logback/base.xml"/>
  <jmxConfigurator/>
</configuration>

集群

Spring Boot Admin基于Hazelcast实现集群的能力,如下是启用的方法。

  1. 修改pom.xml,增加如下软件的依赖。
    <dependency>
        <groupId>com.hazelcast</groupId>
        <artifactId>hazelcast</artifactId>
    </dependency>
    
  2. 增加HazelcastConfig类型的Bean对象。如下是样例。
    @EnableDiscoveryClient
    @EnableAdminServer
    @SpringBootApplication
    public class Application {
        @Bean
        public Config hazelcastConfig() {
            return new Config().setProperty("hazelcast.jmx", "true")
                    .addMapConfig(new MapConfig("spring-boot-admin-application-store").setBackupCount(1)
                            .setEvictionPolicy(EvictionPolicy.NONE))
                    .addListConfig(new ListConfig("spring-boot-admin-event-store").setBackupCount(1).setMaxSize(1000));
        }
    
        public static void main(String[] args) {
            SpringApplication.run(Application.class, args);
        }
    }
    

故障通知

以邮件通知为例,介绍使用方法。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>
spring:
    mail:
        host: smtp.example.com
    boot:
        admin:
            notify:
                mail:
                    to: admin@example.com

集成熔断面板

<dependency>
    <groupId>de.codecentric</groupId>
    <artifactId>spring-boot-admin-server-ui-hystrix</artifactId>
    <version>1.5.0</version>
</dependency>
spring.boot.admin.routes.endpoints: env,metrics,trace,dump,jolokia,info,configprops,trace,logfile,refresh,flyway,liquibase,heapdump,loggers,auditevents,hystrix.stream
<dependency>
    <groupId>de.codecentric</groupId>
    <artifactId>spring-boot-admin-server-ui-turbine</artifactId>
    <version>1.5.0</version>
</dependency>
spring:
    boot:
        admin:
            turbine:
                clusters: default
                location: turbine

集成登录控制

<dependency>
    <groupId>de.codecentric</groupId>
    <artifactId>spring-boot-admin-server-ui-login</artifactId>
    <version>1.5.0</version>
</dependency>

参考资料

发表在 Spring Boot | 标签为 | 留下评论

Zookeeper的四字咒语

Zookeeper的四字咒语

介绍

conf

3.3.0版本引入。
输出当前的配置信息。

cons

3.3.0版本引入。
输出当前实例上的链接/会话的详细信息。

crst

3.3.0版本引入。
用于重置链接/会话上的统计数据。

dump

通过访问Zookeeper集群的Leader实例,列出会话和暂态节点的详细信息。

envi

输出实例启动时的环境变量的信息。

ruok

检测当前实例是否工作在无错的状态。假如工作正常,本条命令的返回为imok,否则无返回信息。
本命令的返回信息仅具有参考意义,原因是imok仅表示实例已启动且绑定到指定端口上运行,但该实例是否正确的加入集群,并没有检查,因此使用时需要注意。

srst

用于重置统计数据。

srvr

3.3.0版本引入。
输出当前实例的全部细节信息。

stat

列出当前实例重要的信息,以及与当前实例建立链接的客户端的清单。

wchs

3.3.0版本引入。
输出当前实例上的watch的相关信息。

wchc

3.3.0版本引入。
以当前实例的会话为维度,输出相关watch的详细数据。当数据量大时,本操作对当前实例的性能存在影响,因此需要谨慎使用。

wchp

3.3.0版本引入。
以当前实例上保存的path为维度,输出相关watch的详细数据。当数据量大时,本操作对当前实例的性能存在影响,因此需要谨慎使用。

mntr

3.4.0版本引入。
输出集群运行状态的相关指标,有助于外部监控系统获取Zookeeper集群运行情况的数据。

使用样例

参考资料

发表在 Zookeeper, 大数据 | 标签为 | 留下评论

Spring Boot集成MyBatis访问数据库

基于spring boot开发的微服务应用,与MyBatis如何集成?

集成方法

可行的方法有:

  1. 基于XML或者Java Config,构建必需的对象,配置MyBatis。
  2. 使用MyBatis官方提供的组件,实现MyBatis的集成。

方法一

建议参考如下文章,完成集成的验证。

由于不是本文的重点,因此不附上样例。

方法二

有如下步骤:

  • 修改pom.xml,增加软件依赖
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>1.2.0</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.21</version>
    </dependency>
    
  • 修改application.yml,增加数据源的定义
    spring:
        datasource:
            url: jdbc:mysql://localhost:3306/test
            username: root
            password: 123456
            driver-class-name: com.mysql.jdbc.Driver
    
  • 修改application.yml,增加MyBatis的配置
    mybatis:
        type-aliases-package: com.example.domain.model
        type-handlers-package: com.example.typehandler
        configuration:
            map-underscore-to-camel-case: true
            default-fetch-size: 100
            default-statement-timeout: 30
    

日志的配置

通过观察日志,可有效的分析MyBatis生成的SQL,检查SQL配置的正确性。
修改application.yml,增加如下配置

logging:
    level:
        net: 
            jackieathome:
                db:
                    mapper: DEBUG

其中net.jackieathome.db.mapper下定义了访问数据库的mapper接口。

输出的日志样例如下

2017-04-16 11:32:23.266 DEBUG 27801 --- [io-11002-exec-1] n.j.db.mapper.UserMapper.createUser      : ==>  Preparing: insert into `user`(id, name, password) values(?, ?, ?) 
2017-04-16 11:32:23.293 DEBUG 27801 --- [io-11002-exec-1] n.j.db.mapper.UserMapper.createUser      : ==> Parameters: id1492313542(String), null, null
2017-04-16 11:32:23.366 DEBUG 27801 --- [io-11002-exec-1] n.j.db.mapper.UserMapper.createUser      : <==    Updates: 1
2017-04-16 11:32:23.372 DEBUG 27801 --- [io-11002-exec-1] n.j.db.mapper.UserMapper.findUserById    : ==>  Preparing: select * from `user` where id = ? 
2017-04-16 11:32:23.373 DEBUG 27801 --- [io-11002-exec-1] n.j.db.mapper.UserMapper.findUserById    : ==> Parameters: id1492313542(String)
2017-04-16 11:32:23.417 DEBUG 27801 --- [io-11002-exec-1] n.j.db.mapper.UserMapper.findUserById    : <==      Total: 1

事务的使用

依据MyBatis的官方文档,允许用户将事务交给Spring来管理,使用编程和注解来控制事务。这里以注解方式来举例说明使用方法,样例代码如下:

  1. mapper的定义,如下
    package net.jackieathome.db.mapper;
    
    import java.util.List;
    
    import org.apache.ibatis.annotations.Mapper;
    import org.apache.ibatis.annotations.Param;
    
    import net.jackieathome.bean.User;
    
    @Mapper
    public interface UserMapper {
        // 创建用户
        void createUser(User user);
        // 查找用户
        User findUserById(@Param("id") String id);
    }
    
  2. 数据库访问的中间层代码,对上述mapper进行了封装。

    使用@Transactional标记该类,表明该类的公有方法全部都启用了事务的支持。关于@Transactional的使用,可以参考相关的官方文档。

    package net.jackieathome.dao;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import org.springframework.transaction.annotation.Transactional;
    
    import net.jackieathome.bean.User;
    import net.jackieathome.db.mapper.UserMapper;
    
    @Component
    @Transactional
    public class UserDao {
        @Autowired
        private UserMapper userMapper;
    
        /**
         * 重复插入相同的用户数据,确认事务是否生效
         */
        public List<String> createBatch() {
    
            long time = System.currentTimeMillis() / 1000;
            User user = null;
            List<String> ids = new ArrayList<>();
    
            String id = "id" + time;
            String name = "name" + time;
            String password = "password" + time;
    
            user = new User();
            user.setId(id);
            user.setName(name);
            user.setPassword(password);
            userMapper.createUser(user);
            ids.add(id);
    
            user = new User();
            user.setId(id);
            user.setName(name);
            user.setPassword(password);
            userMapper.createUser(user);
            ids.add(id);
    
            return ids;
        }
    }
    
  3. 业务层实现
    package net.jackieathome.controller;
    
    import java.util.List;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RestController;
    
    import net.jackieathome.bean.User;
    import net.jackieathome.dao.UserDao;
    import net.jackieathome.db.mapper.UserMapper;
    
    @RestController
    public class UserController {
        @Autowired
        private UserMapper userMapper;   
        @Autowired
        private UserDao userDao;
        
        @RequestMapping(method = RequestMethod.GET, value = "/user/create/batch")
        public List<User> createBatch() {
            try
            {
                userDao.createBatch();
            }
            catch (Exception e)
            {
                
            }
            return userMapper.loadAllUsers();
        }
    }
    

从实际测试看,上述事务的实现有效,可保证当数据出现主键冲突时,事务中的插入操作可全部撤销,不会出现部分数据插入成功、部分失败的现象。
注意事项:

由于注解事务的实现依赖Spring AOP,因此只有当注入行为存在时,注解事务的控制才会生效。

  1. 假如在上述UserController类中定义createBatch方法,并且使用注解@Transactional标记,经验证可确认此时注解事务是无效的。
  2. 假如在上述UserDao中定义了多个公有方法,存在相互调用的行为,基于相同的原因,这些方法相互调用时注解事务并不会生效。如果确实需要保证事务可用,可以考虑调整类的设计或者使用编程的方式来控制事务。

参考资料

Spring Boot与MyBatis

Spring与MyBatis

发表在 Spring Boot | 标签为 , | Spring Boot集成MyBatis访问数据库已关闭评论

Spring Boot配置数据库链接池

配置方法

基于当前的1.5.2.RELEASE的Spring Boot。
依照官方文档,如果增加了如下依赖的配置,或者类路径中存在spring-boot-starter-jdbc的jar,那么已默认启用了数据库链接池。

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

Spring Boot选择数据库链接池实现的判断逻辑:

  1. 检查Tomcat的数据库链接池实现是否可用,如可用,则启用。使用spring.datasource.tomcat.*可以控制链接池的行为。
  2. 检查HikariCP是否可用,如可用,则启用。使用spring.datasource.hikari.*可以控制链接池的行为。
  3. 检查Commons DBCP是否可用,如可用,则启用;但Spring Boot不建议在生产环境使用该链接池的实现。
  4. 检查Commons DBCP2是否可用,如可用,则启用。使用spring.datasource.dbcp2.*可以控制链接池的行为。

使用tomcat-jdbc时,可在application.yml增加配置项spring.datasource.tomcat.*来控制链接池的行为。比如如下配置。

spring:
    datasource:
        url: jdbc:mysql://localhost:3306/jackieathome?useSSL=false
        username: root
        password: mypassword
        # 6.x版本的MySQL JDBC驱动类为com.mysql.cj.jdbc.Driver
        # 5.X版本的MySQL JDBC驱动类为com.mysql.jdbc.Driver
        driver-class-name: com.mysql.cj.jdbc.Driver
        tomcat:
            max-wait: 10000
            max-active: 30
            test-on-borrow: true
            # 传递MySQL JDBC特有的参数
            db-properties:
                logger: net.jackieathome.db.customized.MySQLLogger
                gatherPerfMetrics: 'true'
                profileSQL: 'true'
                reportMetricsIntervalMillis: '60000'
                logSlowQueries: 'true'
                explainSlowQueries: 'true'
logging:
    level:
        # 关闭其它软件的日志,减少干扰
        org: ERROR
        net: ERROR
        com: ERROR
        # 开启MySQL JDBC驱动的日志
        MySQL: DEBUG

上述spring.datasource.tomcat.*代表的配置项,可参考tomcat-jdbc的官方文档Apache Tomcat 8.5 – The Tomcat JDBC Connection Pool或者Apache Tomcat 8.0 – The Tomcat JDBC Connection Pool

依据tomcat-jdbc的文档,如需要向数据库的JDBC驱动传入控制参数,可以使用db-properties字段。需要注意的是,当使用MySQL驱动时,控制参数的值需要强制转换为字符串,否则创建数据库链接时会报错。配置方法如上述样例中的 reportMetricsIntervalMillis: '60000'logSlowQueries: 'true'

依照MySQL JDBC驱动文档,可以配置一个日志记录器,用于记录其工作时的输出,如下是实现样例。

package net.jackieathome.db.customized;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MySQLLogger implements com.mysql.cj.api.log.Log {
    private static Logger LOG;

    public MySQLLogger(String name) {
        LOG = LoggerFactory.getLogger(name);
    }

    @Override
    public boolean isDebugEnabled() {

        return LOG.isDebugEnabled();
    }

    @Override
    public boolean isErrorEnabled() {
        return LOG.isErrorEnabled();
    }

    @Override
    public boolean isFatalEnabled() {
        return LOG.isErrorEnabled();
    }

    @Override
    public boolean isInfoEnabled() {
        return LOG.isInfoEnabled();
    }

    @Override
    public boolean isTraceEnabled() {
        return LOG.isTraceEnabled();
    }

    @Override
    public boolean isWarnEnabled() {
        return LOG.isWarnEnabled();
    }

    @Override
    public void logDebug(Object msg) {
        LOG.debug("{}", msg);
    }

    @Override
    public void logDebug(Object msg, Throwable thrown) {
        LOG.debug("{}", msg, thrown);
    }

    @Override
    public void logError(Object msg) {
        LOG.error("{}", msg);
    }

    @Override
    public void logError(Object msg, Throwable thrown) {
        LOG.error("{}", msg, thrown);
    }

    @Override
    public void logFatal(Object msg) {
        LOG.error("{}", msg);
    }

    @Override
    public void logFatal(Object msg, Throwable thrown) {
        LOG.error("{}", msg, thrown);
    }

    @Override
    public void logInfo(Object msg) {
        LOG.info("{}", msg);
    }

    @Override
    public void logInfo(Object msg, Throwable thrown) {
        LOG.info("{}", msg, thrown);
    }

    @Override
    public void logTrace(Object msg) {
        LOG.trace("{}", msg);
    }

    @Override
    public void logTrace(Object msg, Throwable thrown) {
        LOG.trace("{}", msg, thrown);
    }

    @Override
    public void logWarn(Object msg) {
        LOG.warn("{}", msg);
    }

    @Override
    public void logWarn(Object msg, Throwable thrown) {
        LOG.warn("{}", msg, thrown);
    }
}

同时修改application.yml,增加相应的日志配置,如下。

logging:
    level:
        # 开启MySQL JDBC驱动的日志
        MySQL: DEBUG

如下是MySQL JDBC驱动输出的样例日志。

2017-04-16 00:51:32.626 QUERY created: Sun Apr 16 00:51:32 CST 2017 duration: 0 connection: 93 statement: 1 resultset: 1 message: /* mysql-connector-java-6.0.6 ( Revision: 3dab84f4d9bede3cdd14d57b99e9e98a02a5b97d ) */SELECT  @@session.auto_increment_increment AS auto_increment_increment, @@character_set_client AS character_set_client, @@character_set_connection AS character_set_connection, @@character_set_results AS character_set_results, @@character_set_server AS character_set_server, @@init_connect AS init_connect, @@interactive_timeout AS interactive_timeout, @@license AS license, @@lower_case_table_names AS lower_case_table_names, @@max_allowed_packet AS max_allowed_packet, @@net_buffer_length AS net_buffer_length, @@net_write_timeout AS net_write_timeout, @@query_cache_size AS query_cache_size, @@query_cache_type AS query_cache_type, @@sql_mode AS sql_mode, @@system_time_zone AS system_time_zone, @@time_zone AS time_zone, @@tx_isolation AS tx_isolation, @@wait_timeout AS wait_timeout
2017-04-16 00:51:32.629 FETCH created: Sun Apr 16 00:51:32 CST 2017 duration: 36 connection: 93 statement: 1 resultset: 1
2017-04-16 00:51:32.639 QUERY created: Sun Apr 16 00:51:32 CST 2017 duration: 1 connection: 93 statement: 999 resultset: 0 message: SET NAMES latin1
2017-04-16 00:51:32.640 FETCH created: Sun Apr 16 00:51:32 CST 2017 duration: 0 connection: 93 statement: 999 resultset: 0
2017-04-16 00:51:32.642 QUERY created: Sun Apr 16 00:51:32 CST 2017 duration: 2 connection: 93 statement: 999 resultset: 0 message: SET character_set_results = NULL
2017-04-16 00:51:32.643 FETCH created: Sun Apr 16 00:51:32 CST 2017 duration: 0 connection: 93 statement: 999 resultset: 0
2017-04-16 00:51:32.645 QUERY created: Sun Apr 16 00:51:32 CST 2017 duration: 0 connection: 93 statement: 999 resultset: 0 message: SET autocommit=1
2017-04-16 00:51:32.646 FETCH created: Sun Apr 16 00:51:32 CST 2017 duration: 0 connection: 93 statement: 999 resultset: 0
2017-04-16 00:51:32.667 QUERY created: Sun Apr 16 00:51:32 CST 2017 duration: 1 connection: 94 statement: 2 resultset: 2 message: /* mysql-connector-java-6.0.6 ( Revision: 3dab84f4d9bede3cdd14d57b99e9e98a02a5b97d ) */SELECT  @@session.auto_increment_increment AS auto_increment_increment, @@character_set_client AS character_set_client, @@character_set_connection AS character_set_connection, @@character_set_results AS character_set_results, @@character_set_server AS character_set_server, @@init_connect AS init_connect, @@interactive_timeout AS interactive_timeout, @@license AS license, @@lower_case_table_names AS lower_case_table_names, @@max_allowed_packet AS max_allowed_packet, @@net_buffer_length AS net_buffer_length, @@net_write_timeout AS net_write_timeout, @@query_cache_size AS query_cache_size, @@query_cache_type AS query_cache_type, @@sql_mode AS sql_mode, @@system_time_zone AS system_time_zone, @@time_zone AS time_zone, @@tx_isolation AS tx_isolation, @@wait_timeout AS wait_timeout

参考资料

Spring的链接池配置

Spring Boot的链接池的配置

Tomcat的链接池的配置

MySQL JDBC

发表在 Spring Boot | 标签为 , | Spring Boot配置数据库链接池已关闭评论