跳转至

MyBatis-Plus 数据访问

操作数据库。本书用 MyBatis-Plus(简称 MP)——SQL 透明可控、CRUD 自动生成、对前端人最直观。

Mapper 继承 BaseMapper,白嫖 CRUD

只要让你的 Mapper 继承 BaseMapper<实体>,一行 SQL 不写,自动获得一堆方法:

package com.javaglm.task.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.javaglm.task.entity.Task;

/**
 * 任务 Mapper(第 26 章)。
 * 继承 BaseMapper<Task>,白嫖一堆 CRUD 方法(insert/updateById/deleteById/selectById/selectList...),
 * 一行 SQL 都不用写。需要自定义 SQL 时再在这里加方法 + 写 XML/注解。
 */
public interface TaskMapper extends BaseMapper<Task> {
}

自动获得的方法:

方法 作用
insert(task) 插入
updateById(task) 按 id 更新
deleteById(id) 按 id 删除
selectById(id) 按 id 查询
selectList(wrapper) 条件查询
selectCount(wrapper) 计数
selectPage(page, wrapper) 分页查询

条件构造器:LambdaQueryWrapper

查询不用拼 SQL 字符串,用类型安全的 LambdaQueryWrapper

LambdaQueryWrapper<Task> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Task::getUserId, userId);          // WHERE user_id = ?
if (status != null) {
    wrapper.eq(Task::getStatus, status);      // AND status = ?
}
wrapper.orderByDesc(Task::getCreateTime);     // ORDER BY create_time DESC
List<Task> list = taskMapper.selectList(wrapper);

Task::getUserId 是方法引用(第 15 章),MP 据此推出列名 user_id(驼峰转下划线)。

分页:要注册插件

分页不是开箱即用,得注册分页插件,否则 page() 会查出全部:

package com.javaglm.task.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * MyBatis-Plus 配置(第 26 章):注册分页插件。
 * 不加这个,page() 不会真的分页(会查出全部)。
 */
@Configuration
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

实战:分页查询任务

Service 层组合 wrapper + page:

package com.javaglm.task.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.javaglm.task.common.BizException;
import com.javaglm.task.dto.TaskRequest;
import com.javaglm.task.entity.Task;
import com.javaglm.task.mapper.TaskMapper;
import com.javaglm.task.service.TaskService;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;

/**
 * 任务服务实现(第 28 章)。
 * 继承 ServiceImpl<TaskMapper, Task>:自动拥有 CRUD,Mapper 也自动注入。
 * 业务逻辑写在这里(校验归属、组装数据),Controller 保持"瘦"。
 */
@Service
public class TaskServiceImpl extends ServiceImpl<TaskMapper, Task> implements TaskService {

    @Override
    public IPage<Task> pageTasks(long current, long size, Long userId, Integer status) {
        LambdaQueryWrapper<Task> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Task::getUserId, userId);
        if (status != null) {
            wrapper.eq(Task::getStatus, status);
        }
        wrapper.orderByDesc(Task::getCreateTime);
        return this.page(new Page<>(current, size), wrapper);
    }

    @Override
    public Task createTask(TaskRequest req, Long userId) {
        Task task = new Task();
        task.setTitle(req.getTitle());
        task.setDescription(req.getDescription());
        task.setStatus(req.getStatus() == null ? 0 : req.getStatus());
        task.setUserId(userId);
        task.setCreateTime(LocalDateTime.now());
        task.setUpdateTime(LocalDateTime.now());
        this.save(task);
        return task;
    }

    @Override
    public Task updateTask(Long id, TaskRequest req, Long userId) {
        Task task = this.getById(id);
        if (task == null || !task.getUserId().equals(userId)) {
            throw new BizException(404, "任务不存在或无权操作");
        }
        task.setTitle(req.getTitle());
        task.setDescription(req.getDescription());
        if (req.getStatus() != null) {
            task.setStatus(req.getStatus());
        }
        task.setUpdateTime(LocalDateTime.now());
        this.updateById(task);
        return task;
    }

    @Override
    public void deleteTask(Long id, Long userId) {
        Task task = this.getById(id);
        if (task == null || !task.getUserId().equals(userId)) {
            throw new BizException(404, "任务不存在或无权操作");
        }
        this.removeById(id);   // @TableLogic:实际执行逻辑删除
    }
}

pageTasks 方法:组装条件 → this.page(new Page<>(current, size), wrapper) 返回 IPage<Task>,里面含 records(当前页数据)、total(总数)、current/size(页码/页大小)。

为什么不用 JPA

  • MyBatis-Plus:你看得见 SQL(或自动生成可控),调试简单,国内主流。
  • JPA:声明接口自动生成 SQL,优雅但"魔法"重,复杂查询难写,国内用得少。

对前端人:MP 的"写条件→出数据"和你的"发请求→拿数据"心智最接近。

需要自定义 SQL 怎么办

BaseMapper 不够用时,在 Mapper 接口加方法 + 写 XML 或注解 SQL。本书任务系统的需求 BaseMapper 全够了,不展开。


:octicons-arrow-left-16: 上一章:配置管理 | 下一章:数据库设计实战