这些年,我写过的BUG(二)
时间:2022-07-22
本文章向大家介绍这些年,我写过的BUG(二),主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
❝BUG是最好的学习素材。❞
最近的「BUG」都不疼不痒,基本秒修复。昨天遇到一个大坑,修复了好几个小时。这是一个事务挂起导致数据库连接未释放,然后导致获取数据库连接失败的「BUG」。
场景
运行测试用例集(包含多个测试用例),处理逻辑如下:1、首先去并发处理用例参数,例如关联用户的登录状态(这个比较麻烦,请参考旧文内容:我的开发日记(十五)中的分布式锁的实现);2、把用例组装成多线程任务,丢到线程池去执行;3、异步等待所有用例执行完成,处理数据,异步写入数据库。
BUG代码
/**
* 获取用户登录凭据,map缓存
*
* @param id
* @param map
* @return
*/
@Override
@Transactional(isolation = Isolation.REPEATABLE_READ, propagation = Propagation.REQUIRES_NEW)
public String getCertificate(int id, ConcurrentHashMap<Integer, String> map) {
if (map.contains(id)) return map.get(id);
Object o = UserLock.get(id);
synchronized (o) {
if (map.contains(id)) return map.get(id);
logger.warn("非缓存读取用户数据{}", id);
TestUserCheckBean user = testUserMapper.findUser(id);
if (user == null) UserStatusException.fail("用户不存在,ID:" + id);
String create_time = user.getCreate_time();
long create = Time.getTimestamp(create_time);
long now = Time.getTimeStamp();
if (now - create < OkayConstant.CERTIFICATE_TIMEOUT && user.getStatus() == UserState.OK.getCode()) {
map.put(id, user.getCertificate());
return user.getCertificate();
}
boolean b = UserUtil.checkUserLoginStatus(user);
if (!b) {
updateUserStatus(user);
if (user.getStatus() != UserState.OK.getCode()) UserStatusException.fail("用户不可用,ID:" + id);
} else {
testUserMapper.updateUserStatus(user);
}
map.put(id, user.getCertificate());
return user.getCertificate();
}
}
BUG分析
这里犯了两个错误:
判断key方法错误
应该使用map.containsKey(id)
来判断,而不是map.contains(id)
,可以看一下map.contains(id)
的源码:
/**
* Legacy method testing if some key maps into the specified value
* in this table. This method is identical in functionality to
* {@link #containsValue(Object)}, and exists solely to ensure
* full compatibility with class {@link java.util.Hashtable},
* which supported this method prior to introduction of the
* Java Collections framework.
*
* @param value a value to search for
* @return {@code true} if and only if some key maps to the
* {@code value} argument in this table as
* determined by the {@code equals} method;
* {@code false} otherwise
* @throws NullPointerException if the specified value is null
*/
public boolean contains(Object value) {
return containsValue(value);
}
其实map.contains(id)
查的「value」而不是「key」,导致很多多余的查询和其他操作。
事务传播行为
具体知识点参考旧文:我的开发日记(三)中对于「事务隔离级别」和「事务传播行为」的记录。
这里的REQUIRES_NEW
表示REQUIRES_NEW :创建一个新的事务,如果当前存在事务,则把当前事务挂起。
每一个事务都会占用一个连接,然后会把之前的事务挂起等待,这样就导致会占用很多数据库连接而不释放。再加上本身有很多读写数据库的操作,所以导致了下面的报错:
2020-07-29 10:27:50 ERROR com.okay.family.service.impl.CaseCollectionServiceImpl:287 [] [Thread-176] 处理用例参数发生错误!
org.springframework.transaction.CannotCreateTransactionException: Could not open JDBC Connection for transaction; nested exception is java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 30006ms.
at org.springframework.jdbc.datasource.DataSourceTransactionManager.doBegin(DataSourceTransactionManager.java:308)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.startTransaction(AbstractPlatformTransactionManager.java:400)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:373)
at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:572)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:360)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:118)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
at com.sun.proxy.$Proxy82.handleParams(Unknown Source)
at com.okay.family.service.impl.CaseCollectionServiceImpl.lambda$null$4(CaseCollectionServiceImpl.java:282)
解决办法
调整事务传播行为
删除REQUIRES_NEW
设置,恢复默认值。
设置超时时间
数据库连接池获取连接超时时间设置:
spring.datasource.hikari.connection-timeout=3000
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Azure认知服务之使用墨迹识别功能识别手写汉字
- 基于 TypeScript 的 Weex 优化实践
- R语言数据库中如何多条件排序
- 用好 Java 中的枚举,真的没有那么简单!
- ABAP整型类型的几种位操作 - OR, AND, XOR的
- 【机器学习】算法原理详细推导与实现(七):决策树算法
- 如何使用代码修改SAP CRM One Order CUMULAT_H对象的值
- Caffeine Cache~高性能 Java 本地缓存之王
- 用Python的Pandas和Matplotlib绘制股票唐奇安通道,布林带通道和鳄鱼组线
- Java的Covariance设计原理和SAP ABAP的模拟实现
- ABAP和Java的tag(marker) interface
- setTimeout 是到了xx ms 就执行吗,了解浏览器的 Event-Loop 机制
- 使用ABAP的RTTI和Java反射机制访问static private属性
- ABAP面试问题 - 不使用加减乘除等操作比较两个整数大小
- SAP订单上Shipping抬头和行项目字段的持久化实现原理