news 2026/1/26 12:55:54

JavaEE进阶-SpringBoot三层架构:餐厅模式解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JavaEE进阶-SpringBoot三层架构:餐厅模式解析

目录

核心架构:数据流转的“流水线”

1. Entity (实体类) —— com.entity

2. Controller (控制层) —— com.controller

3. Service (业务层) —— com.service

4. Dao (持久层) —— com.dao

辅助架构:让系统更好用的“后勤部”

5. Config (配置类) —— com.config

6. Interceptor (拦截器) —— com.interceptor

7. Utils (工具类) —— com.utils

8. Handler (异常处理) —— com.handler

总结:一个请求的完整一生

我还是不明白,实体类到底是干嘛的,我们不是已经有mybatis了吗?这里存在的作用是啥?

1. 核心逻辑:ORM(对象-关系映射)

2. 生活比喻:快递员与快递盒

3. 代码实战:如果没有实体类会发生什么?

情况 A:有实体类(现在的主流写法)

情况 B:没有实体类(硬要用 MyBatis)

总结

dao层和resources/mapper 下的 XML 文件到底是怎么配合使用的?不理解,写个伪代码举例说明一下。

1. 核心概念:遥控器与指令

2. 伪代码举例:它们是如何“暗送秋波”的

A. 遥控器:UserDao.java (位于 com.dao 包)

B. 指令手册:UserMapper.xml (位于 resources/mapper)

3. 图解配合流程

4. 总结:三个关键匹配


结合你提供的 Spring Boot 项目结构图,这其实是一个非常标准的MVC 三层架构变体。为了让你深刻理解,我依然沿用“餐厅”的比喻,但会结合你图中的具体文件夹名称,用伪代码(即不纠结语法,只看逻辑的代码)来演示一个完整的**“用户注册”**流程。


核心架构:数据流转的“流水线”

数据就像是餐厅里的食材,在不同的层级之间被传递、加工、存储。

1. Entity (实体类) ——com.entity
  • 图片标注:“一张表对应一个实体类”。

  • 本质数据的模具/载体。它不干活,只负责装数据。

  • 比喻:盘子、菜单上的菜品介绍。

  • 伪代码

    Java
    // 这是一个模具,对应数据库里的 User 表 class User { int id; // 对应表里的 id 字段 String name; // 对应表里的 name 字段 String pwd; // 对应表里的 password 字段 }
