【求教】老菜鸟遇到新问题,双bug欢迎有緣人答疑

文章目录

  • 一,序
  • 二,需求
  • 三,代码实现
    • 1. 代码结构
    • 2. 完整代码备份
  • 四,bug1 详情
    • 1. 运行准备
      • 1. )将 application.yml 文件active设置为test
      • 2.)修改jdbc-mysql.properties 数据库参数设为实际值
      • 3.)注释 RefreshLogPoolManager 40-45行代码
    • 2. bug现象
      • 1.)数据表记录超过100后,执行清理操作时,后台报错
      • 2.)mysql 执行`SHOW PROCESSLIST` 发现数据库连接处于 Waiting for table metadata lock 状态
    • 3. 疑问
  • 五,bug2 详情
    • 1. 运行准备
    • 2. bug现象
    • 3. 疑问

一,序

俗话说:但行好事,莫问前程,心之所向,无问西东

编程亦然,coding多了,就会遇到各种各样奇怪的问题,真是让人欢喜让人忧啊!

这不,小C最近实现了一个使用mysql数据库来保存日志的功能,不幸的是,遇到两个难解的问题,现拿出来,希望各位见多识广的大佬能帮忙分析,小可不胜感激!

二,需求

  1. 系统需要保存一定周期内的日志
  2. 系统自动清理超过设定周期的过期日志文件
  3. 日志数据表与业务系统是同一数据库
  4. 配置文件可能采用参数外部文件注入(spring.config.location指定)、启动时指定环境(spring.profiles.active指定)或docker-compose环境变量注入(environment)

三,代码实现

1. 代码结构

【求教】老菜鸟遇到新问题,双bug欢迎有緣人答疑_第1张图片

2. 完整代码备份

如何使用下面的备份文件恢复成原始的如上图的项目代码,请移步查阅:神奇代码恢复工具

因工具不能备份非文本文件,请大家在src\main\resources下新建/data/pic/目录并放入不少于4张jpg图片。

//goto docker\docker-compose.yml
version: '3'
networks:
  default:
    name: devops
    driver: bridge
    ipam:
      config:
      - subnet: 172.88.88.0/24

services:
  hello:
    image: registry.cn-shanghai.aliyuncs.com/00fly/springboot-log:1.0.0
    container_name: hello
    deploy:
      resources:
        limits:
          cpus: '1'
          memory: 300M
        reservations:
          memory: 200M
    ports:
    - 8080:8081
    environment:
      JAVA_OPTS: -server -Xms200m -Xmx200m -Djava.security.egd=file:/dev/./urandom
      SPRING_DATASOURCE_USERNAME: test
      SPRING_DATASOURCE_PASSWORD: test123
      SPRING_DATASOURCE_DRIVERCLASSNAME: com.mysql.cj.jdbc.Driver
      SPRING_DATASOURCE_URL: jdbc:mysql://172.88.88.11:3306/test?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&autoReconnect=true
    restart: on-failure
    logging:
      driver: json-file
      options:
        max-size: 5m
        max-file: '1'
    entrypoint: 'sh wait-for.sh 172.88.88.11:3306 -- java -jar /app.jar'

  hello-simple:
    image: registry.cn-shanghai.aliyuncs.com/00fly/springboot-log:1.0.0
    container_name: hello-simple
    deploy:
      resources:
        limits:
          cpus: '1'
          memory: 300M
        reservations:
          memory: 200M
    environment:
      JAVA_OPTS: -server -Xms200m -Xmx200m -Djava.security.egd=file:/dev/./urandom
      SPRING_DATASOURCE_USERNAME: test
      SPRING_DATASOURCE_PASSWORD: test123
      SPRING_DATASOURCE_DRIVERCLASSNAME: com.mysql.cj.jdbc.Driver
      SPRING_DATASOURCE_URL: jdbc:mysql://172.88.88.11:3306/test?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&autoReconnect=true
    restart: on-failure
    logging:
      driver: json-file
      options:
        max-size: 5m
        max-file: '1'
    entrypoint: 'sh wait-for.sh 172.88.88.11:3306 -- java -jar /app.jar --noweb'

  mysql8:
    image: mysql:8.0
    container_name: mysql-8
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 500M
        reservations:
          memory: 300M
    volumes:
      - ./mysql8:/var/lib/mysql/
    ports:
    - 3307:3306
    environment:
      TZ: Asia/Shanghai
      MYSQL_ROOT_PASSWORD: root123
      MYSQL_USER: test
      MYSQL_PASSWORD: test123
      MYSQL_DATABASE: test
    command:
      --default-authentication-plugin=mysql_native_password
      --character-set-server=utf8mb4
      --collation-server=utf8mb4_general_ci
      --explicit_defaults_for_timestamp=true
      --lower_case_table_names=1
      --sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
    restart: on-failure
    logging:
      driver: 'json-file'
      options:
        max-size: '5m'
        max-file: '1'
    networks:
      default:
        ipv4_address: 172.88.88.11
//goto docker\restart-simple.sh
#!/bin/bash
docker-compose down && docker-compose up -d hello-simple -d mysql8 && docker stats
//goto docker\restart-web.sh
#!/bin/bash
docker-compose down && docker-compose up -d hello -d mysql8 && docker stats
//goto docker\restart.sh
#!/bin/bash
docker-compose down && docker system prune -f && docker-compose up -d && docker stats
//goto docker\stop.sh
#!/bin/bash
docker-compose down
//goto docker\wait-for.sh
#!/bin/sh

TIMEOUT=15
QUIET=0

