写在最前

最近在折腾 SpringBoot,后端开发最火的当属 MyBatis-Plus 框架了。虽然它简化了很多操作,但手动编写 CRUD 代码依然是一件繁琐又枯燥的事。有没有一种“一键生成、原地起飞”的解决方案呢?当然有!MyBatis-Plus 官方提供了代码生成器 —— MyBatis Generator。不过,原生工具在使用体验上还有些不足。幸运的是,我在 GitHub 上发现了一个基于它开发的图形化代码生成工具,功能强大、界面友好,真正做到了开箱即用,简直不要太香!

1. 前置要求

用 MyBatis 代码生成器之前,记得先把相关依赖加进项目里,尤其是 pom.xml。不然生成一堆代码也白搭,项目都用不上,岂不是白忙一场?

<!-- MyBatis Plus: 增强的MyBatis框架,简化数据库操作 -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
    <version>3.5.5</version>
</dependency>

2. 操作流程

https://github.com/fengwenyi/mybatis-plus-code-generator

在使用代码生成器之前,需要先配置好数据库连接信息。然后通过 IDEA 创建好 SpringBoot 项目,并进入项目的 java 目录,例如:
F:\idea_code\demo\src\main\java。将这个路径复制下来,并作为代码生成器的输出目录。这样生成的代码就会直接写入到你的项目中,无需手动移动,IDEA 也能立即识别使用。

还有一点非常重要,那就是在生成器配置中务必勾选“开启 @Mapper 注解”的选项。这样就可以启用注解驱动的方式,完全舍弃传统的 mapper.xml 配置文件,实现无 XML 的简洁开发模式。

在完成配置后,点击“立即生成”,系统会自动将生成的代码插入到项目中,我们可以在项目中查看生成结果。

顺带一提,因为我们开启了@Mapper了,所以生成的mapper_xml这个目录的内容是可以直接删掉的。

image-caXG.png

image-JEFy.png

image-HXYI.png

3. 优化配置

虽然系统已生成了所有代码,但我们注意到各个 Controller 类中尚未包含具体实现。为了解决这一问题,我们可以创建一个通用的 BaseController 类,统一实现常用的 RESTful API 接口。然后让这些空的 Controller 类继承 BaseController,即可自动拥有基础的接口功能,提升代码复用性与开发效率。

package com.tanqidi.survey.common;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.tanqidi.survey.common.utils.LogContextUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.io.Serializable;
import java.util.List;

/**
 * 通用 RESTful 控制器
 * @param <T> 实体类型
 * @param <S> Service 类型
 */
@RestController
public abstract class BaseController<T, S extends IService<T>> {

    protected final Logger log = LoggerFactory.getLogger(getClass());

    @Autowired
    protected S service;

    /**
     * 记录请求开始时间
     */
    protected void logRequestStart(String operation, Object... args) {
        log.info("开始执行: {} - 参数: {}", operation, args);
    }

    /**
     * 记录请求结束时间和执行时间
     */
    protected void logRequestEnd(String operation, long startTime) {
        long endTime = System.currentTimeMillis();
        log.info("结束执行: {} - 耗时: {}ms", operation, (endTime - startTime));
    }

    /**
     * 记录异常信息
     */
    protected void logError(String operation, Exception e) {
        log.error("执行出错: {} - 错误信息: {}", operation, e.getMessage(), e);
    }

    @PostMapping
    public R<T> create(@RequestBody T entity) {
        String operation = "创建实体";
        long startTime = System.currentTimeMillis();
        logRequestStart(operation, entity);
        try {
            boolean success = service.save(entity);
            logRequestEnd(operation, startTime);
            return success ? R.ok(entity) : R.error("新增失败");
        } catch (Exception e) {
            logError(operation, e);
            return R.error("新增失败: " + e.getMessage());
        }
    }

    @PutMapping
    public R<T> update(@RequestBody T entity) {
        String operation = "更新实体";
        long startTime = System.currentTimeMillis();
        logRequestStart(operation, entity);
        try {
            boolean success = service.updateById(entity);
            logRequestEnd(operation, startTime);
            return success ? R.ok(entity) : R.error("更新失败");
        } catch (Exception e) {
            logError(operation, e);
            return R.error("更新失败: " + e.getMessage());
        }
    }

