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

2023-10-27 22:20

本文主要是介绍【求教】老菜鸟遇到新问题,双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. 代码结构

在这里插入图片描述

2. 完整代码备份

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

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

//goto docker\docker-compose.yml
version: '3'
networks:default:name: devopsdriver: bridgeipam:config:- subnet: 172.88.88.0/24services:hello:image: registry.cn-shanghai.aliyuncs.com/00fly/springboot-log:1.0.0container_name: hellodeploy:resources:limits:cpus: '1'memory: 300Mreservations:memory: 200Mports:- 8080:8081environment:JAVA_OPTS: -server -Xms200m -Xmx200m -Djava.security.egd=file:/dev/./urandomSPRING_DATASOURCE_USERNAME: testSPRING_DATASOURCE_PASSWORD: test123SPRING_DATASOURCE_DRIVERCLASSNAME: com.mysql.cj.jdbc.DriverSPRING_DATASOURCE_URL: jdbc:mysql://172.88.88.11:3306/test?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&autoReconnect=truerestart: on-failurelogging:driver: json-fileoptions:max-size: 5mmax-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.0container_name: hello-simpledeploy:resources:limits:cpus: '1'memory: 300Mreservations:memory: 200Menvironment:JAVA_OPTS: -server -Xms200m -Xmx200m -Djava.security.egd=file:/dev/./urandomSPRING_DATASOURCE_USERNAME: testSPRING_DATASOURCE_PASSWORD: test123SPRING_DATASOURCE_DRIVERCLASSNAME: com.mysql.cj.jdbc.DriverSPRING_DATASOURCE_URL: jdbc:mysql://172.88.88.11:3306/test?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&autoReconnect=truerestart: on-failurelogging:driver: json-fileoptions:max-size: 5mmax-file: '1'entrypoint: 'sh wait-for.sh 172.88.88.11:3306 -- java -jar /app.jar --noweb'mysql8:image: mysql:8.0container_name: mysql-8deploy:resources:limits:cpus: '2'memory: 500Mreservations:memory: 300Mvolumes:- ./mysql8:/var/lib/mysql/ports:- 3307:3306environment:TZ: Asia/ShanghaiMYSQL_ROOT_PASSWORD: root123MYSQL_USER: testMYSQL_PASSWORD: test123MYSQL_DATABASE: testcommand:--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_SUBSTITUTIONrestart: on-failurelogging: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/shTIMEOUT=15
QUIET=0echoerr() {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
USAGEexit "$exitcode"
}wait_for() {for i in `seq $TIMEOUT` ; donc -z "$HOST" "$PORT" > /dev/null 2>&1result=$?if [ $result -eq 0 ] ; thenif [ $# -gt 0 ] ; thenexec "$@"fiexit 0fisleep 1doneecho "Operation timed out" >&2exit 1
}while [ $# -gt 0 ]
docase "$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=1shift 1;;-t)TIMEOUT="$2"if [ "$TIMEOUT" = "" ]; then break; fishift 2;;--timeout=*)TIMEOUT="${1#*=}"shift 1;;--)shiftbreak;;--help)usage 0;;*)echoerr "Unknown argument: $1"usage 1;;esac
doneif [ "$HOST" = "" -o "$PORT" = "" ]; thenechoerr "Error: you need to provide a host and port to test."usage 2
fiwait_for "$@"//goto Dockerfile
#基础镜像
FROM openjdk:8-jre-alpineRUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezoneCOPY docker/wait-for.sh /
RUN chmod +x /wait-for.sh#拷贝发布包
COPY target/*.jar  /app.jarEXPOSE 8080CMD ["--server.port=8080"]#启动脚本
ENTRYPOINT ["java","-Xmx128m","-Xms128m","-jar","/app.jar"]
//goto 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.fly</groupId><artifactId>springboot-log</artifactId><version>1.0.0</version><packaging>jar</packaging><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.4.RELEASE</version><relativePath /></parent><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.build.timestamp.format>yyyyMMdd-HH</maven.build.timestamp.format><docker.hub>registry.cn-shanghai.aliyuncs.com</docker.hub><java.version>1.8</java.version><skipTests>true</skipTests></properties><dependencies><!-- Compile --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId></exclusion><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-log4j2</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId><exclusions><exclusion><groupId>org.apache.tomcat</groupId><artifactId>tomcat-jdbc</artifactId></exclusion></exclusions></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.16</version></dependency><dependency><groupId>com.h2database</groupId><artifactId>h2</artifactId><scope>runtime</scope></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-spring-boot-starter</artifactId><version>2.0.5</version></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId></dependency><!-- 异步日志,需要加入disruptor依赖 --><dependency><groupId>com.lmax</groupId><artifactId>disruptor</artifactId><version>3.4.2</version></dependency><!-- Provided --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jetty</artifactId></dependency><dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.5</version></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><scope>provided</scope></dependency><!--<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><optional>true</optional></dependency>--><!-- Test --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><!-- 阿里云maven仓库 --><repositories><repository><id>public</id><name>aliyun nexus</name><url>https://maven.aliyun.com/repository/public/</url><releases><enabled>true</enabled></releases></repository></repositories><pluginRepositories><pluginRepository><id>public</id><name>aliyun nexus</name><url>https://maven.aliyun.com/repository/public/</url><releases><enabled>true</enabled></releases><snapshots><enabled>false</enabled></snapshots></pluginRepository></pluginRepositories><build><finalName>${project.artifactId}-${project.version}</finalName><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin><!-- 添加docker-maven插件 --><plugin><groupId>io.fabric8</groupId><artifactId>docker-maven-plugin</artifactId><version>0.41.0</version><executions><execution><phase>package</phase><goals><goal>build</goal><goal>push</goal><goal>remove</goal></goals></execution></executions><configuration><!-- 连接到带docker环境的linux服务器编译image --><!--<dockerHost>http://192.168.182.10:2375</dockerHost>--><!-- Docker 推送镜像仓库地址 --><pushRegistry>${docker.hub}</pushRegistry><images><image><!--推送到私有镜像仓库,镜像名需要添加仓库地址 --><name>${docker.hub}/00fly/${project.artifactId}:${project.version}-UTC-${maven.build.timestamp}</name><!--定义镜像构建行为 --><build><dockerFileDir>${project.basedir}</dockerFileDir></build></image><image><name>${docker.hub}/00fly/${project.artifactId}:${project.version}</name><build><dockerFileDir>${project.basedir}</dockerFileDir></build></image></images></configuration></plugin></plugins><resources><resource><directory>src/main/java</directory><excludes><exclude>**/*.java</exclude></excludes></resource><resource><directory>src/main/resources</directory><includes><include>**/**</include></includes><filtering>false</filtering></resource></resources></build>
</project>
//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
{@BeanThreadPoolTaskExecutor 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;}@Overridepublic Executor getAsyncExecutor(){return taskExecutor();}@Overridepublic 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;@BeanDocket 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
{@Overridepublic 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
{@Overridepublic void configureMessageConverters(final List<HttpMessageConverter<?>> converters){converters.add(this.stringHttpMessageConverter());converters.add(this.mappingJackson2HttpMessageConverter());}@Overridepublic 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);}@Beanpublic StringHttpMessageConverter stringHttpMessageConverter(){return new StringHttpMessageConverter();}@Beanpublic 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:view-controller path="/" view-name="redirect:doc.html" /><br>* 等价于mvc中<mvc:view-controller path="/index" view-name="index" />* * @param registry*/@Overridepublic void addViewControllers(final ViewControllerRegistry registry){registry.addViewController("/").setViewName("redirect:doc.html");registry.addViewController("/index").setViewName("index.html");registry.addViewController("/in").setViewName("auto.html");}/*** 加载静态资源文件或文件映射*/@Overridepublic 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();@Autowiredprivate 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 tablejdbcTemplate.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>
{@AutowiredDataSource dataSource;@Overridepublic 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;@Overridepublic 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;@AutowiredServletContext servletContext;@Overridepublic void onApplicationEvent(WebServerInitializedEvent event){int port = event.getWebServer().getPort();log.info("#### server.port: {} ####", port);}@BeanCommandLineRunner 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<>();@PostConstructprivate 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
{@AutowiredSpringContextUtils 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);}}@PreDestroypublic void destroy(){log.info("###### destroy ######");}
}
//goto src\main\resources\application-dev.yml
spring:h2:console:enabled: truepath: /h2-consolesettings:web-allow-others: true
//goto src\main\resources\application.yml
server:port: 8081servlet:context-path: /session:timeout: 1800
spring:datasource:url: ${druid.url}username: ${druid.username}password: ${druid.password}driverClassName: ${druid.driverClassName}type: com.alibaba.druid.pool.DruidDataSourcesqlScriptEncoding: utf-8
#   initialization-mode: embedded
#   initialization-mode: neverinitialization-mode: alwaysschema: classpath:schema.sqlcontinue-on-error: truedruid: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.propertiesprofiles:active:- testservlet:multipart:max-file-size: 10MBmax-request-size: 100MB
knife4j:enable: true
logging:level:root: infoorg.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环境,因为每个应用访问的是自己的内存数据库*/@Testpublic 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
{@Testpublic 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);}}@Testpublic 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行代码

