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

目录

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

[1. Entity (实体类) ------ com.entity](#1. Entity (实体类) —— com.entity)

[2. Controller (控制层) ------ com.controller](#2. Controller (控制层) —— com.controller)

[3. Service (业务层) ------ com.service](#3. Service (业务层) —— com.service)

[4. Dao (持久层) ------ com.dao](#4. Dao (持久层) —— com.dao)

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

[5. Config (配置类) ------ com.config](#5. Config (配置类) —— com.config)

[6. Interceptor (拦截器) ------ com.interceptor](#6. Interceptor (拦截器) —— com.interceptor)

[7. Utils (工具类) ------ com.utils](#7. Utils (工具类) —— com.utils)

[8. Handler (异常处理) ------ com.handler](#8. Handler (异常处理) —— com.handler)

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

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

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

[2. 生活比喻:快递员与快递盒](#2. 生活比喻:快递员与快递盒)

[3. 代码实战:如果没有实体类会发生什么?](#3. 代码实战:如果没有实体类会发生什么?)

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

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

总结

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

[1. 核心概念:遥控器与指令](#1. 核心概念:遥控器与指令)

[2. 伪代码举例:它们是如何"暗送秋波"的](#2. 伪代码举例:它们是如何“暗送秋波”的)

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

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

[3. 图解配合流程](#3. 图解配合流程)

[4. 总结:三个关键匹配](#4. 总结:三个关键匹配)


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


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

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

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

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

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

  • 伪代码

    Java

    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

    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

    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

    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

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

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

  • 伪代码

    Java

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

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

  • 伪代码

    Java

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

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

  • 伪代码

    Java

    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

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

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

java 复制代码
package com.dao; // 1. 注意这个包名

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

XML

java 复制代码
<!-- 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 在运行的时候,自动帮你生成了一个"代理对象",帮你把这套查找和执行的流程全干了。

相关推荐
掉鱼的猫2 小时前
Java 低代码平台的“动态引擎”:Liquor
java·低代码·groovy
TT哇3 小时前
【Database Navigator 插件】idea 社区版连接 mysql 数据库
java·数据库·mysql·intellij-idea·database
Tony__Ferguson3 小时前
抽奖系统测试报告
java·功能测试·模块测试
做人不要太理性3 小时前
【Linux系统】ELF 文件格式的硬核揭秘
java·linux·服务器
zhglhy3 小时前
Jaccard相似度算法原理及Java实现
java·开发语言·算法
啥都不懂的小小白3 小时前
Java日志篇3:Logback 配置全解析与生产环境最佳实践
java·开发语言·logback
江沉晚呤时3 小时前
延迟加载(Lazy Loading)详解及在 C# 中的应用
java·开发语言·microsoft·c#
谷哥的小弟3 小时前
Spring Framework源码解析——ConfigurableApplicationContext
java·spring·源码
麒qiqi3 小时前
【Linux 系统编程】文件 IO 与 Makefile 核心实战:从系统调用到工程编译
java·前端·spring