项目问题汇总(持续更新中)

项目遇到的问题:

持续记录日常踩坑,希望大家自己踩一遍能帮大家少走弯路

Java篇

1. target中无自定义路径的xml文件

项目目录参考

微信截图_20220718184912

微信截图_20220718184835

解决方法

1、在pom.xml中放行mapper.xml,在Maven的build中加入以下配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<build>
<resources>
<!--以下是xml放置java文件下的放行-->
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<!--以下是xml放置resource文件下的放行-->
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.yml</include>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>

2、配置application.properties文件

1
2
#配置mybatis-plus的xml路径
mybatis-plus.mapper-locations=classpath:com/example/common/mapper/*.xml

3、maven选择clean和campile重新编译即可

微信截图_20220718185824

4、成功生成xml

微信截图_20220718185914

2. 多模块找不到配置属性

报错参考

1
2
3
4
5
6
7
8
9
10
***************************
APPLICATION FAILED TO START
***************************
Description:
Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.
Reason: Failed to determine a suitable driver class
Action:
Consider the following:
If you want an embedded database (H2, HSQL or Derby), please put it on the classpath.
If you have database settings to be loaded from a particular profile you may need to activate it (no profiles are currently active).

原因

1、SpringBoot启动的时候默认会在以下5个路径下找配置文件

  • 项目路径例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
springboot
┠.idea
┠config
┠my
#application.properties
#application.properties
┠src
┠main
┠java
┠resources
┠config
#application.properties
#application.properties
#application.properties
┠pom.xml
  • 优先级由上往下递减
1
2
3
4
5
file:./config/                    [项目根目录下的config目录]
file:./config/**/ [根文件加下的config/**/目录]
file:./ [根目录下]
classpath:config/ [resources/config下]
classpath: [resources下]

2、多模块项目启动时会去匹配当前模块的根目录路径

解决方法

1、启动模块下添加application.properties文件

