bug解决
# bug 解决
# 1、bean 冲突
问题:
本地 Demo 项目使用 @Resource
注解注入 MyMessageChannel
自定义消息通道接口的时候,没有问题,但融入大项目的时候就会报错(找不到这个 bean),换成使用 Spring 自带的 @Autowired
注解就会没问题。
猜测原因:
可能是因为是两个注解默认注入 bean 的方式不一样引起的
参考知识点:
# 2、占位符问题
错误的写法
下面这种写法会导致第二个 {}
占位符接收不到记录数的数据
log.info("接收到的消息数:{}",getReceivedFaceMessages() + " --- 转发成功的消息数:{}", getForwardedFaceMessages());
正解
将占位符 {}
分别替换为每个计数器的值,类似于以下方式:
确保在日志输出中提供正确的参数,以便正确显示接收数和转发数。
log.info("接收到的消息数:{} --- 转发成功的消息数:{}", getReceivedFaceMessages(), getForwardedFaceMessages());
# 3、Navicat 如何设置字段唯一约束
参考文章:navicat for mysql 如何设置字段唯一 (opens new window)
# 4、插入数据时覆盖已经存在的记录
如果你想在插入数据时覆盖已经存在的记录,通常有两种常见的方式:
使用数据库的 "INSERT ON DUPLICATE KEY UPDATE"(MySQL)或 "MERGE INTO"(Oracle)等语法:
这些数据库支持的语法可以在插入记录时,如果发现唯一键冲突,就执行更新操作而不是抛出异常。这样,如果已经存在记录,就会更新现有记录,而不是创建新记录。
例如,在 MySQL 中,可以使用以下语法:
INSERT INTO your_table (unique_column, other_column) VALUES (value1, value2) ON DUPLICATE KEY UPDATE other_column = value2;
1
2
3这将尝试插入一条新记录,如果唯一键冲突,就会更新
other_column
的值。使用 MyBatis Plus 提供的
saveOrUpdate
方法:MyBatis Plus 提供了
saveOrUpdate
方法,可以在插入数据时检查是否存在相同唯一值的记录,如果存在则执行更新操作,如果不存在则执行插入操作。你可以像这样使用
saveOrUpdate
方法:faceStatsService.saveOrUpdate(faceStats);
1这将根据数据库中是否存在具有相同唯一值的记录来决定是插入新记录还是更新现有记录。
要注意的是,具体的实现方式可能因你所使用的数据库和 MyBatis Plus 的版本而异。如果你使用的是 MySQL 等支持 "INSERT ON DUPLICATE KEY UPDATE" 语法的数据库,你可以选择使用该语法。如果你希望保持代码的数据库无关性,可以使用 saveOrUpdate
方法。
添加事务
这个功能需要添加成事务,因为保存或更新数据是一个原子操作,如果在这个过程中出现异常,那么之前的数据可能会丢失。为了保证数据的完整性和一致性,建议将这个功能添加成事务。
需要提取设置好主键,在实体类中的唯一字段上添加 TableId
注解
官方文档:CRUD 接口 - saveorupdate (opens new window)
数据库建表为什么必须包含某些字段?怎么设置的?
# 5、消费者组 ID 问题
为什么要使用消费者组 ID,有什么好处?不使用有什么不好?
消费者组 ID 是 Apache Kafka 中用于管理消费者的重要概念,它具有以下好处:
- 并行处理:使用消费者组 ID 可以实现多个消费者并行处理同一个主题的消息。每个消费者都可以处理主题的不同分区,从而提高消息处理的并发性能。
- 负载均衡:Kafka 可以确保同一个消费者组中的每个消费者都获得主题中的一部分分区,从而实现负载均衡。这意味着消息处理在消费者组内均匀分布,不会造成某个消费者过载,而其他消费者处于空闲状态。
- 容错性:如果一个消费者失败或离线,Kafka 会将它的分区重新分配给其他在线的消费者,确保消息不会丢失,同时保持负载均衡。这有助于实现高可用性。
- 水平扩展:通过增加消费者,可以轻松地扩展消息处理能力。只需创建更多的消费者并加入同一个消费者组,Kafka 就会自动分配分区,从而实现水平扩展。
如果不使用消费者组 ID,会导致以下问题:
- 消息重复处理:每个消费者都会独立地处理同一主题的所有消息,可能导致消息的重复处理。
- 无法实现负载均衡:没有消费者组 ID,无法确保不同消费者之间均匀处理消息,因此某些消费者可能会过载,而其他消费者可能处于空闲状态。
- 无法实现高可用性:没有消费者组 ID,无法轻松实现消费者的容错和高可用性,一旦某个消费者失败,消息处理可能会受到影响。
总之,消费者组 ID 是 Kafka 中实现并行、负载均衡、高可用性和水平扩展的关键机制,因此在使用 Kafka 时通常建议使用消费者组 ID。
# 6、执行插入操作但只有可读权限
报错
## Error updating database. Cause: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
## The error may exist in cn/tisson/etouch/mapper/TGa1400DataStatsMapper.java (best guess)
## The error may involve cn.tisson.etouch.mapper.TGa1400DataStatsMapper.insert-Inline
## The error occurred while setting parameters
## SQL: INSERT INTO t_ga1400_data_stats (channel_code, face_received_count) VALUES (?, ?)
## Cause: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed; Connection is read-only. Queries leading to data modification are not allowed; nested exception is java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed -------------------
2
3
4
5
6
连接只读。不允许修改数据的查询。
原因:
接口方法命名不规范,在全局事务管理器中被规定为了只读。
部分 全局事务管理器(TransactionManagerConfig)
代码如下:
Map<String, TransactionAttribute> methodMap = new HashMap<>();
//可以提及事务或回滚事务的方法
methodMap.put("add*", requiredTx);
methodMap.put("save*", requiredTx);
methodMap.put("update*", requiredTx);
methodMap.put("modify*", requiredTx);
methodMap.put("edit*", requiredTx);
methodMap.put("insert*", requiredTx);
methodMap.put("delete*", requiredTx);
methodMap.put("remove*", requiredTx);
methodMap.put("repair*", requiredTx);
methodMap.put("bind*", requiredTx);
methodMap.put("binding*", requiredTx);
methodMap.put("batch*", requiredTx);
methodMap.put("clear*", requiredTx);
methodMap.put("append*", requiredTx);
methodMap.put("create*", requiredTx);
methodMap.put("import*", requiredTx);
methodMap.put("change*", requiredTx);
methodMap.put("mod*", requiredTx);
methodMap.put("equ*", requiredTx);
//其他方法无事务,只读
methodMap.put("*", readOnlyTx);
source.setNameMap(methodMap);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
解决方法:
将接口命名修改成 saveXXX
即可
例如:
// 修改前
void countMessageStats(TGa1400DataStats tGa1400DataStats);
// 修改后
void saveCountMessageStats(TGa1400DataStats tGa1400DataStats);
2
3
4
为什么要编写全局事务管理器?
编写全局事务管理器的主要目的是确保在应用程序中的不同组件之间或不同服务之间的交互操作时,可以保持数据的一致性和完整性。全局事务管理器的存在有以下几个主要原因和优势:
- 维护数据一致性: 当一个操作涉及多个数据源或多个步骤时,如果其中一个步骤失败,可能会导致数据不一致。全局事务管理器可以确保要么所有步骤都成功完成,要么所有步骤都会被回滚,以维护数据的一致性。
- 简化事务管理: 使用全局事务管理器,开发人员可以将事务逻辑从业务逻辑中分离出来,这样业务代码就不需要直接管理事务,使代码更清晰和可维护。
- 处理跨服务事务: 在微服务架构中,一个业务操作可能涉及多个微服务之间的交互。全局事务管理器可以协调不同微服务上的事务,确保它们在整个操作中保持一致。
- 提高可靠性: 通过使用全局事务管理器,可以更容易地处理故障情况。如果某个步骤失败,事务管理器可以协调回滚其他步骤,从而确保系统状态的稳定。
- 支持分布式事务: 在分布式系统中,全局事务管理器可以确保跨多个节点的事务在所有节点上以原子方式执行。
综上所述,全局事务管理器是复杂应用程序和分布式系统中确保数据完整性和一致性的关键组件。它简化了事务管理,提高了系统的可靠性,并支持跨多个服务或节点的事务处理。这就是为什么需要编写全局事务管理器的原因。
为什么要编写统一事务管理器?
编写统一事务管理器是为了解决分布式系统中的分布式事务管理问题。在分布式系统中,事务可能涉及多个不同的服务或资源,这些服务和资源可能分布在不同的机器或节点上,因此需要一种机制来确保事务的一致性和隔离性。
以下是为什么要编写统一事务管理器的一些原因:
- 跨服务事务:在分布式系统中,一个事务可能需要跨越多个不同的服务,每个服务都可能有自己的数据库或资源。统一事务管理器可以协调这些服务之间的事务操作,确保它们在一个事务中要么全部成功,要么全部失败。
- 事务隔离性:事务隔离性是 ACID(原子性、一致性、隔离性、持久性)属性之一,它确保在多个并发事务执行时,一个事务的操作不会受到其他事务的影响。统一事务管理器可以确保在分布式环境下的事务隔离性。
- 分布式数据库事务:如果系统中使用了多个分布式数据库,统一事务管理器可以管理这些数据库之间的事务,确保它们协同工作,以满足事务的一致性要求。
- 事务的补偿机制:在分布式环境中,由于网络故障或其他异常情况,事务可能无法完全成功。统一事务管理器可以实现事务的补偿机制,以处理这些部分失败的事务。
- 可靠消息传递:一些分布式事务场景可能涉及消息队列,而消息队列的可靠性传递也是一个事务问题。统一事务管理器可以与消息队列集成,确保事务消息的可靠传递和处理。
- 容错和回滚:分布式系统中可能会出现各种故障,包括节点故障、网络故障等。统一事务管理器可以实现容错机制,以确保在发生故障时能够正确回滚事务。
总之,统一事务管理器是为了确保分布式系统中的事务操作具有一致性、隔离性和可靠性,以应对分布式环境下的复杂性和挑战。它可以协调和管理跨越多个服务和资源的事务,提供一致的事务处理机制。
# 7、查询时间报错
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException:
## Error querying database. Cause: java.lang.IllegalArgumentException: invalid comparison: java.util.Date and java.lang.String
## Cause: java.lang.IllegalArgumentException: invalid comparison: java.util.Date and java.lang.String
2
3
参数类型错误:无效比较
解决办法:
Date startTime;
// 改成
String startTime;
2
3
# 8、含时间计算的 SQL 语句
计算 现在的时间 和 最大的上传时间 的差
语句报错:"Invalid use of group function"
表示在错误的地方使用了聚合函数(例如 MAX
)或将聚合函数用于不允许的地方。
SELECT
MAX(upload_time) AS lastUploadTime,
DATEDIFF(NOW(), MAX(upload_time)) AS unSentDays
FROM
t_enterprise_market_bills
WHERE
enterprise_id = '1704757410670866434'
AND type = 0
AND DATEDIFF(NOW(), MAX(upload_time)) > 7;
2
3
4
5
6
7
8
9
修正:
# 使用子查询
SELECT
lastUploadTime,
DATEDIFF(NOW(), lastUploadTime) AS unSentDays
FROM
(SELECT
MAX(upload_time) AS lastUploadTime
FROM
t_enterprise_market_bills
WHERE
enterprise_id = '123321') AS subquery
WHERE
DATEDIFF(NOW(), lastUploadTime) > 7;
# 使用Having子句
SELECT
MAX(upload_time) AS lastUploadTime,
DATEDIFF(NOW(), MAX(upload_time)) AS unSentDays
FROM
t_enterprise_market_bills
WHERE
enterprise_id = '123321'
HAVING
DATEDIFF(NOW(), MAX(upload_time)) > 7;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
解决办法:
- 可以使用子查询或使用
HAVING
子句来过滤结果。 HAVING
子句允许在聚合函数上执行过滤条件。
# 9、空指针异常
空对象获取属性时会报空指针异常,所以在获取对象属性时要做好空对象校验。
代码举例:
// 正例
EnterpriseEntity enterprise = enterpriseService.getById(enterpriseId);
Assert.notNull(enterprise, "企业不存在!!");
Date createTime = enterprise.getCreateTime();
// 反例 - 第二行会报空指针异常
EnterpriseEntity enterprise = enterpriseService.getById(enterpriseId);
Date createTime = enterprise.getCreateTime();
Assert.notNull(enterprise, "企业不存在!!");
2
3
4
5
6
7
8
9
# 10、Controller 接收不到参数
错误的写法:
要注意参数的传递方式**(低级错误)**
@PostMapping("/test/list")
public R<ArrayList<TestVO>> getTest(Long id) {
return messageService.getTest(id);
}
2
3
4
正确的写法:
@PostMapping("/test/list")
public R<ArrayList<TestVO>> getTest(@RequestBody IdDTO idDTO) {
return messageService.getTest(idDTO);
}
// 或者
@PostMapping("/test/list")
public R<ArrayList<TestVO>> getTest(@RequestParam("id") Long id) {
return messageService.getTest(id);
}
2
3
4
5
6
7
8
9
10
11
# 11、EasyExcel 的 Date 转换问题
# 12、触发器执行动态 sql 失败
触发器语句:
DELIMITER $$
CREATE TRIGGER update_ai_alarm_status
AFTER UPDATE ON t_ai_alarm_stat FOR EACH ROW
BEGIN
IF NEW.status <> OLD.status THEN
SET @today = DATE_FORMAT(NEW.alert_date, '%Y%m%d');
SET @update_status_sql = CONCAT('UPDATE t_ai_alarm_', @today, '
SET status = NEW.status
WHERE alarm_time = NEW.alert_date
AND org_id = NEW.org_id
AND dev_id = NEW.dev_id
AND channel_id = NEW.channel_id
AND alg_type = NEW.alg_type
AND alert_type = NEW.alert_type');
PREPARE create_stmt FROM @update_status_sql;
EXECUTE create_stmt;
DEALLOCATE PREPARE create_stmt;
END IF;
END$$
DELIMITER ;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
执行报错:
1336 - Dynamic SQL is not allowed in stored function or trigger
MySQL不允许在存储函数或触发器中使用动态SQL
2
相关报错:https://blog.csdn.net/weixin_36369848/article/details/113382888 (opens new window)
解决方法:
在应用程序上实现该功能,即代码层面
首先创建触发器需要超级用户(Super)的权限
# 13、引入测试类失败
报错:
运行 XXApplicationTests.getXXTest 时出错。命令行过长。通过 JAR 清单或通过类路径文件缩短命令行然后重新运行。
缩短命令行后运行报错:
java.lang.IllegalStateException: Failed to load ApplicationContext
原因分析:
连接不上 nacos 服务
# 14、nacos 无法对服务进行上/下线操作
将 nacos 中的 data 目录下的 protocol 文件夹,然后重启 nacos 即可
# 15、SQLSyntaxErrorException 异常
# 报错
java.sql.SQLSyntaxErrorException: Expression #1 of ORDER BY clause is not in GROUP BY clause and contains nonaggregated column 'gu.create_time' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by
# 报错分析
这个错误是由于数据库的 sql_mode
配置为 only_full_group_by
,导致查询中的 ORDER BY 子句与 GROUP BY 子句的列不兼容引起的。
# 错误代码
坐标 XxMapper.xml
GROUP BY
gu.elevator_type,gu.elevator_name,gu.entry_location_record
HAVING
DATEDIFF(NOW(),MAX( gu.maintenance_time )) > 15
order by gu.create_time desc
2
3
4
5
尝试过用 MAX(gu.create_time)
, 但还是报错。
>
是大于号的意思。具体可参考这篇文章:mybatis的特殊符号:&;<;>;";&apos;_mybatis &_会飞的猫不吃鱼的博客-CSDN博客 (opens new window)
# 解决方法
在业务层面上修改代码,进行对数据列表排序,在结果视图 VO 里面写个比较器,然后使用。(也可在业务类利用匿名内部类写比较器)
比较器代码如下:
坐标 XxVO.java
/**
* 按最后一次维保时间 - 降序排序
*/
public static class TimeComparator implements Comparator<OvertimeAlarmVO> {
@Override
public int compare(OvertimeAlarmVO a, OvertimeAlarmVO b) {
return b.getLastTime().compareTo(a.getLastTime());
}
}
2
3
4
5
6
7
8
9
修改使用:
坐标 XxServiceImpl.java
@Service
@Slf4j
public class TestServiceImpl extends BaseServiceImpl<TestMapper, TestEntity>
implements ITestService {
@Override
public Page<OvertimeAlarmVO> overtimeAlarm(OvertimeAlarmReqDTO reqDTO) {
Page<OvertimeAlarmVO> page = new Page<>();
page.setCurrent(reqDTO.getCurrent());
page.setSize(reqDTO.getSize());
List<OvertimeAlarmVO> overtimeAlarmList = this.baseMapper.overtimeAlarm(reqDTO);
overtimeAlarmList.sort(new OvertimeAlarmVO.TimeComparator()); // 使用比较器
int total = overtimeAlarmList.size();
int fromIndex = (reqDTO.getCurrent() - 1) * reqDTO.getSize();
int toIndex = Math.min(fromIndex + reqDTO.getSize(), total);
List<OvertimeAlarmVO> pagedOvertimeAlarmList = overtimeAlarmList.subList(fromIndex, toIndex);
Page<OvertimeAlarmVO> resPage = new Page<>();
resPage.setCurrent(reqDTO.getCurrent());
resPage.setSize(reqDTO.getSize());
resPage.setRecords(pagedOvertimeAlarmList);
resPage.setTotal(total);
return resPage;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 16、错误:Parse Error: Invalid header value char
# 前言
将压缩包写入响应流时报的错。
# 错误代码
String zipName = "压缩包.zip";
try {
response.setContentType("application/zip");
response.setHeader("Content-Disposition", "attachment;filename=" + zipName); // 报错
response.getOutputStream().write(zipStream.toByteArray());
} finally {
zipStream.close();
}
2
3
4
5
6
7
8
9
# 原因分析
- 这个错误通常是由于
zipName
中包含了无效的字符导致的。 - 错误发生在设置响应头(response header)时,具体是在设置
Content-Disposition
头的值时出错。 - HTTP 头的值应该是有效的 ASCII 字符,并且不能包含特殊字符或非 ASCII 字符。根据错误信息,
zipName
中可能包含了一个或多个无效字符,导致无法设置正确的头值。
# 解决方法
对 zipName
进行编码,确保其中的特殊字符被正确处理。你可以使用 URLEncoder
对文件名进行编码,如下所示:
String zipName = "压缩包.zip";
zipName = URLEncoder.encode(zipName, "UTF-8"); // 主要添加这行代码
try {
response.setContentType("application/zip");
response.setHeader("Content-Disposition", "attachment;filename=" + zipName);
response.getOutputStream().write(zipStream.toByteArray());
} finally {
zipStream.close();
}
2
3
4
5
6
7
8
9
10