数据库永远是你最后一道防线但千万别把它当成第一道防线。我维护过一个跑了三年的老系统最深的体会是所有能在代码层解决的问题都不该抛给数据库去扛。比如分页查询很多人习惯直接count()再查列表可当数据量涨到千万级这条SQL能把连接池拖到打喷嚏。后来我改成先查ID再用ID批量查详情响应时间从800ms降到120ms。这不是技巧是态度。别让实体类变成“万能口袋”刚开始写后台我喜欢设计一个超级Entity字段从用户ID一直堆到最后登录IP恨不得把整张表所有列都塞进去。前端要什么我就从里面取什么。结果就是每次需求变更实体类动一下全链路跟着重新编译。后来我痛定思痛实体类的职责边界必须清晰一个类只对应一种业务语义查询VO、更新DTO、持久化PO各管各的。哪怕多写几个类换来的是改动隔离。有人觉得这样麻烦可真正麻烦的是上线后你发现一个字段的改动影响了十几个接口。后台系统的维护成本多半不是加功能加出来的而是改共用代码改出来的。我现在写代码前会先问自己这个字段是给谁用的谁允许改它如果答案是“都有可能”那它就不该放在公共实体里。事务要么短要么死事务是后台系统最容易踩的坑。我曾经写过一个批量导入功能一口气读五千条数据每条都开一个事务插入跑完花了四十分钟。后来把整个导入包进一个事务速度快了可一旦中途有脏数据全部回滚用户得重来。事务的粒度不是越小越好也不是越大越好而是短到刚好能保证一致性。真正的经验是把耗时操作IO、远程调用、复杂计算挪到事务外面事务里只做写库和必要的校验。比如导入场景先解析文件、清洗数据、做格式校验最后开一个事务批量插入。如果某条失败记录错误行号而不是整体回滚。用“局部失败”代替“全局原子”在后台系统里往往是更优雅的取舍。还有一点别在事务里调用外部接口。一次调用就算只超时三秒事务就悬空三秒锁就多占三秒并发稍微一高死锁就来了。异常不是用来“吞”的我见过不少同事喜欢catch(Exception e)然后log.error一下代码继续跑。表面上看系统稳定了实际上数据早就错乱了。异常是业务的一部分不是系统的噪音。比如扣库存库存不足抛个BizException前端可以明确提示可如果你把异常吞掉用户看到“成功”后台库存却变成负数这比报错可怕一百倍。我的习惯是自定义统一异常携带错误码和上下文信息。在Controller层用全局异常处理器兜底返回统一格式。任何异常都要有“出口”要么转成业务提示要么触发熔断要么记录审计日志。一个没有异常处理策略的后台系统就像没有应急预案的消防队平时看不出问题出事了就是大事。日志是系统的“黑匣子”不是“碎纸机”每次排查线上问题我最怕看到两种日志一种是几乎没有日志只知道报错了不知道在哪另一种是全屏打印从SQL到HTTP头全输出但全是一堆无意义的debug。有意义的日志应该能还原一次请求的完整链路谁调的、参数是什么、报了什么错、耗时多少。我开发时会在关键节点打点比如接口入口打印请求参数注意脱敏出口打印响应码和耗时在service层打印业务状态变更在调用外部服务时打印请求和响应摘要。上线后把这些日志接入监控平台按traceId关联。真正省时间的不是写代码的速度而是排错的速度。一个能三分钟定位问题的日志体系比多写一千行代码都有价值。缓存是止痛药不是营养品Redis用得好能飞用不好会摔。我早期做缓存喜欢“先删缓存再更新数据库”结果并发下缓存击穿数据库被打爆。后来改成“先更新数据库再删除缓存”配合短暂过期问题解决了大半。但缓存最大的坑不在这儿而在一致性。缓存永远无法和数据库强一致能做的只是降低不一致的概率和窗口。所以我会给缓存设置合理的过期时间热点数据用双缓存策略写操作通过消息队列异步刷新。更要命的是缓存穿透——一个不存在的key每次请求都穿过缓存直击数据库。解决方案是布隆过滤器或缓存空值。记住缓存是用来挡子弹的防弹衣不是藏污纳垢的仓库。如果业务数据变更频繁写并发高那干脆别用缓存。异步化别让后台系统成为“人形同步工具”后台系统里很多操作根本不需要同步等结果。比如发送通知、更新统计报表、生成历史快照。早期我写代码喜欢线性执行先做A再做B再通知C客户端等到全部完成才返回。用户体验差系统吞吐量也低。后台系统的核心能力之一就是把“必须立即响应”和“可以延后处理”的逻辑拆开。我用Spring的Async加上自定义线程池把那些不重要的任务丢到后台慢慢跑。但线程池必须隔离写操作一个池读操作一个池异步任务又一个池。否则一个慢任务把线程池占满核心接口全部阻塞。异步化不是把所有东西都扔出去而是给不同的任性任务划分车道。另外别忘了幂等性——异步任务可能重复执行接口要做好重复消费的防护。配置永远比编码灵活线上问题一半以上是配置不当引起的。连接池大小、超时时间、重试次数、开关阈值这些参数如果能硬编码那系统就失去了自我调节的能力。我现在会将关键参数全部外置到配置中心用ConfigurationProperties绑定到配置类。任何环境差异都应该由配置来消化而不是靠程序员写if-else。有一回线上应用频繁超时排查半天发现是连接池默认最大活跃数只有10一个高峰期就满了。当时如果配置在代码里还得发版配置化之后直接改配置中心优雅地扩容。配置管理不是锦上添花而是后台系统的呼吸管道。前后端分离不是“甩锅”开始后台系统开发中接口设计经常成为前后端矛盾的焦点。我踩过的坑是为了“灵活”接口返回MapString, Object前端爱取什么取什么。结果前端换个人没人知道Map里到底有哪些键。接口的首要义务是稳定和自描述而不是迁就调用方的随意。我更习惯约定明确的DTO字段类型、命名、是否必填全部体现在API文档里。还有接口版本管理。曾经有个系统没做版本前端改了字段名后端改了返回结构上线后互相甩锅。后来每条接口都带上版本号旧版本保留一段时间兼容期过后再下线。后台系统的优雅迭代靠的是契约精神既约束自己也约束调用方。监控不是“事后诸葛”很多系统都是出了问题才去查日志被用户投诉才想起看监控。真正的监控应该是预防性的。你要能提前看到连接池使用率逼近80%看到慢SQL数量曲线在上升看到内存消耗在稳步爬坡。这些指标不会立即让系统挂掉但预警能给你抢出时间。我开发时会用Micrometer加Prometheus把关键指标暴露出来QPS、RT、错误率、JVM内存、线程池活跃数、数据库连接数。再配上告警规则比如成功率低于99.5%就通知。上线不是终点监控才是运维的起点。后台系统的强者不是不犯错而是在错误发生前就看到了征兆。代码评审不只是“找茬”独自写代码时很容易陷入“自己的逻辑自己信”的怪圈。代码评审不是为了打压谁而是借助他人的视角发现盲区。我经历过一次痛苦的代码评审我写的批量删除接口没考虑子表外键关联评审时被指出来当时觉得小题大做。结果上线一个月后真有客户删除主数据关系表里残留了一堆孤儿记录。代码评审查出的每一个问题都是一次生产事故的预演。我更看重评审时的“可读性”变量名是否清晰方法是否足够短分支是否复杂到需要注释。能让人一眼看懂的代码往往比花哨的设计更可靠。所以每次提交MR我都会自己先当一次reviewer从“一个月后接手的人”的角度审视自己的代码。回归测试是最便宜的保险后台系统改动频繁往往改一个接口牵动十几个调用方。如果每次只是“自测通过”就完事迟早会在不痛不痒的地方翻车。我的经验是为核心业务路径写自动化回归测试重点关注状态流转、金额计算、权限控制这三类易错逻辑。测试不是开发之后的仪式而是开发过程中的一个环节。我会在写代码前先想好测试用例至少覆盖正常路径、边界路径、异常路径。但也不必盲目追求覆盖率把Controller、Service、Repository层层都测一遍成本太高。用20%的测试代码覆盖80%的崩溃风险剩下的交给监控和日志。最后代码给你写的其实是“未来的自己”每写一行Java代码要想的不是今天能跑通而是半年后的人能不能看懂、半年后改需求时能不能不动筋动骨。我见过太多烂代码一眼看过去全是技巧却没有一丝克制。精彩的后台系统不是炫技的舞台而是稳健的基座。它要经得起流量冲击扛得住需求变化还得让后来者能轻松接手。技术永远在更新框架一年换一个但经验的沉淀是恒久的。你可以不追新版本但不能没有自己的判断标准。我衡量一段代码的好坏只有一个朴素的标准如果明天这个模块出了问题是否能在半小时内定位到根因。做不到就是积累还不够。后台系统开发是一场没有终点的马拉松摔过的坑、调优过的SQL、救过的线上事故都会变成你气定神闲的资本。哪怕只是积累了一点点经验也值得认真对待每一次设计、每一行代码、每一个不眠夜。这就是我为什么热爱Java后台开发的原因——它枯燥但足够真实。