2、若多模块下公用一个配置文件,可以用spring.profiles.active指定公共配置文件

  • 多模块项目路径例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    springboot
    ┠.idea
    ┠dream-admin(后台启动模块)
    ┠main
    ┠java
    ┠resources
    #application.properties
    ┠dream-core(核心模块)
    ┠main
    ┠java
    ┠resources
    #application-common.properties
    ┠pom.xm
  • application.properties:

    1
    spring.profiles.active=common
  • application-common.properties:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    spring.datasource.url=jdbc:mysql://127.0.0.1:3306/dream?characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
    spring.datasource.username=root
    spring.datasource.password=123456
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    #spring.datasource.driver-class-name=com.mysql.jdbc.Driver

    server.port=80

    mybatis-plus.mapper-locations=classpath*:dc/common/mapper/**Mapper.xml

3、配置完后用maven的插件compile一下

3. 多模块找不到主类

报错参考

微信截图_20220721182918

原因

参考官方文档

一旦spring-boot-maven-plugin被包含到pomm .xml中,通过使用spring-boot:repackage目标,它会自动尝试重写归档文件,使它们可执行,当启动类找不到时会去找第一个启动方法

解决方法

1、将父类pom.xml删除spring-boot-maven-plugin相关

1
2
3
4
5
6
7
8
<!--一旦spring-boot-maven-plugin被包含到pomm .xml中,通过使用spring-boot:repackage目标,它会自动尝试重写归档文件,使它们可执行-->
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugins>

2、在启动的模块下的pom.xml添加spring-boot-maven-plugin相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<!-- 指定该Main Class为全局的唯一入口 -->
<mainClass>dc.DreamApiApplication</mainClass>
</configuration>
<executions>
<execution>
<goals>
<!--可以把依赖的包都打包到生成的Jar包中-->
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<!--默认关掉单元测试 -->
<skipTests>true</skipTests>
</configuration>
</plugin>
</plugins>

4. jar包配置SSL失败

配置参考

我的SpringBoot依赖配置

1
2
3
springboot <version>2.1.9</version>
tomacat <version>9.0.22</version>
JDK <version>1.8</version>

yaml配置(SSL证书是阿里云免费申请的.jks文件默认放Resources目录下即可)

1
2
3
4
5
6
7
8
9
server:
port: 443
servlet:
context-path: /ZaiZai
ssl:
enabled: true
key-store: classpath:5989139_dreamcollector.ltd.jks #5989139_dreamcollector.ltd.jks为jks文件名#
key-store-password: 3Fut7Rt1 #此处为jks给的txt文件中的密码#
key-store-type: JKS #指定文件类型为jks#

若本地调试提示端口占用问题

1
2
3
netstat -ano|find "443"        #查找进程状态为LISTENING的进程#
taskkill /f /im xxx.exe #按进程名强制结束#
taskkill /f /pid 8642 #按进程PID-8642强制结束#

解决方法

1、cmd 中查找占用端口的进程并结束进程

1
2
3
netstat -ano|find "443"        #查找进程状态为LISTENING的进程#
taskkill /f /im xxx.exe #按进程名强制结束#
taskkill /f /pid 8642 #按进程PID-8642强制结束#

2、若本地调试运行正常但打包jar包后报错,可尝试将springboot版本降低到 2.1.8

5. 获取webp后缀图片尺寸失败

代码参考

如果在线图片是webp后缀的图片都获取不到图片尺寸

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static void main(String[] args) throws IOException {
String webpImageUrl = "https://d13n19w6sf5stu.cloudfront.net/comic/manhwahentai/popular/29340/83/01.webp";

try {
URL url = new URL(webpImageUrl);
InputStream inputStream = url.openStream();

// 使用 ImageIO 读取 WebP 图片
BufferedImage webpImage = ImageIO.read(inputStream);

if (webpImage != null) {
int width = webpImage.getWidth();
int height = webpImage.getHeight();
Dimension imageSize = new Dimension(width, height);

System.out.println("WebP 图片宽度:" + imageSize.width);
System.out.println("WebP 图片高度:" + imageSize.height);
} else {
System.out.println("无法读取 WebP 图片。");
}
} catch (IOException e) {
e.printStackTrace();
}

}

原因

标准的 javax.imageio.ImageIO 不支持读取 WebP 格式的图片,ImageIO支持读写的文件后缀:jpgbmpgifpngwbmpjpeg,通过以下代码可以查看:

1
2
System.out.println("ImageIO支读取的文件后缀:" + String.join(",", ImageIO.getReaderFileSuffixes()));
System.out.println("ImageIO支持写入的文件后缀:" + String.join(",", ImageIO.getWriterFileSuffixes()));

解决方法

新增webp-imageio三方扩展库依赖来支持webp后缀图片

1
2
3
4
5
6
7
<!-- https://mvnrepository.com/artifact/org.sejda.imageio/webp-imageio -->
<dependency>
<groupId>org.sejda.imageio</groupId>
<artifactId>webp-imageio</artifactId>
<version>0.1.6</version>
</dependency>

6. Scheduled定时任务没有按时执行

前提是排除没有开启定时任务@EnableScheduling,能正常的执行定时任务的哈

代码参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class TestTask {
@Scheduled(fixedDelay = 1000 * 60L) //1分钟同步一次数据
public void task1() {
System.out.println("task1");

}
@Scheduled(fixedDelay = 1000 * 60L) //1分钟同步一次数据
public void task2() {
System.out.println("task2");

}
@Scheduled(fixedDelay = 1000 * 60L) //1分钟同步一次数据
public void task3() {
System.out.println("task3");

}
...
}

原因

默认情况下,Spring的@Scheduled注解是串行执行的,即任务是在单线程中执行的。如果上一次的任务还未完成,下一次任务将等待上一次任务完成后执行。在这个等待的过程中,如果首次任务的执行时间超过了1分钟,就会导致主线程第二次的任务重复执行;如果有多个定时任务时,时间挨得比较近,其中一个耗时比较长就会影响其他的定时任务,导致主线程第二次的任务迟迟不会执行

解决方法

方案1:将默认的Scheduled改为多线程的模式

1
2
3
4
5
6
7
8
9
10
11
@Configuration
public class ScheduledConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
//修改Scheduled为多线程执行-采用CallerRunsPolicy策略
ThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(15,
new CustomizableThreadFactory("定时任务线程:"),
new ThreadPoolExecutor.CallerRunsPolicy());
taskRegistrar.setScheduler(executor);
}
}

方案2:开启异步调用@EnableAsync,并使用@Async注解开启异步操作,但是@Async有安全隐患

1
2
3
4
5
6
public class TestTask {
@Async
@Scheduled(fixedDelay = 1000 * 60L) //1分钟同步一次数据
public void task1() {
System.out.println("task1");
}

方案3:比较建议方案1加上再加上方案3一起,多线程+锁防止不同线程重复操作同一批数据,这里用Redisson的锁做示范

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class TestTask {
@Resource(name = "redissonClient")
private RedissonClient redissonClient;

@Scheduled(fixedDelay = 1000 * 60L) //1分钟同步一次数据
public void task1() {
RLock lock = redissonClient.getLock("taskLock");
try {
if (lock.tryLock()) {
// 获取到锁,执行相关任务
System.out.println("task1");
} else {
// 未获取到锁,任务已在执行中
logger.info("Another instance is processing the task. Skip current execution.");
}
} catch (Exception e) {
e.printStackTrace();
logger.error("task | msg : " + e.getMessage());
} finally {
lock.unlock();
}
}

}

7. stream.toList()的坑

代码参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
List<Long> ids = new ArrayList<>();
if (CollectionUtils.isNotEmpty(ids)) {
List<OeChannelRuleSetting> oeChannelRuleSettings = oeChannelRuleSettingMapper.selectByAdvertiserIds(advertiserIds);
adRuleIds = oeChannelRuleSettings.stream().map(OeChannelRuleSetting::getAdRuleId).toList();

}
List<task> tasks=taskMapper.selectAll(ids);

//taskMapper.xml
<select id="selectAll" resultMap="BaseResultMap">
select *
from task
<where>
<if test="ids != null and ids.size() != 0">
and ad_rule_id in
<foreach collection="ids" item="item" open="(" close=")" separator=",">
#{item}
</foreach>
</if>
</where>
order by id desc
</select>

报错参考

1
2
3
4
5
6
7
8
9
10
11
12
13
### Error querying database.  Cause: java.lang.reflect.InaccessibleObjectException: Unable to make public int java.util.ImmutableCollections$ListN.size() accessible: module java.base does not "opens java.util" to unnamed module @2df3b89c
### Cause: java.lang.reflect.InaccessibleObjectException: Unable to make public int java.util.ImmutableCollections$ListN.size() accessible: module java.base does not "opens java.util" to unnamed module @2df3b89c
at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:96)
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:441)
at jdk.proxy2/jdk.proxy2.$Proxy84.selectList(Unknown Source)
at org.mybatis.spring.SqlSessionTemplate.selectList(SqlSessionTemplate.java:224)
at org.apache.ibatis.binding.MapperMethod.executeForMany(MapperMethod.java:147)
at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:80)
at org.apache.ibatis.binding.MapperProxy$PlainMethodInvoker.invoke(MapperProxy.java:145)
at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:86)
at jdk.proxy2/jdk.proxy2.$Proxy162.selectAll(Unknown Source)
at com.maiyawx.put.service.batchput.oe.RuleTaskService.selectAll(TaskService.java:75)
at com.maiyawx.put.service.batchput.oe.RuleTaskService$$FastClassBySpringCGLIB$$c5d93ce0.invoke(<generated>)

原因

在 Java 9 和以后的版本中,Collections.unmodifiableList() 返回的是一个不可变的 List,它的实现类是 ImmutableCollections$ListNImmutableCollections$ListN 类在反射方面有严格的访问控制。这个类并不允许通过反射访问其方法,因为它在模块系统中被标记为不可访问。在java.base 模块并没有将 java.util 包开放给外部模块,导致你的代码试图通过反射访问该类的 size() 等方法时抛出异常

image-20241106171623357

解决方法

1
2
3
4
5
6
7
8
9
10
11
12
//1.用`Collectors.toList()`方式
adRuleIds = oeChannelRuleSettings.stream()
.map(OeChannelRuleSetting::getAdRuleId)
.collect(Collectors.toList());
//2.创建一个新的 ArrayList
adRuleIds = new ArrayList<>(oeChannelRuleSettings.stream()
.map(OeChannelRuleSetting::getAdRuleId)
.toList()); // 创建一个新的 ArrayList
//3.使用 Collectors.toCollection() 创建指定类型的集合
adRuleIds = oeChannelRuleSettings.stream()
.map(OeChannelRuleSetting::getAdRuleId)
.collect(Collectors.toCollection(ArrayList::new));

Mysql篇

1. 修改全文索引命中最小长度无效

SQL参考

查询漫画来源为manga,并且comic_namecomic_introauthor_name字段中包含Sta的数据,若查询包含Stag的数据则可以正常查询出来(前提:已经把索引的最小长度修改成功并重启服务,只是查询不命中)

1
2
3
4
5
6
7
8
9
SELECT
*
FROM
comic
WHERE
comic_source = 'manga'
AND MATCH ( comic_name, comic_intro, author_name ) AGAINST ( 'Sta' );

LIMIT 5

原因

由于Mysql的全文搜索默认是在自然MySQL的全文搜索默认在自然语言模式下工作,它可能会忽略某些单词,尤其是那些被认为是“常见”的或“不重要”的单词。即使你已经将innodb_ft_min_token_size’设置为1,Sta’这样的短词可能仍然被忽略。这是因为MySQL可能认为它是一个常见的词前缀

解决方法

使用布尔搜索模式,+*不可忽略

1
2
3
4
5
6
7
8
9
SELECT
*
FROM
comic
WHERE
comic_source = 'manga'
AND MATCH (comic_name, comic_intro, author_name) AGAINST ('+Sta*' IN BOOLEAN MODE)

LIMIT 5

完整流程

1、修改mysql根目录下的my.ini配置文件,添加以下配置

1
2
3
4
5
[mysqld]
#MyISAM存储引擎下的修改
ft_min_word_len=1
#Innodb存储引擎下的修改
innodb_ft_min_token_size = 1

2、重启mysql服务

image-20230901161253024

3、SQL查询修改是否生效,%内换成对应存储引擎下的配置

1
SHOW GLOBAL VARIABLES LIKE '%min_token_size%'

4、新建Fulltext全文索引,若已存在则需删除重建索引

需确保覆盖到要全文索引的字段

5、用布尔搜索模式

1
MATCH (comic_name, comic_intro, author_name) AGAINST ('+Sta*' IN BOOLEAN MODE)

2. order by+limit 数据重复出现

SQL参考

前提:publish_at没有任何索引,为了更直观的比较第一页跟第二页重复的数据可以用INTERSECT关键字将两条sql所查询出来的数据做并集(只有当数据总量足够多的时候分页才可能会出现重复出现数据,而且INTERSECT关键字得看具体Mysql版本)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#1.根据发布时间降序的第一页,每页30条数据
SELECT
*
FROM
comic
ORDER BY
publish_at DESC
LIMIT 30

#求两SQL并集的关键字
INTERSECT

#2.根据发布时间降序的第二页,每页30条数据
SELECT
*
FROM
comic
ORDER BY
publish_at DESC
LIMIT 30 OFFSET 30

原因

order by+limit时Mysql会进行优化,使用的是内存中的filesort文件排序,in memory filesort 使用的是优先级队列(priority queue),优先级队列使用的二叉堆。使用priority queue 的目的,就是在不能使用索引有序性的时候,如果要排序,并且使用了limit n,那么只需要在排序的过程中,保留n条记录即可这样虽然不能解决所有记录都需要排序的开销,但是只需要 sort buffer 少量的内存就可以完成排序。因此,在limit n时,只会堆排序前n个,且是不稳定排序,因此并不能保证字段值相同时的相对顺序,因此分页时可能造成重复;MySQL 5.5 没有这个优化,所以也就不会出现这个问题,5.6版本之后才出现了这种情况

官方描述:如果多行在列中具有相同的值ORDER BY,则服务器可以自由地以任何顺序返回这些行,并且可以根据整体执行计划以不同的方式返回这些行。换句话说,这些行的排序顺序相对于无序列来说是不确定的。影响执行计划的因素之一是 LIMIT,因此ORDER BY 带有和不带有 的查询LIMIT可能会以不同的顺序返回行。

解决方法

ORDER BY子句中包含其他列以使顺序具有确定性,例如id为主键具有唯一性,又或者ORDER BY的字段拥有索引

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#1.加入`id`字段确保顺序唯一
SELECT
*
FROM
comic
ORDER BY
publish_at,id DESC
LIMIT 30 OFFSET 30

#2.给`publish_at`字段上索引,利用索引的有序性
CREATE INDEX idx_publish_at ON comic (publish_at);
SELECT
*
FROM
comic
ORDER BY
publish_at DESC
LIMIT 30 OFFSET 30;