找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2239

积分

0

好友

293

主题
发表于 19 小时前 | 查看: 1| 回复: 0

技术可行性警示图:红绿灯隐喻“可行”与“不可行”

从纯技术实现的角度来看,答案是“能跑”。但从工程实践和软件架构设计的角度出发,结论通常是“不该”

问题的核心不在于“能不能”,而在于你打算将业务复杂度和变化隔离在哪一层

一、为什么会想直接调用Mapper?

先看一段典型的代码:

@GetMapping("/user/{id}")
public User getUser(@PathVariable Long id) {
    return userMapper.selectById(id);
}

这种写法的诱惑力非常直接:少一层

  • 少写一个 Service 接口
  • 少写一个 Service 实现类
  • 对于简单的 CRUD,代码看起来干净利落

在以下场景中,这种做法似乎“没什么问题”:

  • 纯粹的单表查询(CRUD)
  • 个人学习项目或快速原型
  • 功能极其简单的 Demo

因此,许多项目在初期阶段都采用了这种模式。然而,隐患就此埋下:随着业务演进,这种架构几乎必然导致代码失控。

二、直接调用Mapper,哪些问题会接踵而至?

1. 业务逻辑膨胀时,缺乏承载层

一旦业务需求不再满足于简单的查询,例如需要:

  • 参数校验(非空、格式、业务规则)
  • 权限判断(用户是否有权访问此数据)
  • 状态流转(如用户从“激活”变为“禁用”)
  • 涉及多张表的操作

你的 Controller 很快就会变成这样:

User user = userMapper.selectById(id);
if (user == null) {
    throw new BizException("用户不存在");
}
if (!user.isEnable()) {
    throw new BizException("用户已禁用");
}
userMapper.updateStatus(id);
// ... 可能还有更多操作

此时,Controller 已经承担了本应由 Service 层处理的复杂业务逻辑,而真正的 Service 层却缺失了。 这严重破坏了经典三层架构(Controller-Service-DAO)的职责边界。

2. 事务(Transaction)无处安放

Java开发,特别是Spring生态中,事务管理通常使用 @Transactional 注解。那么,事务该放在哪里?

@Transactional // 事务注解放在Controller上
@GetMapping("/user/update")
public void update() {
    // 涉及多个Mapper调用的业务操作
}

技术上,Spring 允许你这样做。但这是一种非常糟糕的设计

  • 职责错位:Controller 作为接口层,本应关注请求/响应的协调,却承担了业务事务的语义。
  • 耦合严重:HTTP 接口与具体业务的事务边界强绑定,不利于复用和测试。
  • 难以拆分:如果未来需要将这部分业务逻辑迁移到消息队列消费者或定时任务中,会异常困难。

事务的天然归属地是 Service 层,因为它封装了具有完整业务语义的操作单元。

3. Mapper 的复用价值被扼杀

一旦 Controller 直接依赖 Mapper,这个 Mapper 方法就与特定的 HTTP 接口深度绑定了。

  • 其他 Service 想复用这段数据访问逻辑?不敢用,因为逻辑散落在 Controller 里。
  • 定时任务或消息消费者需要同样的数据操作?是直接复制代码,还是也去调 Mapper?无论哪种,都会导致逻辑重复或层级混乱。

最终,Mapper 沦为了某个特定接口的“私有 DAO”,失去了在数据库操作层进行抽象和复用的价值。

一个蓝白配色的品牌或吉祥物Logo

三、是否存在可以直接调用的例外情况?

存在,但条件极为苛刻:仅限于完全没有业务语义的、只读的简单查询。

例如:

  • 查询静态字典表
  • 获取系统配置项
  • 获取无需任何加工的基础数据

并且必须同时满足以下前提:

  • 无事务要求:仅仅是查询。
  • 无状态判断:数据拿来即用,无需检查业务状态。
  • 几乎无扩展预期:可以预见未来也不会增加复杂逻辑。

即便如此,从设计一致性和未来可维护性出发,也更推荐你哪怕只是包装一层:

// 在Service中
public Dict getDictByCode(String code) {
    return dictMapper.selectByCode(code); // 依然只是一行代码
}

然后用 service.getDictByCode() 替代直接的 mapper 调用。这一行代码的 Service 方法,为未来的校验、缓存、日志等需求预留了空间。

核心总结:Service 层的存在,不是为了机械地“多一层”,而是为了战略性地“隔离变化”。 它将易变的业务逻辑、事务边界与相对稳定的接口层(Controller)和数据访问层(Mapper)解耦,是构建可维护、可扩展后端应用的关键设计。

一个开心的卡通形象,表达积极肯定的情绪

希望这篇分析能帮助你更好地理解分层架构的意义。更多关于架构设计和技术实践的讨论,欢迎在云栈社区与大家交流。




上一篇:碳积分交易详解:双积分政策如何推动电车盈利与油车转型
下一篇:使用Vibe Coding在30分钟内快速搭建FreeSWITCH管理界面
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-18 19:46 , Processed in 0.217442 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表