    @DeleteMapping("/{id}")
    public R<Boolean> delete(@PathVariable Serializable id) {
        String operation = "删除实体";
        long startTime = System.currentTimeMillis();
        logRequestStart(operation, id);
        try {
            boolean success = service.removeById(id);
            logRequestEnd(operation, startTime);
            return R.ok(success);
        } catch (Exception e) {
            logError(operation, e);
            return R.error("删除失败: " + e.getMessage());
        }
    }

    @PostMapping("/batch-delete")
    public R<Boolean> deleteBatch(@RequestBody List<? extends Serializable> ids) {
        String operation = "批量删除实体";
        long startTime = System.currentTimeMillis();
        logRequestStart(operation, ids);
        try {
            boolean success = service.removeByIds(ids);
            logRequestEnd(operation, startTime);
            return R.ok(success);
        } catch (Exception e) {
            logError(operation, e);
            return R.error("批量删除失败: " + e.getMessage());
        }
    }

    @GetMapping("/{id}")
    public R<T> getById(@PathVariable Serializable id) {
        String operation = "根据ID查询实体";
        long startTime = System.currentTimeMillis();
        logRequestStart(operation, id);
        try {
            T result = service.getById(id);
            logRequestEnd(operation, startTime);
            return result != null ? R.ok(result) : R.error("数据不存在");
        } catch (Exception e) {
            logError(operation, e);
            return R.error("查询失败: " + e.getMessage());
        }
    }

    @GetMapping
    public R<List<T>> list() {
        String operation = "查询所有实体";
        long startTime = System.currentTimeMillis();
        logRequestStart(operation);
        try {
            List<T> list = service.list();
            logRequestEnd(operation, startTime);
            return R.ok(list);
        } catch (Exception e) {
            logError(operation, e);
            return R.error("查询失败: " + e.getMessage());
        }
    }

    @PostMapping("/page")
    public R<Page<T>> page(
            @RequestBody(required = false) T query,
            @RequestParam(defaultValue = "1") long page,
            @RequestParam(defaultValue = "10") long size
    ) {
        String operation = "分页查询实体";
        long startTime = System.currentTimeMillis();
        logRequestStart(operation, query, page, size);
        try {
            QueryWrapper<T> wrapper = new QueryWrapper<>();
            if (query != null) {
                wrapper.setEntity(query);
            }
            Page<T> resultPage = service.page(new Page<>(page, size), wrapper);
            logRequestEnd(operation, startTime);
            return R.ok(resultPage);
        } catch (Exception e) {
            logError(operation, e);
            return R.error("分页查询失败: " + e.getMessage());
        }
    }

    @PostMapping("/list-by")
    public R<List<T>> listBy(@RequestBody(required = false) T query) {
        String operation = "条件查询实体";
        long startTime = System.currentTimeMillis();
        logRequestStart(operation, query);
        try {
            QueryWrapper<T> wrapper = new QueryWrapper<>();
            if (query != null) {
                wrapper.setEntity(query);
            }
            List<T> resultList = service.list(wrapper);
            logRequestEnd(operation, startTime);
            return R.ok(resultList);
        } catch (Exception e) {
            logError(operation, e);
            return R.error("条件查询失败: " + e.getMessage());
        }
    }
}

只需继承 BaseController,并传入对应的实体类(Entity)和对应的 IXXXService 接口,即可通过 BaseController 自动完成依赖注入(@Autowired)并找到具体的实现类,从而让每个 Controller 拥有完整的基础接口能力,无需重复编写增删改查等通用代码。

package com.tanqidi.survey.controller;

import com.tanqidi.survey.common.BaseController;
import com.tanqidi.survey.common.R;
import com.tanqidi.survey.entity.SysUserEntity;
import com.tanqidi.survey.service.ISysUserService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * <p>
 *  前端控制器
 * </p>
 *
 * @author 
 * @since 2025-06-01
 */
@RestController
@RequestMapping("/api/v1/sys-users")
public class SysUserController extends BaseController<SysUserEntity, ISysUserService> {

}

4. 写在最后

值得注意的是,本项目中还有很多细节未一一展示,例如 application.properties 的具体配置。若各位希望深入了解这些内容,欢迎留言探讨,我会视情况补充更多细节文档。