在这里插入图片描述

2. bug现象

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

在这里插入图片描述
在这里插入图片描述

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

在这里插入图片描述

3. 疑问

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

五,bug2 详情

1. 运行准备

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

2. bug现象

运行程序,发现日志保存根本不被执行在这里插入图片描述
在这里插入图片描述

3. 疑问

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


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

这篇关于【求教】老菜鸟遇到新问题,双bug欢迎有緣人答疑的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/288797

相关文章

好题——hdu2522(小数问题:求1/n的第一个循环节)

好喜欢这题,第一次做小数问题,一开始真心没思路,然后参考了网上的一些资料。 知识点***********************************无限不循环小数即无理数,不能写作两整数之比*****************************(一开始没想到,小学没学好) 此题1/n肯定是一个有限循环小数,了解这些后就能做此题了。 按照除法的机制,用一个函数表示出来就可以了,代码如下

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

购买磨轮平衡机时应该注意什么问题和技巧

在购买磨轮平衡机时,您应该注意以下几个关键点: 平衡精度 平衡精度是衡量平衡机性能的核心指标,直接影响到不平衡量的检测与校准的准确性,从而决定磨轮的振动和噪声水平。高精度的平衡机能显著减少振动和噪声,提高磨削加工的精度。 转速范围 宽广的转速范围意味着平衡机能够处理更多种类的磨轮,适应不同的工作条件和规格要求。 振动监测能力 振动监测能力是评估平衡机性能的重要因素。通过传感器实时监

