一个SQL错误的问题让我找到了公司框架中三个bug,2024年最新java面试中经常被问到的问题

复现问题

====

这个问题简单的来说就是SQL中的字段发生了重复, Column ‘update_time’ specified twice。其实不止update_time字段发生了重复,是很多字段都发生了重复。因为这个接口除了并发高一点之外,和其他的接口没有什么特别之处!突然我脑子里灵光一闪,难道是这个接口并发较高,导致框架在生成INSERT类型的SQL发生了错误???要是这样的话,我直接创造一个这样的场景不就可以实现了嘛!然后我写下了下面的代码:

public void test(){

for (int i = 0; i < 1000; i++) {

ExceptionLogEntity exceptionLogEntity = new ExceptionLogEntity();

exceptionLogEntity.setXX(“”);

exceptionLogEntity.setXXX(“”);

exceptionLogEntity.setIp(“”);

exceptionLogEntity.setUrl(“”);

exceptionLogEntity.setBusinessScene(“”);

exceptionLogEntity.setExceptionType(“”);

exceptionLogEntity.setExceptionDetailType(“”);

exceptionLogEntity.setExceptionMessage(“”);

exceptionLogEntity.setNoticeStatus(“”);

exceptionLogEntity.setCreateTime(new Date());

exceptionLogEntity.setUpdateTime(new Date());

Thread thread = new Thread(() -> {

exceptionLogDao.insert(exceptionLogEntity);

});

thread.start();

}

}

代码写完直接重启项目,最后一触发上面的代码。果然,同样的错误又出现了。这里我先介绍下使用的ORM框架:

此ORM框架是集团O2O研发中心研发的基础组件,提供同Mybatis一样的功能:只需定义接口,无需写实现类。此外,对单表操作还封装了一套常用的增删改查处理,连接口也无需再定义,对开发人员非常友好,大大降低了冗长的jdbc操作代码量,提升了开发效率。

我问了问旁边的同事,在使用公司的ORM框架自带的INSERT方法时,有没有遇到SQL列重复的问题吗?嘿你还别说,大家异口同声说遇到过。那厉害了,看来这真的不是个别现象,真的是框架的bug。于是乎我花了一点时间来找找看到底是在哪个地方出现了问题,皇天不负有心人我还真的找到了,具体的代码如下:

public List getDalColumnsWithoutId() {

if (this.columnsWithoutId != null) {

return this.columnsWithoutId;

} else {

this.columnsWithoutId = new ArrayList(this.dalColumns.size() - 1);

Iterator var1 = this.dalColumns.iterator();

while(var1.hasNext()) {

DalColumn column = (DalColumn)var1.next();

if (!column.isIdColumn()) {

this.columnsWithoutId.add(column);

}

}

return this.columnsWithoutId;

}

}

问题就出在 columnsWithoutId 上,它的类型是 List ,是所在类的一个属性默认为null,保存的是一个表除主键ID之外的所有字段。上面的代码在单线程情况下没有问题,但是在多线程的情况下就问题大了。 columnsWithoutId 在多线程情况下就是一个 线程共享变量 ,假设在某个时机有多个线程恰好执行到 if 代码块,发现 columnsWithoutId 的值为null,则就会同时执行 else代码块,那这时就会执行多次循环,自然 columnsWithoutId 中的字段就会出现了重复。

解决问题

====

这个问题已经找到,那接下来就差一个解决方案了。

官方修复

====

我当时想既然这个框架是公司研发中心提供的基础组件,那就是说明在公司内部会有很多项目组使用,问题说不定早就被修复了。你还别说在公司的wiki上我还真的找到了,有位负责这个组件的大佬在wiki上分享了”单表插入偶发列重复问题定位与解决”的文章。(文章地址我就不发了,公司内部地址发出来你们也访问不了)文章里详细的描述了问题发生的场景(和我上面描述的差不多)、问题定位过程还有解决方案。摘选文章上的解决方案:

明确了是线程安全的问题,解决方案就好确定了,解决线程安全有三种方案:

方案一:互斥同步——synchronized,ReentrantLock

方案二:非阻塞同步——CAS

方案三:无同步方案:——ThreadLocal

结合业务场景,综合分析,最终确认使用方案一,采用synchronized关键字,使用双重检查锁的方式解决此问题。