echoerr() {
  if [ "$QUIET" -ne 1 ]; then printf "%s\n" "$*" 1>&2; fi
}

usage() {
  exitcode="$1"
  cat << USAGE >&2
Usage:
  $cmdname host:port [-t timeout] [-- command args]
  -q | --quiet                        Do not output any status messages
  -t TIMEOUT | --timeout=timeout      Timeout in seconds, zero for no timeout
  -- COMMAND ARGS                     Execute command with args after the test finishes
USAGE
  exit "$exitcode"
}

wait_for() {
  for i in `seq $TIMEOUT` ; do
    nc -z "$HOST" "$PORT" > /dev/null 2>&1

    result=$?
    if [ $result -eq 0 ] ; then
      if [ $# -gt 0 ] ; then
        exec "$@"
      fi
      exit 0
    fi
    sleep 1
  done
  echo "Operation timed out" >&2
  exit 1
}

while [ $# -gt 0 ]
do
  case "$1" in
    *:* )
    HOST=$(printf "%s\n" "$1"| cut -d : -f 1)
    PORT=$(printf "%s\n" "$1"| cut -d : -f 2)
    shift 1
    ;;
    -q | --quiet)
    QUIET=1
    shift 1
    ;;
    -t)
    TIMEOUT="$2"
    if [ "$TIMEOUT" = "" ]; then break; fi
    shift 2
    ;;
    --timeout=*)
    TIMEOUT="${1#*=}"
    shift 1
    ;;
    --)
    shift
    break
    ;;
    --help)
    usage 0
    ;;
    *)
    echoerr "Unknown argument: $1"
    usage 1
    ;;
  esac
done

if [ "$HOST" = "" -o "$PORT" = "" ]; then
  echoerr "Error: you need to provide a host and port to test."
  usage 2
fi

wait_for "$@"

//goto Dockerfile
#基础镜像
FROM openjdk:8-jre-alpine

RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone

COPY docker/wait-for.sh /
RUN chmod +x /wait-for.sh

#拷贝发布包
COPY target/*.jar  /app.jar

EXPOSE 8080

CMD ["--server.port=8080"]

#启动脚本
ENTRYPOINT ["java","-Xmx128m","-Xms128m","-jar","/app.jar"]
//goto pom.xml


	4.0.0
	com.fly
	springboot-log
	1.0.0
	jar

	
		org.springframework.boot
		spring-boot-starter-parent
		2.2.4.RELEASE
		
	

	
		UTF-8
		yyyyMMdd-HH
		registry.cn-shanghai.aliyuncs.com
		1.8
		true
	

	
		
		
			org.springframework.boot
			spring-boot-starter-web
			
				
					org.springframework.boot
					spring-boot-starter-tomcat
				
				
					org.springframework.boot
					spring-boot-starter-logging
				
			
		
		
			org.springframework.boot
			spring-boot-starter-log4j2
		
		
			org.springframework.boot
			spring-boot-starter-jdbc
			
				
					org.apache.tomcat
					tomcat-jdbc
				
			
		
		
			com.alibaba
			druid-spring-boot-starter
			1.2.16
		
		
			com.h2database
			h2
			runtime
		
		
			mysql
			mysql-connector-java
			runtime
		
		
			com.github.xiaoymin
			knife4j-spring-boot-starter
			2.0.5
		
		
			org.aspectj
			aspectjweaver
		

		
		
			com.lmax
			disruptor
			3.4.2
		

		
		
			org.springframework.boot
			spring-boot-starter-jetty
		

		
			commons-io
			commons-io
			2.5
		
		
			org.apache.commons
			commons-lang3
		
		
			org.projectlombok
			lombok
			provided
		
		

		
		
			org.springframework.boot
			spring-boot-starter-test
			test
		
	

	
	
		
			public
			aliyun nexus
			https://maven.aliyun.com/repository/public/
			
				true
			
		
	
	
		
			public
			aliyun nexus
			https://maven.aliyun.com/repository/public/
			
				true
			
			
				false
			
		
	

	
		${project.artifactId}-${project.version}
		
			
				org.springframework.boot
				spring-boot-maven-plugin
			

			
			
				io.fabric8
				docker-maven-plugin
				0.41.0
				
					
						package
						
							build
							push
							remove
						
					
				
				
					
					

					
					${docker.hub}
					
						
							
							
								${docker.hub}/00fly/${project.artifactId}:${project.version}-UTC-${maven.build.timestamp}
							
							
								${project.basedir}
							
						
						
							
								${docker.hub}/00fly/${project.artifactId}:${project.version}
							
								${project.basedir}
							
						
					
				
			
		
		
			
				src/main/java
				
					**/*.java</exclude>
				</excludes>
			</resource>
			<resource>
				<directory>src/main/resources</directory>
				<includes>
					<include>**/**
				
				false
			
		
	

//goto src\main\java\com\fly\core\aop\RunTimeAspect.java
package com.fly.core.aop;

import java.lang.reflect.Method;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

import lombok.extern.slf4j.Slf4j;

/**
 * 
 * aop 打印运行时间
 * 
 * @author 00fly
 * @version [版本号, 2018年12月2日]
 * @see [相关类/方法]
 * @since [产品/模块版本]
 */
@Slf4j
@Aspect
@Component
public class RunTimeAspect
{
    /**
     * 构造方法
     */
    public RunTimeAspect()
    {
        super();
    }
    
