前言:这份文档专门给编程小白准备,全程用大白话+完整代码,先讲「没有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">
<!-- 绑定对应的Mapper接口(路径要和接口完全一致) -->
<mapper namespace="com.xxx.mapper.AdminMapper">
<!-- 1. 新增管理员:对应接口的insertAdmin方法 -->
<insert id="insertAdmin" parameterType="com.xxx.entity.Admin">
-- 这里是SQL语句,和你在数据库里写的一样
insert into admin (username, password) values (#{username}, #{password})
</insert>
<!-- 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的原有功能。