保姆级教程:手写三层架构 vs MyBatis-Plus

前言:这份文档专门给编程小白准备,全程用大白话+完整代码,先讲「没有MyBatis-Plus」时,我们怎么手动写三层架构(懂手动,才懂框架的方便),再讲「有MyBatis-Plus」时,框架怎么帮我们省掉重复代码,一步步看懂、能直接复制用。

核心前提:不管有没有MyBatis-Plus,我们写项目都要遵循「三层架构」,目的是让代码有条理,后期好修改、好维护(就像家里分客厅、卧室、厨房,各干各的活,不乱)。

三层架构核心分工(记死!小白必背):

  • Controller(控制层):最外层,负责接前端的请求(比如用户点"查询管理员"),再调用Service处理,最后把结果返回给前端(给用户看); 大白话:相当于奶茶店前台,只接单、传单、给奶茶,不做奶茶。

  • Service(业务层):中间层,负责处理业务逻辑(比如"查询管理员前要检查权限""新增管理员要判断用户名不重复"),再调用Mapper去操作数据库; 大白话:相当于奶茶店后厨,按订单做奶茶(处理规则),要原料就喊仓库。

  • Mapper(数据层):最内层,直接和数据库打交道,负责"存数据、取数据"(比如查管理员、新增管理员); 大白话:相当于奶茶店仓库,只负责拿原料、存原料,不做奶茶、不接单。

第一部分:非MyBatis-Plus架构(手动版,小白先懂"底层逻辑")

没有MyBatis-Plus框架时,所有代码都要我们自己写,哪怕是重复的"查数据、存数据",也要逐行写,全程手动,虽然麻烦,但能看懂"代码到底在做什么"。

我们以「管理员(Admin)操作」为例(比如新增管理员、查询管理员列表),一步步写完整三层代码,每一步都带注释,小白跟着看就能懂。

第一步:准备实体类(Entity)------ 对应数据库的表

先有一个"管理员"实体类,用来封装管理员的信息(比如id、用户名、密码),和数据库里的"admin表"一一对应(数据库表有什么字段,实体类就有什么属性)。

代码(直接复制能用):

java 复制代码
// 实体类:对应数据库的admin表
public class Admin {
    // 管理员id(对应数据库表的主键)
    private Long id;
    // 管理员用户名
    private String username;
    // 管理员密码
    private String password;

    // 小白不用管下面的getter/setter,是用来获取、设置属性值的
    // (IDE能自动生成,比如IDEA右键→Generate→Getter and Setter)
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
}

大白话:这个类就像一个"管理员信息模板",我们查数据库、存数据库,都用这个模板来装数据。

第二步:Mapper层(数据层)------ 手动写代码操作数据库

Mapper层是直接和数据库打交道的,没有框架的话,我们要写两个东西:Mapper接口(定义操作方法)+ Mapper XML(写SQL语句,告诉程序怎么查、怎么存)。

2.1 Mapper接口(定义方法)

先定义我们要做的操作(比如新增管理员、查询所有管理员),相当于"给仓库定规矩,告诉仓库要拿什么、存什么"。

java 复制代码
// Mapper接口:定义操作数据库的方法(只定规矩,不写具体怎么做)
public interface AdminMapper {
    // 1. 新增管理员(参数是Admin对象,里面装着要新增的管理员信息)
    void insertAdmin(Admin admin);

    // 2. 查询所有管理员(返回一个列表,里面装着所有管理员的信息)
    List<Admin> selectAllAdmin();
}

2.2 Mapper XML(写SQL,实现方法)

接口只定了"要做什么",XML要写"具体怎么做"(写SQL语句),程序通过SQL语句去操作数据库。