    /**
     * 
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("within(com.fly.hello.web..*||com.fly.hello.service..*)")
    public Object around(ProceedingJoinPoint joinPoint)
        throws Throwable
    {
        String className = joinPoint.getTarget().getClass().getSimpleName();
        MethodSignature signature = (MethodSignature)joinPoint.getSignature();
        Method method = signature.getMethod();
        String methodName = new StringBuffer(className).append('.').append(method.getName()).toString();
        final StopWatch clock = new StopWatch();
        clock.start(methodName);
        Object object = joinPoint.proceed();
        clock.stop();
        log.info("running {} ms, method = {}", clock.getTotalTimeMillis(), clock.getLastTaskName());
        return object;
    }
}
//goto src\main\java\com\fly\core\config\AsyncThreadPoolConfig.java
package com.fly.core.config;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import lombok.extern.slf4j.Slf4j;


/**
 * 
 * 异步线程池配置
 * 
 * @author  00fly
 * @version  [版本号, 2023年10月22日]
 * @see  [相关类/方法]
 * @since  [产品/模块版本]
 */
@Slf4j
@Configuration
@EnableAsync
public class AsyncThreadPoolConfig implements AsyncConfigurer
{
    @Bean
    ThreadPoolTaskExecutor taskExecutor()
    {
        int processors = Runtime.getRuntime().availableProcessors();
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(Math.max(processors, 5));
        executor.setMaxPoolSize(Math.max(processors, 5) * 2);
        executor.setQueueCapacity(10000);
        executor.setKeepAliveSeconds(60);
        executor.setThreadNamePrefix("asyncTask-");
        
        // ThreadPoolExecutor类有几个内部实现类来处理这类情况:
        // AbortPolicy 丢弃任务,抛运行时异常
        // CallerRunsPolicy 执行任务
        // DiscardPolicy 忽视,什么都不会发生
        // DiscardOldestPolicy 从队列中踢出最先进入队列(最后一个执行)的任务
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
        return executor;
    }
    
    @Override
    public Executor getAsyncExecutor()
    {
        return taskExecutor();
    }
    
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler()
    {
        return (ex, method, params) -> {
            log.info("Exception message - {}", ex.getMessage());
            log.info("Method name - {}", method.getName());
            for (Object param : params)
            {
                log.info("Parameter value - {}", param);
            }
        };
    }
}
//goto src\main\java\com\fly\core\config\DataSourceConfig.java
package com.fly.core.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.PropertySource;

@Configuration
@Profile("dev")
@PropertySource("classpath:jdbc-h2.properties")
class H2Config
{
}

@Configuration
@Profile({"test", "prod"})
@PropertySource("classpath:jdbc-mysql.properties")
class MysqlConfig
{
}
//goto src\main\java\com\fly\core\config\Knife4jConfig.java
package com.fly.core.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;

import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

/**
 * knife4j
 *
 * @author jack
 */
@Configuration
@EnableKnife4j
@EnableSwagger2
@ConditionalOnWebApplication
public class Knife4jConfig
{
    @Value("${knife4j.enable:false}")
    private boolean enable;
    
    @Bean
    Docket defaultApi2()
    {
        return new Docket(DocumentationType.SWAGGER_2).enable(enable)
            .apiInfo(apiInfo())
            .groupName("非异步接口组")
            .select()
            .apis(RequestHandlerSelectors.basePackage("com.fly.hello.web"))
            .paths(PathSelectors.regex("/(demo|knife|show)/.*")) // 针对RequestMapping
            // .paths(PathSelectors.regex("(?!.*async).*")) //url中不包含async
            .build();
    }
    
    private ApiInfo apiInfo()
    {
        return new ApiInfoBuilder().title("数据接口API").description("接口文档").termsOfServiceUrl("http://00fly.online/").version("1.0.0").build();
    }
    
}
//goto src\main\java\com\fly\core\config\ScheduleThreadPoolConfig.java
package com.fly.core.config;

import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;

/**
 * 
 * Schedule线程池配置
 * 
 * @author  00fly
 * @version  [版本号, 2023年10月22日]
 * @see  [相关类/方法]
 * @since  [产品/模块版本]
 */
@Configuration
public class ScheduleThreadPoolConfig implements SchedulingConfigurer
{
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar)
    {
        ScheduledExecutorService service= new ScheduledThreadPoolExecutor(8, new CustomizableThreadFactory("schedule-pool-"));
        taskRegistrar.setScheduler(service);
    }
}
//goto src\main\java\com\fly\core\config\WebMvcConfig.java
package com.fly.core.config;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import lombok.extern.slf4j.Slf4j;

/**
 * 
 * mvc配置
 * 
 * @author 00fly
 * @version [版本号, 2021年4月23日]
 * @see [相关类/方法]
 * @since [产品/模块版本]
 */
@Slf4j
@Configuration
@ConditionalOnWebApplication
public class WebMvcConfig implements WebMvcConfigurer
{
    @Override
    public void configureMessageConverters(final List<HttpMessageConverter<?>> converters)
    {
        converters.add(this.stringHttpMessageConverter());
        converters.add(this.mappingJackson2HttpMessageConverter());
    }
    
    @Override
    public void configureContentNegotiation(final ContentNegotiationConfigurer configurer)
    {
        // 是否通过请求Url的扩展名来决定mediatype
        // TODO:放开,状态码406,待解决
        // configurer.favorPathExtension(false).favorParameter(false).ignoreUnknownPathExtensions(false).defaultContentType(MediaType.APPLICATION_JSON);
        final Map<String, MediaType> mediaTypes = new ConcurrentHashMap<>(3);
        mediaTypes.put("atom", MediaType.APPLICATION_ATOM_XML);
        mediaTypes.put("html", MediaType.TEXT_HTML);
        mediaTypes.put("json", MediaType.APPLICATION_JSON);
        configurer.mediaTypes(mediaTypes);
    }
    