缓存雪崩问题

缓存雪崩是缓存中大量key失效后当高并发到来时导致大量请求到数据库,瞬间耗尽数据库资源,导致数据库无法使用。 解决方案: 1、使用锁进行控制 2、对同一类型信息的key设置不同的过期时间 3、缓存预热 1. 什么是缓存雪崩 缓存雪崩是指在短时间内,大量缓存数据同时失效,导致所有请求直接涌向数据库,瞬间增加数据库的负载压力,可能导致数据库性能下降甚至崩溃。这种情况往往发生在缓存中大量 k

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)

【VUE】跨域问题的概念,以及解决方法。

目录 1.跨域概念 2.解决方法 2.1 配置网络请求代理 2.2 使用@CrossOrigin 注解 2.3 通过配置文件实现跨域 2.4 添加 CorsWebFilter 来解决跨域问题 1.跨域概念 跨域问题是由于浏览器实施了同源策略,该策略要求请求的域名、协议和端口必须与提供资源的服务相同。如果不相同,则需要服务器显式地允许这种跨域请求。一般在springbo

题目1254:N皇后问题

题目1254:N皇后问题 时间限制:1 秒 内存限制:128 兆 特殊判题:否 题目描述: N皇后问题,即在N*N的方格棋盘内放置了N个皇后,使得它们不相互攻击(即任意2个皇后不允许处在同一排,同一列,也不允许处在同一斜线上。因为皇后可以直走,横走和斜走如下图)。 你的任务是,对于给定的N,求出有多少种合法的放置方法。输出N皇后问题所有不同的摆放情况个数。 输入

vscode中文乱码问题,注释,终端,调试乱码一劳永逸版

忘记咋回事突然出现了乱码问题,很多方法都试了,注释乱码解决了,终端又乱码,调试窗口也乱码,最后经过本人不懈努力,终于全部解决了,现在分享给大家我的方法。 乱码的原因是各个地方用的编码格式不统一,所以把他们设成统一的utf8. 1.电脑的编码格式 开始-设置-时间和语言-语言和区域 管理语言设置-更改系统区域设置-勾选Bata版:使用utf8-确定-然后按指示重启 2.vscode

Android Environment 获取的路径问题

1. 以获取 /System 路径为例 /*** Return root of the "system" partition holding the core Android OS.* Always present and mounted read-only.*/public static @NonNull File getRootDirectory() {return DIR_ANDR

form表单提交编码的问题

浏览器在form提交后,会生成一个HTTP的头部信息"content-type",标准规定其形式为Content-type: application/x-www-form-urlencoded; charset=UTF-8        那么我们如果需要修改编码,不使用默认的,那么可以如下这样操作修改编码,来满足需求: hmtl代码:   <meta http-equiv="Conte