大佬已经将bug修复并发布了新版本到Maven仓库,项目中有发生上述问题的,直接升级对应的组件版本即可。既然官方已经给出了解决方案,我直接升级项目中的组件版本号就行了。结果我一更换项目中的版本,我的项目就再也没有启动起来。研究发现,组件版本相差太多,很多依赖发生了冲突。我项目中使用的是 1.1.6-RELEASE ,问题官方在版本 2.1.0-RELEASE 中修复。这组件版本差距太大贸然升级肯定是有很大的风险了,于是我放弃了!

另辟蹊径解决

======

既然你自带的插入方法有问题,我直接自定义一个插入方法不就得了。惹不起我还躲不起么!我最后又问了同事们最后都是怎么解决的,大家也都说是自定义SQL,而且都不再用框架自带的方法了,大家都很聪明呀!(其实这个还有后续,后面有一期开发过程中,需要用到批量插入的功能,我又偷懒用了框架自带的batch方法,又把我给坑了。从那之后,我再也没有用自带的方法了……)

问题2

===

我在排查上面的问题的时候,在框架里面看到了下面的代码:

private Number execute4PrimaryKey(String sqlId, Map<String, Object> paramMap, KeyHolder keyHolder) {

long startTimestamp = System.currentTimeMillis();

String sql = null;

Object var8;

try {

MappedStatement mappedStatement = this.configuration.getMappedStatement(sqlId, true);

mappedStmtThreadLocal.set(mappedStatement);

sql = mappedStatement.getBoundSql(paramMap);

int result = false;

int result;

if (keyHolder != null) {

this.execution.update(sql, new MapSqlParameterSource(DalUtils.mapIfNull(paramMap)), keyHolder);

result = keyHolder.getKey() == null ? 0 : keyHolder.getKey().intValue();

} else {

result = this.execution.update(sql, DalUtils.mapIfNull(paramMap));

}

Integer var9 = result;

return var9;

} catch (Exception var13) {

this.throwException(var13);

var8 = null;

} finally {

mappedStmtThreadLocal.remove();

logger.debug(“{} method:{}, sql:{}, param:{}”, new Object[]{this.logPrefix, “execute”, sql, paramMap});

this.logProfileLongTimeRunningSql(startTimestamp, sql, paramMap);

}

return (Number)var8;

}

大家仔细看看,这上面的代码有个不容易发现的问题。问题是这样的,我们系统中表的主键类型定义的都是 bigint ,这个类型对应的是Java中的 Long 类型,说到这大家明白了么?对了,就是数据溢出的问题。

复现问题

====

我将表中的主键自增量设置为Java Int类型的最大值+1,也就是2147483648。然后数据库再新增一条记录,此时表中记录的主键是2147483648,但Java实体中的主键ID已经是个负数-2147483648了。

使用Long类型时,要注意JavaScript接收后端Long类型数据精度丢失问题

解决问题

====

这个问题还真不好另辟蹊径了,只能后面出现类似的问题,升级ORM框架的版本了。

问题3

===

其实这个问题我应该在看那篇内部文章的时候就应该发现的,但我当时就是没有看出来!此时的我留下了没有技术的泪水。对于问题1,当时那位大佬最终采用 synchronized 进行加锁解决多线程情况下共享变量读写问题,具体代码如下(敏感信息打码):

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
img

总结

对于面试还是要好好准备的,尤其是有些问题还是很容易挖坑的,例如你为什么离开现在的公司(你当然不应该抱怨现在的公司有哪些不好的地方,更多的应该表明自己想要寻找更好的发展机会,自己的一些现实因素,比如对于我而言是现在应聘的公司离自己的家更近,又或者是自己工作到达了迷茫期,想跳出迷茫期等等)

image

Java面试精选题、架构实战文档

整理不易,觉得有帮助的朋友可以帮忙点赞分享支持一下小编~

你的支持,我的动力;祝各位前程似锦,offer不断!

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
img

实因素,比如对于我而言是现在应聘的公司离自己的家更近,又或者是自己工作到达了迷茫期,想跳出迷茫期等等)

[外链图片转存中…(img-DPRAsk3N-1712798722892)]

Java面试精选题、架构实战文档

整理不易,觉得有帮助的朋友可以帮忙点赞分享支持一下小编~

你的支持,我的动力;祝各位前程似锦,offer不断!

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-vO6fg7DL-1712798722892)]

  • 22
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值