    @Bean
    public StringHttpMessageConverter stringHttpMessageConverter()
    {
        return new StringHttpMessageConverter();
    }
    
    @Bean
    public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter()
    {
        final MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
        final List<MediaType> list = new ArrayList<>();
        list.add(MediaType.APPLICATION_JSON);
        list.add(MediaType.APPLICATION_XML);
        list.add(MediaType.TEXT_PLAIN);
        list.add(MediaType.TEXT_HTML);
        list.add(MediaType.TEXT_XML);
        messageConverter.setSupportedMediaTypes(list);
        return messageConverter;
    }
    
    /**
     * 等价于mvc中
* 等价于mvc中 * * @param registry */
@Override public void addViewControllers(final ViewControllerRegistry registry) { registry.addViewController("/").setViewName("redirect:doc.html"); registry.addViewController("/index").setViewName("index.html"); registry.addViewController("/in").setViewName("auto.html"); } /** * 加载静态资源文件或文件映射 */ @Override public void addResourceHandlers(final ResourceHandlerRegistry registry) { try { registry.addResourceHandler("/upload/**").addResourceLocations("file:" + new File("./upload/").getCanonicalPath() + "/"); } catch (IOException e) { log.error(e.getMessage(), e); } } } //goto src\main\java\com\fly\core\JsonResult.java package com.fly.core; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; /** * * 结果对象 * * @author 00fly * @version [版本号, 2021年5月2日] * @see [相关类/方法] * @since [产品/模块版本] */ @Data @ApiModel(description = "Json格式消息体") public class JsonResult<T> { @ApiModelProperty(value = "数据对象") private T data; @ApiModelProperty(value = "是否成功", required = true, example = "true") private boolean success; @ApiModelProperty(value = "错误码") private String errorCode; @ApiModelProperty(value = "提示信息") private String message; public JsonResult() { super(); } public static <T> JsonResult<T> success(T data) { JsonResult<T> r = new JsonResult<>(); r.setData(data); r.setSuccess(true); return r; } public static JsonResult<?> success() { JsonResult<Object> r = new JsonResult<>(); r.setSuccess(true); return r; } public static JsonResult<Object> error(String code, String msg) { JsonResult<Object> r = new JsonResult<>(); r.setSuccess(false); r.setErrorCode(code); r.setMessage(msg); return r; } public static JsonResult<Object> error(String msg) { return error("500", msg); } } //goto src\main\java\com\fly\core\log\CleanLogJob.java package com.fly.core.log; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import com.fly.core.utils.SpringContextUtils; import lombok.extern.slf4j.Slf4j; /** * * CleanLogJob * * @author 00fly * @version [版本号, 2022年11月30日] * @see [相关类/方法] * @since [产品/模块版本] */ @Slf4j @Component public class CleanLogJob { private String welcome = "Hello 00fly in ScheduleJob, profile: " + SpringContextUtils.getActiveProfile(); @Autowired private JdbcTemplate jdbcTemplate; @Scheduled(fixedRate = 10000L) public void run() { final long count = jdbcTemplate.queryForObject("select count(*) from boot_log", Long.class); log.info("------------------- boot_log count: {} --------------------", count); if (count > 100) { log.info("###### clean table boot_log ######"); // truncate 执行后将重新水平线和索引(id从零开始) // MySQL5.5版本开始引入了MDL锁(metadata lock),来保护表的元数据信息,用于解决或者保证DDL操作与DML操作之间的一致性 // 如果表上有活动事务(未提交或回滚),执行truncate table,请求写入的会话会等待在Metadata lock wait // 故尽量不要使用truncate table jdbcTemplate.execute("truncate table boot_log"); // jdbcTemplate.execute("delete from boot_log"); } } /** * 测试日志打印 */ @Scheduled(fixedRate = 500) public void run3() { log.trace("★★★★★★★★ {}", welcome); log.debug("★★★★★★★★ {}", welcome); log.info("★★★★★★★★ {}", welcome); log.warn("★★★★★★★★ {}", welcome); } } //goto src\main\java\com\fly\core\log\Log4j2Configuration.java package com.fly.core.log; import javax.sql.DataSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.stereotype.Component; import org.springframework.util.Assert; /** * 日志数据源注入,注意不能依赖web事件 */ @Component public class Log4j2Configuration implements ApplicationListener<ContextRefreshedEvent> { @Autowired DataSource dataSource; @Override public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) { // 初始化日志数据源 Assert.notNull(dataSource, "dataSource can not be null"); RefreshLogPoolManager.init(dataSource); } } //goto src\main\java\com\fly\core\log\RefreshLogPoolManager.java package com.fly.core.log; import java.sql.Connection; import java.sql.SQLException; import javax.sql.DataSource; import com.fly.core.utils.SpringContextUtils; /** * * 可刷新日志数据库数据源 * * @author 00fly * @version [版本号, 2023年3月27日] * @see [相关类/方法] * @since [产品/模块版本] */ public final class RefreshLogPoolManager { private static DataSource dataSource; public static void init(DataSource ds) { if (dataSource == null) { System.out.println("------------------- dataSource get in init -------------------" + ds); dataSource = ds; } } /** * getConnection * * @return * @throws SQLException * @see [类、类#方法、类#成员] */ public static Connection getConnection() throws SQLException { if (dataSource == null) { dataSource = SpringContextUtils.getBean(DataSource.class); System.out.println("------------------- dataSource get by springContextUtils -------------------" + dataSource); return dataSource.getConnection(); } System.out.println(dataSource); return dataSource.getConnection(); } } //goto src\main\java\com\fly\core\utils\SpringContextUtils.java package com.fly.core.utils; import java.net.InetAddress; import java.net.UnknownHostException; import javax.servlet.ServletContext; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; import org.springframework.util.Assert; import lombok.extern.slf4j.Slf4j; /** * Spring Context 工具类 * * @author 00fly * */ @Slf4j @Component public class SpringContextUtils implements ApplicationContextAware { private static ApplicationContext applicationContext; /** * web服务器基准URL */ private static String SERVER_BASE_URL = null; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { log.info("###### execute setApplicationContext ######"); SpringContextUtils.applicationContext = applicationContext; } public static ApplicationContext getApplicationContext() { return applicationContext; } public static <T> T getBean(Class<T> clazz) { Assert.notNull(applicationContext, "applicationContext is null"); return applicationContext.getBean(clazz); } /** * execute @PostConstruct May be SpringContextUtils not inited, throw NullPointerException * * @return */ public static String getActiveProfile() { Assert.notNull(applicationContext, "applicationContext is null"); String[] profiles = applicationContext.getEnvironment().getActiveProfiles(); return StringUtils.join(profiles, ","); } /** * can use in @PostConstruct * * @param context * @return */ public static String getActiveProfile(ApplicationContext context) { Assert.notNull(context, "context is null"); String[] profiles = context.getEnvironment().getActiveProfiles(); return StringUtils.join(profiles, ","); } /** * get web服务基准地址,一般为 http://${ip}:${port}/${contentPath} * * @return * @throws UnknownHostException * @see [类、类#方法、类#成员] */ public static String getServerBaseURL() throws UnknownHostException { if (SERVER_BASE_URL == null) { ServletContext servletContext = getBean(ServletContext.class); Assert.notNull(servletContext, "servletContext is null"); String ip = InetAddress.getLocalHost().getHostAddress(); SERVER_BASE_URL = "http://" + ip + ":" + getProperty("server.port") + servletContext.getContextPath(); } return SERVER_BASE_URL; } /** * getProperty * * @param key eg:server.port * @return * @see [类、类#方法、类#成员] */ public static String getProperty(String key) { return applicationContext.getEnvironment().getProperty(key, ""); } } //goto src\main\java\com\fly\hello\runner\WebStartedRunner.java package com.fly.hello.runner; import java.io.IOException; import javax.servlet.ServletContext; import org.apache.commons.lang3.SystemUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.web.context.WebServerInitializedEvent; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Component; import com.fly.core.utils.SpringContextUtils; import lombok.extern.slf4j.Slf4j; @Slf4j @Component @Configuration @ConditionalOnWebApplication public class WebStartedRunner implements ApplicationListener<WebServerInitializedEvent> { @Value("${server.port}") Integer port; @Autowired ServletContext servletContext; @Override public void onApplicationEvent(WebServerInitializedEvent event) { int port = event.getWebServer().getPort(); log.info("#### server.port: {} ####", port); } @Bean CommandLineRunner run() { return args -> { openBrowser(); }; } private void openBrowser() throws IOException { String url; switch (SpringContextUtils.getActiveProfile()) { case "prod": log.info("请修改hosts: 127.0.0.1 test.00fly.online"); url = "https://test.00fly.online:" + port + servletContext.getContextPath(); break; default: url = SpringContextUtils.getServerBaseURL(); break; } if (SystemUtils.IS_OS_WINDOWS && port > 0) { log.info("★★★★★★★★ now open Browser ★★★★★★★★ "); Runtime.getRuntime().exec("cmd /c start /min " + url); Runtime.getRuntime().exec("cmd /c start /min " + url + "/index"); if (SpringContextUtils.getActiveProfile().contains("dev")) { Runtime.getRuntime().exec("cmd /c start /min " + url + "/h2-console"); } } } } //goto src\main\java\com\fly\hello\web\DemoController.java package com.fly.hello.web; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.fly.core.JsonResult; import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiOperation; @Api(tags = "演示排序接口") @RestController @RequestMapping("/demo") public class DemoController { @GetMapping("/first") @ApiOperationSupport(order = 100) @ApiOperation("001 @ApiImplicitParams演示") @ApiImplicitParams({@ApiImplicitParam(name = "key", value = "键", required = true), @ApiImplicitParam(name = "value", value = "值", required = true)}) public JsonResult<?> first(String key, String value) { return JsonResult.success(); } @GetMapping("/second") @ApiOperationSupport(order = 80) @ApiOperation("002 @ApiImplicitParam演示") @ApiImplicitParam(name = "key", value = "键", required = true) public JsonResult<?> second(String key) { return JsonResult.success(); } @GetMapping("/third") @ApiOperationSupport(order = 60) @ApiOperation("003 @Parameter演示") @ApiImplicitParam(name = "key", value = "键,字符串", required = true) public JsonResult<?> third(String key) { return JsonResult.success(); } @GetMapping("/fourth") @ApiOperationSupport(order = 40) @ApiOperation("004 @Parameter演示") @ApiImplicitParams({@ApiImplicitParam(name = "key", value = "键", required = true), @ApiImplicitParam(name = "value", value = "值", required = true)}) public JsonResult<?> fourth(String key, String value) { return JsonResult.success(); } } //goto src\main\java\com\fly\hello\web\PicDataController.java package com.fly.hello.web; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import javax.annotation.PostConstruct; import javax.imageio.ImageIO; import org.apache.commons.lang3.RandomUtils; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.http.MediaType; import org.springframework.util.ResourceUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; @Api(tags = "图片接口") @Slf4j @RestController @RequestMapping("/show") public class PicDataController { Resource[] resources; List<Resource> resourceList = new ArrayList<>(); /** * FIFO */ private Queue<Integer> quque = new ConcurrentLinkedQueue<>(); @PostConstruct private void init() { try { resources = new PathMatchingResourcePatternResolver().getResources(ResourceUtils.CLASSPATH_URL_PREFIX + "data/pic/**/*.jpg"); Arrays.stream(resources).forEach(image -> { resourceList.add(image); log.info("add pic: {}", image.getFilename()); }); } catch (IOException e) { log.error(e.getMessage(), e); } } @ApiOperation("图片") @GetMapping(value = {"/girl", "/pic"}, produces = MediaType.IMAGE_JPEG_VALUE) public byte[] showPic1() throws IOException { ByteArrayOutputStream os = new ByteArrayOutputStream(); ImageIO.write(createImage(), "jpg", os); return os.toByteArray(); } /** * createImage 生成图片 * * @return * @throws IOException * @see [类、类#方法、类#成员] */ private synchronized BufferedImage createImage() throws IOException { if (resources.length < 4) { log.info("############### 请在[resources/data/pic/]目录放入不少于4张jpg图片 ###############"); return new BufferedImage(400, 400, BufferedImage.TYPE_BYTE_GRAY); } // 保留3条记录 while (quque.size() > 3) { quque.poll(); } // 新生成无重复数据 int index; do { index = RandomUtils.nextInt(0, resources.length); } while (quque.contains(index)); quque.add(index); log.info("add: {}, quque:{}", index, quque); // 取图片 return ImageIO.read(resources[index].getInputStream()); } } //goto src\main\java\com\fly\HelloApplication.java package com.fly; import javax.annotation.PreDestroy; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.RandomUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.WebApplicationType; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.web.servlet.ServletComponentScan; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableScheduling; import com.fly.core.utils.SpringContextUtils; import lombok.extern.slf4j.Slf4j; @Slf4j @EnableAsync @EnableScheduling @ServletComponentScan @SpringBootApplication public class HelloApplication { @Autowired SpringContextUtils springContextUtils; public static void main(String[] args) { // args = new String[] {"--noweb"}; boolean web = !ArrayUtils.contains(args, "--noweb"); log.info("############### with Web Configuration: {} #############", web); if (RandomUtils.nextBoolean()) { new SpringApplicationBuilder(HelloApplication.class).web(web ? WebApplicationType.SERVLET : WebApplicationType.NONE).run(args); } else { SpringApplication application = new SpringApplication(HelloApplication.class); application.setWebApplicationType(web ? WebApplicationType.SERVLET : WebApplicationType.NONE); application.run(args); } } @PreDestroy public void destroy() { log.info("###### destroy ######"); } } //goto src\main\resources\application-dev.yml spring: h2: console: enabled: true path: /h2-console settings: web-allow-others: true //goto src\main\resources\application.yml server: port: 8081 servlet: context-path: / session: timeout: 1800 spring: datasource: url: ${druid.url} username: ${druid.username} password: ${druid.password} driverClassName: ${druid.driverClassName} type: com.alibaba.druid.pool.DruidDataSource sqlScriptEncoding: utf-8 # initialization-mode: embedded # initialization-mode: never initialization-mode: always schema: classpath:schema.sql continue-on-error: true druid: initial-size: 5 # 初始化大小 min-idle: 10 # 最小连接数 max-active: 20 # 最大连接数 max-wait: 60000 # 获取连接时的最大等待时间 min-evictable-idle-time-millis: 300000 # 一个连接在池中最小生存的时间,单位是毫秒 time-between-eviction-runs-millis: 60000 # 多久才进行一次检测需要关闭的空闲连接,单位是毫秒 validation-query: SELECT 1 # 检测连接是否有效的 SQL语句,为空时以下三个配置均无效 test-on-borrow: true # 申请连接时执行validationQuery检测连接是否有效,默认true,开启后会降低性能 test-on-return: true # 归还连接时执行validationQuery检测连接是否有效,默认false,开启后会降低性能 test-while-idle: true # 申请连接时如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效,默认false,建议开启,不影响性能 devtools: restart: exclude: jdbc-log.properties profiles: active: - test servlet: multipart: max-file-size: 10MB max-request-size: 100MB knife4j: enable: true logging: level: root: info org.springframework.web: info //goto src\main\resources\jdbc-h2.properties druid.username=sa druid.password= druid.url=jdbc:h2:mem:hello;database_to_upper=false druid.driverClassName=org.h2.Driver //goto src\main\resources\jdbc-mysql.properties druid.url=jdbc:mysql://127.0.0.1:23306/hello?useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true druid.driverClassName=com.mysql.cj.jdbc.Driver druid.username=root druid.password=root123 druid.filters=stat druid.initialSize=2 druid.maxActive=20 druid.maxWait=60000 druid.timeBetweenEvictionRunsMillis=60000 druid.minEvictableIdleTimeMillis=300000 druid.validationQuery=SELECT 1 druid.testWhileIdle=true druid.testOnBorrow=false druid.testOnReturn=false druid.poolPreparedStatements=false druid.maxPoolPreparedStatementPerConnectionSize=200 //goto src\main\resources\log4j2.xml <?xml version="1.0" encoding="UTF-8"?> <!-- 该xml配置中,xml元素大小写不敏感 --> <!-- status="off",log4j2把自身事件记录到控制台的配置,off表示不记录,日志级别以及优先级排序: off > fatal > error > warn > info > debug > trace > all --> <!-- monitorInterval表示检测更改配置的时间,单位是秒,最小间隔为5,0或负数表示不检测 --> <configuration status="off" monitorInterval="0"> <!-- 常量引用 --> <properties> <property name="LOG_HOME">../logs</property> <property name="PROJECT">boot-hello</property> <property name="FORMAT">%d{MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n</property> </properties> <!-- appender用于接收各种日志 --> <appenders> <!-- 常见的输出到console,常用于开发环境中,默认是system_err,还有一个system_out --> <console name="Console" target="system_out"> <!-- appender级别的日志过滤 --> <!-- <thresholdFilter level="info" onMatch="accept" onMismatch="deny"/> --> <patternLayout pattern="${FORMAT}" /> </console> <!-- bufferSize 没起作用,待排查 --> <JDBC name="databaseAppender" bufferSize="20" tableName="boot_log"> <ConnectionFactory class="com.fly.core.log.RefreshLogPoolManager" method="getConnection" /> <Column name="event_id" pattern="%X{id}" /> <Column name="event_date" isEventTimestamp="true" /> <Column name="thread" pattern="%t %x" /> <Column name="class" pattern="%C" /> <Column name="`function`" pattern="%M" /> <Column name="message" pattern="%m" /> <Column name="exception" pattern="%ex{full}" /> <Column name="level" pattern="%level" /> <Column name="time" pattern="%d{yyyy-MM-dd HH:mm:ss.SSS}" /> </JDBC> <RollingRandomAccessFile name="RollingFileInfo" fileName="${LOG_HOME}/${PROJECT}/info.log" filePattern="${LOG_HOME}/${PROJECT}/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log.gz"> <Filters> <ThresholdFilter level="error" onMatch="deny" onMismatch="neutral" /> <ThresholdFilter level="warn" onMatch="deny" onMismatch="neutral" /> <ThresholdFilter level="info" onMatch="accept" onMismatch="deny" /> </Filters> <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n" /> <Policies> <TimeBasedTriggeringPolicy modulate="true" interval="1" /> <SizeBasedTriggeringPolicy size="20 MB" /> </Policies> <!-- DefaultRolloverStrategy不设置的话,max默认值为7,删除符合条件7天之前的日志 --> <!-- info-%d{yyyy-MM-dd}-%i.log.gz保存20个日志文件,删除60天之外的日志 --> <DefaultRolloverStrategy max="20"> <Delete basePath="${LOG_HOME}/${PROJECT}/" maxDepth="2"> <IfFileName glob="*/info-*.log.gz" /> <IfLastModified age="60d" /> </Delete> </DefaultRolloverStrategy> </RollingRandomAccessFile> <RollingRandomAccessFile name="RollingFileWarn" fileName="${LOG_HOME}/${PROJECT}/warn.log" filePattern="${LOG_HOME}/${PROJECT}/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log.gz"> <Filters> <ThresholdFilter level="error" onMatch="deny" onMismatch="neutral" /> <ThresholdFilter level="warn" onMatch="accept" onMismatch="deny" /> </Filters> <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n" /> <Policies> <TimeBasedTriggeringPolicy modulate="true" interval="1" /> <SizeBasedTriggeringPolicy size="20 MB" /> </Policies> <DefaultRolloverStrategy max="20"> <Delete basePath="${LOG_HOME}/${PROJECT}/" maxDepth="2"> <IfFileName glob="*/warn-*.log.gz" /> <IfLastModified age="60d" /> </Delete> </DefaultRolloverStrategy> </RollingRandomAccessFile> <RollingRandomAccessFile name="RollingFileError" fileName="${LOG_HOME}/${PROJECT}/error.log" filePattern="${LOG_HOME}/${PROJECT}/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log.gz"> <ThresholdFilter level="error" onMatch="accept" onMismatch="deny" /> <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n" /> <Policies> <TimeBasedTriggeringPolicy modulate="true" interval="1" /> <SizeBasedTriggeringPolicy size="20 MB" /> </Policies> <DefaultRolloverStrategy max="20"> <Delete basePath="${LOG_HOME}/${PROJECT}/" maxDepth="2"> <IfFileName glob="*/error-*.log.gz" /> <IfLastModified age="60d" /> </Delete> </DefaultRolloverStrategy> </RollingRandomAccessFile> </appenders> <!-- 接收appender --> <loggers> <!--过滤掉spring的一些无用的debug信息 --> <logger name="org.springframework" level="info" /> <!-- 定制包级别日志 --> <!-- 自定义 logger 对象 includeLocation="false" 关闭日志记录的行号信息,开启的话会严重影响异步输出的性能 additivity="false" 不再继承 rootlogger对象 --> <AsyncLogger name="com.fly.common" level="info" includeLocation="false" additivity="false"> <AppenderRef ref="Console" /> <AppenderRef ref="RollingFileInfo" /> <AppenderRef ref="RollingFileWarn" /> <AppenderRef ref="RollingFileError" /> </AsyncLogger> <AsyncLogger name="com.fly.core.log.job" level="info" includeLocation="true" additivity="false"> <AppenderRef ref="Console" /> </AsyncLogger> <!-- root logger,一般用于放置所有的appender --> <root level="info"> <appender-ref ref="Console" /> <AppenderRef ref="databaseAppender" /> <appender-ref ref="RollingFileInfo" /> <appender-ref ref="RollingFileWarn" /> <appender-ref ref="RollingFileError" /> </root> </loggers> <!-- 最终实现:混合异步打印日志,日志文件中,只打印本级别日志,控制台打印全部日志, --> </configuration> //goto src\main\resources\schema.sql DROP TABLE IF EXISTS `boot_log`; CREATE TABLE IF NOT EXISTS boot_log ( `id` bigint NOT NULL AUTO_INCREMENT , `event_id` varchar(50) , `event_date` datetime , `thread` varchar(255) , `class` varchar(255) , `function` varchar(255) , `message` varchar(255) , `exception` text, `level` varchar(255) , `time` datetime, PRIMARY KEY (id) ); //goto src\main\resources\static\index.html <html> <title>Hello World!</title> <body> <h2 align="center"> <a href="index">reload</a> </h2> <img src="show/girl" width="600" height="600" /> <img src="show/pic" width="600" height="600" /> </body> </html> //goto src\test\java\com\fly\hello\ApplicationTest.java package com.fly.hello; import java.util.Scanner; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.test.context.junit4.SpringRunner; import com.fly.HelloApplication; import lombok.extern.slf4j.Slf4j; @Slf4j @RunWith(SpringRunner.class) @SpringBootTest(classes = HelloApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT) public class ApplicationTest { /** * 注意:测试定时任务时,请勿使用dev环境,因为每个应用访问的是自己的内存数据库 */ @Test public void test() { try (Scanner sc = new Scanner(System.in)) { do { log.info("------------输入x退出,回车换行继续------------"); } while (!"x".equalsIgnoreCase(sc.nextLine())); log.info("------------成功退出------------"); } } } //goto src\test\java\com\fly\hello\simple\ClassPathResourceTest.java package com.fly.hello.simple; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.Properties; import javax.sql.DataSource; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.junit.Test; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PropertiesLoaderUtils; import org.springframework.util.ResourceUtils; import com.alibaba.druid.pool.DruidDataSourceFactory; import lombok.extern.slf4j.Slf4j; @Slf4j public class ClassPathResourceTest { @Test public void test() throws IOException { if (ResourceUtils.isFileURL(ResourceUtils.getURL("classpath:"))) { File src = new ClassPathResource("application.properties").getFile(); String path = ResourceUtils.getURL("classpath:").getPath() + "log-jdbc.properties"; File dest = new File(path); log.info("{} ==> {}", src.getCanonicalPath(), dest.getCanonicalPath()); FileUtils.copyFile(src, dest); } } @Test public void test2() throws Exception { // 通过properties文件传递数据源配置 if (ResourceUtils.isFileURL(ResourceUtils.getURL("classpath:"))) { String path = ResourceUtils.getURL("classpath:").getPath() + "jdbc-log.properties"; Collection<String> lines = new ArrayList<>(); lines.add("druid.url="); lines.add("druid.username="); lines.add("druid.password="); lines.add("druid.driverClassName="); try (OutputStream outputStream = new FileOutputStream(new File(path))) { IOUtils.writeLines(lines, null, outputStream, StandardCharsets.UTF_8); } // 读取 Resource resource = new ClassPathResource("jdbc-log.properties"); Properties properties = PropertiesLoaderUtils.loadProperties(resource); DataSource dataSource = DruidDataSourceFactory.createDataSource(properties); log.info("properties: {}", properties); log.info("dataSource: {}", dataSource); } } }

