一个前端工程师,被迫改了37000行Java代码
故事开始
上个月,我接了个需求。
CTO说:”你先研究一下某二手回收公司的后端架构,然后改个接口。”
我说:”好。”
然后他甩给我一个Java后端项目,说:”后端也要改一下,加个失效日志。”
我看着屏幕上25个微服务、37000多个Java文件、LEFT JOIN、INNER JOIN、JOOQ、MQ、定时任务、Feign Client、Redis分布式锁……
每个词我都认识,连起来不知道在干嘛。
第一关:看不懂代码
我打开某二手回收公司的后端代码。
1 | recycle/ ← 回收业务,15个微服务 |
25个微服务,37000个Java文件。
我打开一个Service文件,看到的是:
1 | public class BidServiceConfigService extends GenericServiceImpl<BidServiceConfig, Integer> { |
GenericServiceImpl是什么?DSLContext是什么?AliMQProducter是什么?
我打开了前端项目的Service文件做对比:
1 | // 前端 |
这不是一样的东西吗?
前端:constructor(依赖)
后端:@Autowired 依赖
前端:this.http.get()
后端:dslContext.select().from().where()
只是语法不同,做的事情一样。
第二关:改了第一行代码
我的第一个任务是:给买家通用配置加一个失效日志。
老板说:”现在配置生效有日志,失效没有日志,你加一下。”
我看了代码,发现逻辑是这样的:
1 | 定时任务(每小时扫描) |
我只需要在最后一步加上记日志的代码。
前端思维版
1 | // 如果是前端,我会这么写 |
后端实现
1 | // 我写了这个 |
写完一跑,通过了。
我突然发现:后端开发不就是写函数吗?
第三关:JOIN的血泪教训
接下来,老板让我优化一个方法:
1 | // 旧版:LEFT JOIN + Java判断 |
问题是:LEFT JOIN可能返回null,然后 Objects.equals(null, 2) 返回false。
虽然结果是对的,但逻辑不严谨——没有付款记录 ≠ 不在支付中。
前端思维版
1 | // 前端怎么做的? |
前端是”查出来再判断”,后端可以”让数据库直接告诉我有还是没有”。
优化后的版本
1 | // 新版:JOIN + EXISTS |
改动:
- LEFT JOIN → JOIN(没有付款记录的直接过滤掉)
- Java层判断 → SQL层过滤(STATUS=PAYING直接写在WHERE里)
- 返回Integer再判断 → 直接返回true/false
为什么这么改?
1 | LEFT JOIN:像Excel的VLOOKUP,找不到就返回空 |
第四关:SQL也不会写了
我写了十年前端,SELECT * FROM 都快忘了。
JOOQ是什么鬼?
1 | dao.create() |
我看了半天,突然反应过来:
这不就是用Java写SQL吗?
1 | SELECT * FROM bid_service_config WHERE id = ? |
JOOQ = 类型安全的SQL构建器。
select(fields())→SELECT *from(TABLE)→FROM 表where(COLUMN.eq(value))→WHERE 列 = ?fetchOneInto(Class)→ 返回一个对象
为什么用JOOQ不用MyBatis?
1 | MyBatis:写XML,SQL和Java分开 |
前端类比:
1 | // MyBatis = 写SQL字符串(像内联样式) |
第五关:其实已经会了
一个月后,我回头看自己改过的代码:
1 | ✅ 新建了定时任务 Job(每小时扫描过期配置) |
这些都是后端开发。
我突然发现:我不是”不会写后端”,我是”不熟后端语法”。
前端→后端对照表
这是我总结的对照表,帮自己理解:
| 前端(JS/TS) | 后端(Java) | 实际代码 |
|---|---|---|
list.filter(x => x.id > 0) |
stream().filter(x -> x.getId() > 0) |
✅ PdProductRefundServiceImpl |
list.map(x => x.name) |
stream().map(X::getName) |
✅ OperationManagementService |
obj?.name ?? '默认' |
StringUtils.defaultIfBlank(obj.getName(), "默认") |
✅ MlGoodsSkuPriceConfigService |
if (obj == null) |
if (Objects.isNull(obj)) |
✅ BidServiceConfigConsumer |
axios.post('/api', data) |
@PostMapping + @RequestBody |
✅ 所有Controller |
const { id, name } = obj |
obj.getId(), obj.getName() |
✅ 到处都在用 |
try { } catch(e) {} |
try { } catch(Exception e) {} |
✅ 各种Service |
VLOOKUP找不到返回空 |
LEFT JOIN找不到返回null |
✅ 我踩的坑 |
Array.filter().length > 0 |
stream().anyMatch() |
✅ BidServiceConfigService |
后端就是前端换了个语法。
我学到的几件事
1. 全栈不是”什么都会”,是”需要什么学什么”
我不需要学完Java所有语法,我只需要知道:
- 怎么接收请求(Controller)
- 怎么写业务逻辑(Service)
- 怎么查数据库(JOOQ/MyBatis)
- 怎么发异步消息(MQ)
- 怎么写定时任务(Job)
其他的东西,遇到了再学。
2. 前端思维在后端也适用
1 | 前端:组件 → 状态 → 渲染 |
3. 最大的障碍不是技术,是心理
我看到37000个Java文件时,第一反应是:”我不会。”
但真正开始改代码后,发现:
- 改一个方法,就改一个方法
- 加一个日志,就加一行代码
- 优化一个SQL,就改几个JOIN
都是小事,一件一件做,就做完了。
4. 给自己三个月
如果我现在问自己:”三个月前你会写Java后端吗?”
答案是:不会。
“现在呢?”
会一点。
会写定时任务、会改MQ Consumer、会优化SQL、会用JOOQ查数据库。
不是”精通”,是”能用”。
这就够了。
写在最后
这篇文章不是”前端转全栈教程”,是“一个前端工程师的真实经历”。
我没有系统学过Java,没有看过《Java核心技术》,没有刷过LeetCode。
我就是接了个需求,被迫改代码,遇到问题查资料,改完了发现”其实我也会”。
如果你也是前端,也在考虑要不要搞后端,我的建议是:
别想”我能不能学会”,想”我需不需要用”。
需要用,就学。
用到了,就会了。
会了,就继续用。
三个月后,你也会说:”其实我也会写后端。”
本文基于作者2026年6-7月的真实项目经历,代码来自某二手回收公司后端项目(已脱敏)。
下一篇预告:《前端转全栈:我需要的不是学Java,是这张地图》——后端技术全景图,一张图看懂后端有哪些东西。