注意:XML文件要放在resources目录下,路径和Mapper接口的路径一致(比如接口在com.xxx.mapper,XML就在resources/com/xxx/mapper)。

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"&gt;
<!-- 绑定对应的Mapper接口(路径要和接口完全一致) -->
&lt;mapper namespace="com.xxx.mapper.AdminMapper"&gt;

    <!-- 1. 新增管理员:对应接口的insertAdmin方法 -->
    <insert id="insertAdmin" parameterType="com.xxx.entity.Admin">
        -- 这里是SQL语句,和你在数据库里写的一样
        insert into admin (username, password) values (#{username}, #{password})
    &lt;/insert&gt;

    <!-- 2. 查询所有管理员:对应接口的selectAllAdmin方法 -->
    <select id="selectAllAdmin" resultType="com.xxx.entity.Admin">
        select id, username, password from admin
    </select>

</mapper>

大白话:XML里的SQL,就是我们平时在数据库里执行的语句,这里只是把它写到代码里,让程序能自动执行。

第三步:Service层(业务层)------ 手动写业务逻辑

Service层负责处理业务逻辑,要调用Mapper层的方法(让仓库拿原料/存原料),还要加自己的规则(比如新增管理员时,判断用户名不能重复)。

Service层也要写两个东西:Service接口(定义业务方法)+ Service实现类(实现业务逻辑)。

3.1 Service接口(定义业务方法)

java 复制代码
// Service接口:定义业务方法(和Mapper接口区分开,这里是业务层面的方法)
public interface AdminService {
    // 1. 新增管理员(和Mapper的insertAdmin对应,但多了业务逻辑)
    boolean addAdmin(Admin admin);

    // 2. 查询所有管理员(和Mapper的selectAllAdmin对应)
    List<Admin> getAllAdmin();
}

3.2 Service实现类(实现业务逻辑)

这里要注入Mapper(相当于"后厨联系仓库"),然后实现接口的方法,加业务逻辑,再调用Mapper的方法操作数据库。

java 复制代码
// Service实现类:真正处理业务逻辑的地方
// @Service注解:告诉程序,这是Service层的类,让程序能找到它
@Service
public class AdminServiceImpl implements AdminService {

    // 注入AdminMapper:相当于"后厨联系仓库",让Service能调用Mapper的方法
    @Autowired
    private AdminMapper adminMapper;

    // 实现新增管理员的业务方法
    @Override
    public boolean addAdmin(Admin admin) {
        // 业务逻辑:新增前,判断用户名是否重复(小白重点看这里,这就是业务逻辑)
        // 1. 先查询数据库,看有没有这个用户名
        List<Admin> adminList = adminMapper.selectAllAdmin();
        for (Admin a : adminList) {
            if (a.getUsername().equals(admin.getUsername())) {
                // 用户名重复,新增失败,返回false
                return false;
            }
        }
        // 2. 用户名不重复,调用Mapper的方法,新增管理员
        adminMapper.insertAdmin(admin);
        // 新增成功,返回true
        return true;
    }

    // 实现查询所有管理员的方法
    @Override
    public List<Admin> getAllAdmin() {
        // 直接调用Mapper的方法,查询所有管理员(没有复杂业务逻辑,直接转发)
        return adminMapper.selectAllAdmin();
    }
}

大白话:Service层就是"中间处理器",比如新增管理员,它先检查用户名有没有重复(业务逻辑),没问题再让Mapper去存数据库,有问题就直接返回失败。

第四步:Controller层(控制层)------ 接收请求、返回响应

Controller层只负责接前端的请求,调用Service层的方法,然后把结果返回给前端,绝对不写业务逻辑(就像前台不做奶茶,只传单)。

java 复制代码
// Controller层:接收请求、返回响应
// @RestController注解:告诉程序,这是Controller层的类,专门处理前端请求
@RestController
// @RequestMapping:给这个Controller定一个"访问路径",前端通过这个路径找它
@RequestMapping("/admin")
public class AdminController {

    // 注入AdminService:相当于"前台联系后厨",调用Service的业务方法
    @Autowired
    private AdminService adminService;

    // 1. 新增管理员:接收前端的新增请求(POST请求,路径是/admin/add)
    @PostMapping("/add")
    public String addAdmin(@RequestBody Admin admin) {
        // 调用Service的新增方法,获取结果
        boolean success = adminService.addAdmin(admin);
        // 返回响应给前端(小白能看懂的提示)
        if (success) {
            return "新增管理员成功!";
        } else {
            return "新增失败,用户名已存在!";
        }
    }

    // 2. 查询所有管理员:接收前端的查询请求(GET请求,路径是/admin/list)
    @GetMapping("/list")
    public List<Admin> getAllAdmin() {
        // 调用Service的查询方法,直接返回结果给前端
        return adminService.getAllAdmin();
    }
}

大白话:前端用户点击"新增管理员",请求就会传到Controller的/add路径,Controller喊Service去处理,Service处理完告诉Controller结果,Controller再把结果显示给用户。

非MyBatis-Plus架构总结(小白必记)

  • 所有代码都要手动写:Mapper接口+XML(写SQL)、Service接口+实现类(写业务)、Controller(接请求);

  • 重复工作多:比如每个实体类(Admin、User、Order)的新增、查询方法,都要重复写一遍Mapper和Service;

  • 好处:完全懂代码的执行逻辑,知道每一步在做什么,适合小白打基础。

第二部分:MyBatis-Plus架构(简化版,框架帮我们偷懒)

MyBatis-Plus(简称MP)的核心作用:帮我们自动生成重复的代码(比如Mapper的CRUD方法、Service的基础方法),不用再手动写XML和重复的方法,我们只需要专注写业务逻辑(比如判断用户名重复)。

还是以「管理员(Admin)操作」为例,对比非MP版本,看MP怎么帮我们省代码,全程小白友好,复制就能用。

前提:已经导入MyBatis-Plus的依赖(小白不用管依赖怎么导,跟着项目配置走就行)。

第一步:准备实体类(和非MP版本一样,无变化)

和非MP版本完全一样,唯一的小区别:给主键加一个注解(@TableId),告诉MP这是数据库的主键,MP会自动处理主键的自增/生成。

java 复制代码
// 实体类:对应数据库的admin表
public class Admin {
    // @TableId:告诉MyBatis-Plus,这是主键,type=IdType.AUTO表示主键自增(和数据库一致)
    @TableId(type = IdType.AUTO)
    private Long id;
    private String username;
    private String password;

    // 下面的getter/setter和之前一样,自动生成即可
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
}

第二步:Mapper层(数据层)------ MP帮我们省掉XML和重复方法

非MP版本,我们要写Mapper接口+XML;MP版本,只需要写一个接口,继承BaseMapper,就自动拥有所有单表CRUD方法(新增、删除、修改、查询),不用写XML,不用写方法定义!

java 复制代码
// Mapper接口:继承BaseMapper,自动拥有所有单表CRUD方法
public interface AdminMapper extends BaseMapper<Admin> {
    // 空的!不用写任何方法!
    // MP的BaseMapper里已经写好了insert、deleteById、selectById、selectList等方法
}

大白话:BaseMapper就像一个"万能工具箱",里面装好了所有常用的数据库操作方法,我们继承它,就相当于把这个工具箱拿过来直接用,不用自己造工具(写方法、写SQL)。

MP自动提供的常用方法(小白记几个最常用的):

  • insert(T entity):新增数据(对应非MP的insertAdmin);

  • selectList(null):查询所有数据(对应非MP的selectAllAdmin);

  • selectById(Long id):根据ID查询数据;

  • updateById(T entity):根据ID修改数据;

  • deleteById(Long id):根据ID删除数据。

第三步:Service层(业务层)------ MP帮我们省掉重复的实现代码

非MP版本,我们要写Service接口+实现类,还要手动注入Mapper、写方法实现;MP版本,Service接口继承IService,Service实现类继承ServiceImpl,就自动拥有所有基础业务方法,不用手动写实现!

3.1 Service接口(继承IService)

java 复制代码
// Service接口:继承IService,自动拥有所有基础业务方法
public interface AdminService extends IService<Admin> {
    // 空的!不用写基础方法!
    // 如果有复杂业务(比如判断用户名重复),再在这里加自定义方法
}

3.2 Service实现类(继承ServiceImpl)

继承ServiceImpl<AdminMapper, Admin>,同时实现我们自己的AdminService接口,MP会自动注入Mapper,自动实现所有基础方法,我们只需要写复杂业务逻辑即可。

java 复制代码
// Service实现类:继承ServiceImpl,自动实现基础方法
@Service
public class AdminServiceImpl extends ServiceImpl<AdminMapper, Admin> implements AdminService {
    // 基础方法(save、list、getById等)已经自动实现,不用写!
    // 我们只需要写自己的复杂业务逻辑,比如新增管理员时判断用户名重复

    // 自定义业务方法:新增管理员(带用户名重复校验)
    public boolean addAdmin(Admin admin) {
        // 业务逻辑:判断用户名是否重复
        // MP提供了lambdaQuery()方法,不用自己写SQL,直接调用
        Admin existAdmin = lambdaQuery().eq(Admin::getUsername, admin.getUsername()).one();
        if (existAdmin != null) {
            // 用户名重复,返回false
            return false;
        }
        // 调用MP自动生成的save方法(相当于非MP的insertAdmin),新增管理员
        return save(admin);
    }

    // 示例:给查询列表加日志(小白之前问的需求)
    @Override
    public List<Admin> list() {
        // 自己加的逻辑:打印日志
        System.out.println("开始查询管理员列表,时间:" + new Date());
        // 调用MP父类的list()方法,查询所有数据
        List<Admin> adminList = super.list();
        // 自己加的逻辑:打印查询结果数量
        System.out.println("查询到" + adminList.size() + "个管理员");
        return adminList;
    }
}

大白话:ServiceImpl就像一个"预制后厨",已经做好了所有基础奶茶(基础业务方法),我们只需要根据订单加"少糖、去冰"(自定义业务逻辑),不用从头做奶茶。

MP Service自动提供的常用方法(小白记几个):

  • save(T entity):新增数据(对应Mapper的insert);

  • list():查询所有数据(对应Mapper的selectList);

  • getById(Long id):根据ID查询;

  • updateById(T entity):根据ID修改;

  • removeById(Long id):根据ID删除。

第四步:Controller层(和非MP版本几乎一样)

Controller层的逻辑不变,还是接收请求、调用Service、返回响应,唯一的区别:调用的是MP自动生成的Service方法(比如save、list)。

java 复制代码
@RestController
@RequestMapping("/admin")
public class AdminController {

    @Autowired
    private AdminService adminService;

    // 1. 新增管理员(调用我们自定义的addAdmin方法,带用户名校验)
    @PostMapping("/add")
    public String addAdmin(@RequestBody Admin admin) {
        boolean success = adminService.addAdmin(admin);
        return success ? "新增成功" : "用户名已存在,新增失败";
    }

    // 2. 查询所有管理员(调用MP自动生成的list方法,带我们加的日志)
    @GetMapping("/list")
    public List<Admin> getAllAdmin() {
        return adminService.list();
    }

    // 3. 新增:调用MP自动生成的save方法(无业务逻辑,直接新增)
    @PostMapping("/save")
    public String saveAdmin(@RequestBody Admin admin) {
        adminService.save(admin);
        return "新增管理员成功(无校验)";
    }

    // 4. 根据ID查询(调用MP自动生成的getById方法)
    @GetMapping("/get/{id}")
    public Admin getAdminById(@PathVariable Long id) {
        return adminService.getById(id);
    }
}

MyBatis-Plus架构总结(小白必记)

  • MP帮我们省掉了"重复代码":不用写Mapper的XML、不用写基础的CRUD方法、不用写Service的基础实现;

  • 我们只需要做3件事:① 写实体类(加@TableId);② 写Mapper接口(继承BaseMapper);③ 写Service实现类(继承ServiceImpl),专注写复杂业务逻辑;

  • 核心优势:偷懒、高效,不用做重复的体力活,适合实际项目开发;

  • 注意:复杂查询(比如连表查询),还是要手动写SQL(和非MP版本一样),MP只帮我们处理"单表的基础操作"。

第三部分:小白必看对比表(非MP vs MP)

层级 非MyBatis-Plus(手动版) MyBatis-Plus(简化版) 小白总结
实体类 写属性+getter/setter,无注解 写属性+getter/setter,主键加@TableId 几乎一样,多一个注解
Mapper层 写接口(定义方法)+ XML(写SQL) 写接口,继承BaseMapper,无XML、无方法定义 MP帮我们省了最多代码
Service层 写接口+实现类,手动注入Mapper、写方法实现 写接口(继承IService)+ 实现类(继承ServiceImpl),基础方法自动实现 只需要写复杂业务逻辑
Controller层 调用自己写的Service方法 调用MP自动生成的Service方法,或自己的自定义方法 几乎一样,调用的方法更简洁

第四部分:小白避坑提醒(必看!)

  • 不要删除Service层:哪怕Service实现类是空的,也不能删!Service层是处理业务逻辑、控制事务的核心,直接在Controller里调用Mapper,后期代码会乱成一团,没法维护。

  • 实体类要规范:实体类的属性名要和数据库表的字段名一致(比如数据库是username,实体类也叫username),不一致就用@TableField注解指定,否则MP找不到字段,会报错。

  • 不要过度依赖MP:MP只帮我们处理单表的基础操作,复杂查询(连表、多条件分组)还是要手动写SQL(和非MP版本一样)。

  • 重写/自定义方法:想加自己的逻辑(比如打印日志、校验),直接在Service实现类里重写MP的方法(用@Override),或新增自定义方法,不影响MP的原有功能。

相关推荐
星浩AI2 小时前
让模型自己写 Skills——从素材到自动生成工作流
人工智能·后端·agent
华仔啊4 小时前
为啥不用 MP 的 saveOrUpdateBatch?MySQL 一条 SQL 批量增改才是最优解
java·后端
武子康5 小时前
大数据-242 离线数仓 - DataX 实战:MySQL 全量/增量导入 HDFS + Hive 分区(离线数仓 ODS
大数据·后端·apache hive
砍材农夫5 小时前
TCP和UDP区别
后端
千寻girling6 小时前
一份不可多得的 《 Django 》 零基础入门教程
后端·python·面试
千寻girling6 小时前
Python 是用来做 AI 人工智能 的 , 不适合开发 Web 网站 | 《Web框架》
人工智能·后端·算法
贾铭6 小时前
如何实现一个网页版的剪映(三)使用fabric.js绘制时间轴
前端·后端
xiaoye20186 小时前
Spring 自定义 Redis 超时:TTL、TTI 与 Pipeline 实战
后端
程序员爱钓鱼9 小时前
GoHTML解析利器:github.com/PuerkitoBio/goquery实战指南
后端·google·go