四,bug1 详情

1. 运行准备

1. )将 application.yml 文件active设置为test

  profiles:
    active:
    - test

2.)修改jdbc-mysql.properties 数据库参数设为实际值

druid.url=jdbc:mysql://127.0.0.1:23306/hello?useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
druid.driverClassName=com.mysql.cj.jdbc.Driver
druid.username=root
druid.password=root123
druid.filters=stat
druid.initialSize=2
druid.maxActive=20
druid.maxWait=60000
druid.timeBetweenEvictionRunsMillis=60000
druid.minEvictableIdleTimeMillis=300000
druid.validationQuery=SELECT 1
druid.testWhileIdle=true
druid.testOnBorrow=false
druid.testOnReturn=false
druid.poolPreparedStatements=false
druid.maxPoolPreparedStatementPerConnectionSize=200

3.)注释 RefreshLogPoolManager 40-45行代码

【求教】老菜鸟遇到新问题,双bug欢迎有緣人答疑_第2张图片

2. bug现象

1.)数据表记录超过100后,执行清理操作时,后台报错

【求教】老菜鸟遇到新问题,双bug欢迎有緣人答疑_第3张图片
【求教】老菜鸟遇到新问题,双bug欢迎有緣人答疑_第4张图片

2.)mysql 执行SHOW PROCESSLIST 发现数据库连接处于 Waiting for table metadata lock 状态

【求教】老菜鸟遇到新问题,双bug欢迎有緣人答疑_第5张图片

3. 疑问

假如不注释 RefreshLogPoolManager 40-45行代码,运行程序则不会发生这个错误。
现在我想知道的是: 发生这个错误的根本原因是什么?

五,bug2 详情

1. 运行准备

  1. 不注释 RefreshLogPoolManager 40-45行代码
  2. 将pom文件 108-112行注释打开,启用spring-boot-devtools

2. bug现象

运行程序,发现日志保存根本不被执行【求教】老菜鸟遇到新问题,双bug欢迎有緣人答疑_第6张图片
【求教】老菜鸟遇到新问题,双bug欢迎有緣人答疑_第7张图片

3. 疑问

现在我想知道的是: 启用spring-boot-devtools 为什么会影响到日志的保存?


如有高手帮忙释疑不胜感激,谢谢!

你可能感兴趣的:(Spring,Java,系统架构,bug,日志)