2. Controller (控制层) ——com.controller
  • 本质前台接待/服务员

  • 职责

    1. 对外暴露接口(URL)。

    2. 接收前端传来的参数(拿菜单)。

    3. 做简单的检查(必填项填没填)。

    4. 指挥 Service 干活(绝不自己动手做菜)。

    5. 给前端返回结果(上菜)。

  • 伪代码

    Java
    class UserController { // 注入大厨 Service service; // 对外暴露的接口:/register func register(String name, String pwd) { // 1. 接待检查:名字都没填?直接打回去! if (name is empty) { return "注册失败:名字不能为空"; } // 2. 甩手掌柜:交给大厨去处理 boolean success = service.registerUser(name, pwd); // 3. 反馈结果 if (success) return "注册成功"; else return "注册失败"; } }
3. Service (业务层) ——com.service
  • 本质大厨/业务经理

  • 职责

    1. 核心逻辑都在这(判断用户存不存在、密码加密、算优惠券等)。

    2. 指挥 Dao 层存取数据。

    3. 事务控制(保证一系列操作要么全成功,要么全失败)。

  • 伪代码

    Java
    class UserService { // 注入仓库管理员 Dao dao; func registerUser(String name, String pwd) { // 1. 业务逻辑判断:这人是不是已经注册过了? User existUser = dao.findByName(name); if (existUser != null) { return false; // 用户已存在,不能注册 } // 2. 业务加工:密码不能明文存,要加密 String safePwd = MD5(pwd); // 3. 组装数据 User newUser = new User(); newUser.name = name; newUser.pwd = safePwd; // 4. 指挥仓库管理员入库 dao.save(newUser); return true; } }
4. Dao (持久层) ——com.dao
  • 关联:通常与resources/mapper下的 XML 文件配合使用。

  • 本质仓库管理员/采购员

  • 职责

    1. 哪怕 Service 逻辑再复杂,Dao 层也只听简单的指令:查、存、改、删。

    2. 直接和数据库(Database)对话。

  • 伪代码

    Java
    interface UserDao { // 只定义接口,具体 SQL 写在 resources/mapper/UserMapper.xml 里 // 指令1:根据名字找人 // SQL: SELECT * FROM user WHERE name = ? func findByName(String name); // 指令2:保存用户 // SQL: INSERT INTO user (name, pwd) VALUES (?, ?) func save(User user); }

辅助架构:让系统更好用的“后勤部”

你图片中还有一些非常重要的辅助层级,它们保证了核心业务的稳定运行。

5. Config (配置类) ——com.config
  • 本质装修队/规则制定者

  • 职责:项目启动时,设定好各种规则。比如:“我们的日期格式统一用 YYYY-MM-DD”、“遇到跨域请求允许通过”等。

  • 伪代码

    Java
    class AppConfig { func config() { // 设定规则:允许所有网站访问我的接口(跨域配置) setCors(true); // 设定规则:文件上传最大 10MB setMaxUploadSize("10MB"); } }
6. Interceptor (拦截器) ——com.interceptor
  • 本质保安/安检门

  • 职责:在请求到达 Controller(服务员)之前,先拦下来查验身份。比如判断用户是否登录。

  • 伪代码

    Java
    class LoginInterceptor { // 在 Controller 执行之前触发 func preHandle(Request request) { // 查验令牌 if (request.hasToken() == false) { return "滚去登录!"; // 拦截,不让进 } return true; // 放行 } }
7. Utils (工具类) ——com.utils
  • 本质公共工具箱

  • 职责:放一些谁都能用的通用功能,比如“获取当前时间”、“生成随机数”、“MD5加密”。

  • 伪代码

    Java
    class DateUtil { // 静态方法,谁都能直接调 static func getNow() { return "2023-10-27 12:00:00"; } }
8. Handler (异常处理) ——com.handler
  • 本质售后客服/危机公关

  • 职责:当系统任何地方报错(抛出异常)时,不能直接把难看的错误代码给用户看,而是由它捕获,然后返回一句友好的“系统繁忙,请稍后再试”。

  • 伪代码

    Java
    class GlobalExceptionHandler { // 只要系统报错,就来这 func handle(Exception e) { log.error(e); // 记录错误日志给自己看 return "哎呀,服务器开小差了"; // 友好的告诉用户 } }

总结:一个请求的完整一生

当你点击网页上的“注册”按钮时:

  1. Interceptor (安检):先看你有没有权限访问。

  2. Controller (服务员):接过你的请求,确认你填了名字和密码。

  3. Service (大厨):确认你没注册过,把你密码加密。

  4. Dao (仓库):把你的信息写入数据库。

  5. Utils (工具):Service 可能会借用工具箱里的“加密工具”。

  6. Handler (客服):如果中间数据库挂了,它会跳出来安抚你。

  7. Entity (盘子):你的数据一直装在这个对象里,在大家手中传来传去。

这就是你图片中每一个文件夹存在的意义。



我还是不明白,实体类到底是干嘛的,我们不是已经有mybatis了吗?这里存在的作用是啥?

这是一个非常棒的问题!这也是很多刚开始接触 ORM(对象关系映射)框架的同学最容易卡住的地方。

简单直接的回答是:MyBatis 是“搬运工”,而实体类(Entity)是“箱子”。

如果没有箱子,搬运工(MyBatis)从仓库(数据库)里把东西搬出来后,该往哪里放呢?散落一地吗?

下面我用三个维度来帮你彻底打通这个概念。

1. 核心逻辑:ORM(对象-关系映射)

MyBatis 的全称涉及一个概念叫ORM (Object-Relational Mapping)

  • Object (对象)= Java 里的实体类 (Entity)

  • Relational (关系)= 数据库里的表 (Table)

  • Mapping (映射)=MyBatis

MyBatis 的作用就是充当那个“中间人”,把数据库表里的一行数据(Table Row),自动变成 Java 里的一个对象(Java Object)。

  • 数据库只认识id=1, name="张三", age=18(这是数据)。

  • Java 代码只认识User user = new User();(这是对象)。

  • 实体类的作用:它就是 Java 世界里,对数据库那张表的镜像

如果没有实体类,MyBatis 查出来的name="张三"就没法变成user.getName(),你的 Java 代码就没法处理这数据。


2. 生活比喻:快递员与快递盒

想象一下网购的场景:

  • 数据库 (Database):电商的大仓库,里面堆满了散乱的商品(数据)。

  • MyBatis快递员。他负责去仓库把商品找出来,送到你手上。

  • 实体类 (Entity)快递盒子

  • Controller/Service你(消费者)

流程是这样的:

  1. 你(Service)告诉快递员(MyBatis):“我要买一个手机”。

  2. 快递员去仓库(数据库)找到了手机。

  3. 关键点来了:快递员不能直接把裸露的手机扔给你吧?他需要一个盒子(实体类),把手机装进去,封好口,才能给你。

  4. 你拿到盒子(Entity),打开盒子(getPhone()),才能使用手机。

如果你问“为什么要有实体类?”

这就像在问:“为什么要有快递盒子?快递员不能直接把手机零件散着拿给我吗?”

答案是:可以(用 Map),但非常不安全,容易丢件,而且你拿到手也不好拿。


3. 代码实战:如果没有实体类会发生什么?

为了证明实体类的重要性,我们看看如果没有它,代码会变成什么样。

情况 A:有实体类(现在的主流写法)

因为有User实体类,MyBatis 知道要把数据放进User模具里。

Java

// 1. 定义模具(实体类) public class User { private String name; private int age; // getter, setter... } // 2. MyBatis 只要配置一下 resultType="User" // 3. 业务代码(Service): User user = userMapper.getUserById(1); // ★★★ 这里的代码非常清晰,编译器会帮你检查错误 ★★★ System.out.println("名字是:" + user.getName()); System.out.println("年龄是:" + user.getAge());
情况 B:没有实体类(硬要用 MyBatis)

MyBatis 支持不使用实体类,而是用Map<String, Object>来接收数据(相当于用一个软塌塌的塑料袋装快递)。

Java

// 1. 没有 User 类了 // 2. MyBatis 返回一个 Map Map<String, Object> userMap = userMapper.getUserById(1); // 3. 业务代码(Service): // ★★★ 噩梦开始了 ★★★ // 这里的 "name" 必须手写,万一你手抖写成了 "Name" 或者 "nmae"? // 编译器不会报错,程序运行起来才会崩溃(空指针异常)! String name = (String) userMap.get("name"); // 你必须记得数据库里 age 是数字,如果强转错了也会崩 int age = (Integer) userMap.get("age"); System.out.println("名字是:" + name);

总结

  1. MyBatis 是工具(铲子/搬运工),它负责干活。

  2. 实体类是容器(桶/箱子),它负责装干活挖出来的数据。

  3. 存在的作用

    • 类型安全:防止你把年龄当成名字处理。

    • 代码提示:IDE 会提示你user.getName(),而不会提示你map.get("哪来着?")

    • 规范:它是数据库表在 Java 世界的“身份证”。

所以,MyBatis 越强,它越需要一个标准的实体类来配合它,这样它才能自动把数据库里的脏活累活(比如ResultSet的解析)全帮你干了,直接给你一个干干净净的 Java 对象。



dao层和resources/mapper 下的 XML 文件到底是怎么配合使用的?不理解,写个伪代码举例说明一下。


这是一个非常核心的问题!初学者最容易晕的地方就在这:“为什么我写了个接口(Interface)没写实现类,代码居然能跑起来?”

简单来说,Dao 层(接口)和 XML 文件(SQL)是**“遥控器”和“执行指令”**的关系。

1. 核心概念:遥控器与指令

  • Dao 接口 (UserDao.java):这是遥控器。上面只有按钮(方法名),比如“开机”、“换台”。它只规定了**“能做什么”**,但它内部是空的,没有任何代码实现。

  • Mapper XML (UserMapper.xml):这是指令手册。它详细记录了当按下“开机”按钮时,电路板具体该怎么走(SQL 语句怎么写)。

它们是如何配对的?

依靠**全限定名(Namespace)和方法名(ID)**来精确对接。


2. 伪代码举例:它们是如何“暗送秋波”的

假设我们要实现一个功能:根据 ID 查用户

A. 遥控器:UserDao.java(位于com.dao包)

Java

package com.dao; // 1. 注意这个包名 public interface UserDao { // 2. 注意这个方法名 // 3. 注意这个参数 (int id) User getUserById(int id); }
B. 指令手册:UserMapper.xml(位于resources/mapper)

XML

<!-- 1. namespace 必须完全等于 Dao 接口的“全路径” --> <!-- MyBatis 就是靠这个知道:本文件是专门给 com.dao.UserDao 这个接口服务的! --> <mapper namespace="com.dao.UserDao"> <!-- 2. id 必须完全等于 Dao 接口里的“方法名” --> <!-- MyBatis 看到 UserDao.getUserById() 被调用,就会来这就找 id="getUserById" 的 SQL --> <!-- 3. #{id} 里的 id 对应接口里的参数 --> <!-- 4. resultType 对应接口的返回值类型 (User) --> <select id="getUserById" resultType="com.entity.User"> SELECT * FROM user WHERE id = #{id} </select> </mapper>

3. 图解配合流程

你可以把这个配合过程想象成一次**“查字典”**的操作:

  1. 代码调用:Service 层调用了userDao.getUserById(1)

  2. MyBatis 接管

    • MyBatis 发现这是一个接口,它会立刻去resources/mapper下的所有 XML 文件里翻找。

    • 第一步(找文件):它看 XML 的<mapper namespace="...">。哎!发现有一个 XML 的 namespace 是com.dao.UserDao,跟你的接口名一模一样!锁定这个文件!

    • 第二步(找方法):它接着在这个文件里找,有没有一个标签的idgetUserById?哎!找到了!锁定这段 SQL!

  3. 执行 SQL:MyBatis 把参数1填进 SQL (SELECT ... WHERE id = 1),扔给数据库执行。

  4. 返回结果:数据库返回数据,MyBatis 根据resultType把它自动封装成User对象,扔回给 Service。

4. 总结:三个关键匹配

要想让它们配合成功,三个地方必须一模一样,错一个字符都会报错:

  1. 文件匹配:XML 的namespace= 接口的全类名(包名+类名)。

  2. 方法匹配:XML 标签的id= 接口的方法名

  3. 类型匹配:XML 的resultType= 接口的返回值类型

这就是为什么你不需要写 Dao 的实现类(比如UserDaoImpl),因为 MyBatis 在运行的时候,自动帮你生成了一个“代理对象”,帮你把这套查找和执行的流程全干了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/1/23 13:29:31

Wan2.2-T2V-A14B支持长时间序列生成吗?实测60秒连续输出

Wan2.2-T2V-A14B支持长时间序列生成吗&#xff1f;实测60秒连续输出 在影视制作、广告创意和虚拟内容生产领域&#xff0c;一个长期悬而未决的难题是&#xff1a;AI能否真正理解“时间”&#xff1f; 不是简单拼接几帧画面&#xff0c;也不是靠后期插值强行延长视频&#xff…

作者头像 李华
网站建设 2026/1/21 21:39:41

【高效运维必看】:Agent服务在Docker中跨环境迁移的7种优化方案

第一章&#xff1a;Agent服务在Docker中跨环境迁移的核心挑战在将Agent服务通过Docker容器化部署并实现跨环境迁移的过程中&#xff0c;尽管容器技术提供了“一次构建&#xff0c;处处运行”的理想承诺&#xff0c;实际落地仍面临诸多核心挑战。这些挑战主要集中在配置管理、网…

作者头像 李华
网站建设 2025/12/12 1:16:03

深度指南:如何设计Prompt引导DeepSeek生成高效的分步故障排查流程

深度指南&#xff1a;如何设计Prompt引导DeepSeek生成高效的分步故障排查流程在当今技术驱动的世界中&#xff0c;系统、设备或应用程序出现故障几乎是不可避免的。快速、准确地定位并解决这些故障对于维持业务连续性、提升用户体验以及降低运营成本至关重要。传统的故障排查手…

作者头像 李华
网站建设 2026/1/16 20:40:06

脑机接口:破解大脑密码,连接意识与机器的未来之门

脑机接口&#xff1a;破解大脑密码&#xff0c;连接意识与机器的未来之门 你是否幻想过&#xff0c;无需动手敲键盘、动嘴发指令&#xff0c;仅靠“意念”就能操控手机、驾驶汽车&#xff0c;甚至让瘫痪的肢体重新活动&#xff1f;这不是科幻电影的桥段&#xff0c;而是脑机接口…

作者头像 李华
网站建设 2026/1/15 9:21:04

BepInEx框架实战指南:从入门到精通的Unity模组开发全解析

BepInEx框架实战指南&#xff1a;从入门到精通的Unity模组开发全解析 【免费下载链接】BepInEx Unity / XNA game patcher and plugin framework 项目地址: https://gitcode.com/GitHub_Trending/be/BepInEx 嘿&#xff0c;Unity开发者们&#xff01;你是否曾经遇到过这…

作者头像 李华
网站建设 2026/1/15 2:43:09

2026护士资格考试报名照审核全流程

作为已经上岸的学姐&#xff0c;真的要吐槽卫生&护士资格报名的照片审核&#xff01;当年因为照片格式不对反复改&#xff0c;差点错过报名时间&#x1f62d; 今天把踩坑经验整理出来&#xff0c;宝子们别再踩雷啦&#xff01;&#x1f4f8; 先划重点&#xff01;照片硬性要…

作者头像 李华