醋醋百科网

Good Luck To You!

告别CRUD地狱:MyBatis Plus+SpringBoot重构商品管理的降维打击

从3天开发到10分钟交付,优雅应对复杂业务需求的架构实践。

开篇:被CRUD支配的恐惧

深夜的办公室里,实习生小张面对满屏的SQL映射文件几近崩溃:
“第17个模糊查询接口,字段名又写错了...”
你接过键盘,看着
GoodsMapper.xml中重复的<select>、<insert>、<update>标签——
这些千篇一律的CRUD代码,正吞噬着团队70%的开发时间
今天,你将用MyBatis Plus(MP)开启一场生产力革命。

第一章:传统MyBatis的七宗罪——为什么我们需要MP

1.1 CRUD代码的重复炼狱

<!-- 典型MyBatis映射文件片段 -->
<select id="selectById" resultType="Goods">
    SELECT * FROM goods WHERE id=#{id}
</select>
<insert id="insert" parameterType="Goods">
    INSERT INTO goods(name,price) VALUES(#{name},#{price})
</insert>
<!-- 20个类似方法后... -->

痛点清单

  • 字段硬编码:数据库变更需全局搜索替换。
  • SQL错误price拼成prcie导致生产事故。
  • 分页复杂:每个分页查询重复编写count语句。

1.2 MP的降维打击能力

MP不是语法糖,而是将CRUD抽象为声明式编程,让开发者专注业务逻辑。

第二章:10分钟极速交付——商品模块实战

下面用厨房烹饪比喻,带你感受MP的高效魔法。

2.1 食材准备:依赖配置(2分钟)

// 关键依赖
dependencies {
    implementation 'com.baomidou:mybatis-plus-boot-starter:3.5.3'
    implementation 'com.baomidou:mybatis-plus-generator:3.5.3'
    implementation 'org.freemarker:freemarker:2.3.32' // 代码生成模板
}
# application.yml 核心配置
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开发期SQL日志
  global-config:
    db-config:
      id-type: assign_id # 分布式ID策略
      logic-delete-field: deleted # 逻辑删除字段

2.2 切菜备料:实体类设计(3分钟)

@Data // Lombok自动生成getter/setter
@TableName("goods") // 绑定表名
public class Goods {
    @TableId(type = IdType.ASSIGN_ID) // 分布式雪花ID
    private Long id;
    
    @TableField("goods_name") // 字段映射
    @NotBlank(message = "商品名不能为空")
    private String name;
    
    @TableField
    @DecimalMin(value = "0.01", message = "价格必须≥0.01")
    private BigDecimal price;
    
    @TableField(fill = FieldFill.INSERT) // 自动填充
    private LocalDateTime createTime;
    
    @TableLogic // 逻辑删除标记
    private Integer deleted;
}

设计哲学

  • @TableField解耦字段与列名,避免硬编码。
  • 校验注解直接嵌入实体,实现声明式数据验证。
  • 自动填充减少业务无关代码。

2.3 烹饪核心:Mapper与Service(3分钟)

// ███ 只需声明接口 ███
public interface GoodsMapper extends BaseMapper<Goods> {
    // 复杂查询后期扩展
    @Select("SELECT * FROM goods WHERE price > #{minPrice} ORDER BY sales DESC")
    List<Goods> selectHotGoods(BigDecimal minPrice);
}

// ███ 服务层继承增强接口 ███
@Service
public class GoodsService extends ServiceImpl<GoodsMapper, Goods> {
    // 1. 自带全套CRUD方法
    // save(), removeById(), updateById(), getById(), page()...
    
    // 2. 自定义业务方法
    public List<Goods> searchGoods(String keyword) {
        return lambdaQuery()
            .like(Goods::getName, keyword)
            .or()
            .apply("JSON_CONTAINS(keywords, '" + keyword + "')") // JSON字段查询
            .list();
    }
}

魔法解析Service继承树提供150+开箱即用方法,覆盖90%业务场景。

2.4 出锅装盘:控制器暴露API(2分钟)

@RestController
@RequestMapping("/goods")
public class GoodsController {
    @Autowired
    private GoodsService goodsService;

    // ███ 分页查询 ███
    @GetMapping("/page")
    public Page<Goods> page(
        @RequestParam(defaultValue = "1") int current,
        @RequestParam(defaultValue = "10") int size,
        @RequestParam(required = false) String name) {
        
        return goodsService.page(new Page<>(current, size), 
            Wrappers.<Goods>lambdaQuery()
                .like(StringUtils.isNotBlank(name), Goods::getName, name)
        );
    }

    // ███ 逻辑删除 ███
    @DeleteMapping("/{id}")
    public boolean delete(@PathVariable Long id) {
        return goodsService.removeById(id); // 自动触发逻辑删除
    }
}

关键进步:相比传统Controller减少70%代码量。

第三章:生产级精进——复杂业务的优雅应对

基础CRUD只是起点,真实业务需要更强大的武器库。

3.1 条件构造器:动态SQL的终极方案

public Page<Goods> complexQuery(GoodsQuery query) {
    return goodsService.lambdaQuery()
        .like(StringUtils.isNotBlank(query.getKeyword()), Goods::getName, query.getKeyword())
        .gt(query.getMinPrice() != null, Goods::getPrice, query.getMinPrice())
        .lt(query.getMaxPrice() != null, Goods::getPrice, query.getMaxPrice())
        .in(!CollectionUtils.isEmpty(query.getCategoryIds()), Goods::getCategoryId, query.getCategoryIds())
        .eq(Goods::getStatus, 1) // 上架商品
        .orderByDesc(Goods::getSales) // 销量排序
        .page(new Page<>(query.getPage(), query.getSize()));
}

优势对比

方案

可读性

防SQL注入

代码量

XML动态SQL

需手动防

MP条件构造器

自动防护

少60%

3.2 自定义SQL:应对极端复杂场景

<!-- 位于GoodsMapper.xml -->
<select id="selectGoodsWithCategory" resultType="GoodsVO">
    SELECT g.*, c.name as categoryName 
    FROM goods g
    LEFT JOIN category c ON g.category_id = c.id
    WHERE g.id = #{id}
</select>
// Mapper接口增强
public interface GoodsMapper extends BaseMapper<Goods> {
    // ███ 注解方式 ███
    @Select("SELECT * FROM goods WHERE category_id = #{categoryId}")
    List<Goods> selectByCategory(@Param("categoryId") Long categoryId);
    
    // ███ XML方式 ███
    GoodsVO selectGoodsWithCategory(Long id);
}

设计平衡:80%简单操作用MP,20%复杂查询保留原生MyBatis灵活性。

3.3 多租户架构:SAAS系统必备

public class TenantInterceptor implements InnerInterceptor {
    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, 
                            Object parameter, RowBounds rowBounds, 
                            ResultHandler resultHandler, BoundSql boundSql) {
        // 自动注入租户ID
        String sql = boundSql.getSql();
        if (sql.contains("tenant_id")) return;
        
        String newSql = "SELECT * FROM (" + sql + ") tmp WHERE tenant_id = " + getCurrentTenantId();
        ReflectionUtils.setFieldValue(boundSql, "sql", newSql);
    }
}

安全策略:在SQL解析层自动隔离租户数据,避免越权风险。

第四章:工程化实践——从玩具到生产级应用

4.1 分层架构规范

src/main/java
├── controller      # 控制层
├── service         # 业务层
│   ├── impl        # 实现类
├── mapper          # 数据层
├── entity          # 实体类
├── dto             # 数据传输对象
│   ├── request     # 入参DTO
│   └── response    # 出参DTO
└── config          # 配置类
    └── MybatisPlusConfig.java

4.2 全局异常处理

@RestControllerAdvice
public class GlobalExceptionHandler {
    
    // 处理数据校验异常
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result handleValidException(MethodArgumentNotValidException e) {
        BindingResult result = e.getBindingResult();
        String message = result.getFieldErrors().stream()
                .map(FieldError::getDefaultMessage)
                .collect(Collectors.joining("; "));
        return Result.fail(400, message);
    }
    
    // 处理MyBatisPlus异常
    @ExceptionHandler(MybatisPlusException.class)
    public Result handleMybatisPlusException(MybatisPlusException e) {
        return Result.fail(500, "数据操作失败: " + e.getMessage());
    }
}

4.3 性能优化四板斧

优化点

方案

效果

分页插件

启用PageOptimize模式

减少1次count查询

二级缓存

整合Redis+注解@CacheNamespace

查询速度提升8倍

批量操作

saveBatch()设置批次大小1000

插入速度提升15倍

SQL打印

生产环境关闭log-impl

降低CPU消耗30%

第五章:代码生成器——终结重复劳动的核武器

5.1 生成器配置(核心代码)

FastAutoGenerator.create(dataSourceConfig)
    .globalConfig(builder -> builder.author("DevSquad") // 作者
        .outputDir("D://gen-code") // 输出目录
        .enableSwagger() // 开启swagger
    )
    .packageConfig(builder -> builder.parent("com.example.mall") // 包名
        .moduleName("product") // 模块名
    )
    .strategyConfig(builder -> builder.addInclude("goods", "category") // 表名
        .entityBuilder().enableLombok() // 实体策略
        .controllerBuilder().enableRestStyle() // REST风格
        .mapperBuilder().enableMapperAnnotation() // @Mapper
    )
    .templateEngine(new FreemarkerTemplateEngine()) // 模板引擎
    .execute();

5.2 生成效果

D://gen-code
├── goods
│   ├── GoodsController.java
│   ├── GoodsService.java
│   ├── GoodsServiceImpl.java
│   ├── GoodsMapper.java
│   ├── Goods.java # 实体类
│   └── GoodsDTO.java # DTO对象
└── category # 相同结构

效率对比:手动创建1个实体模块需30分钟 → 生成器10秒完成。

终章:CRUD的涅槃重生

当小张看到自动生成的代码时,眼睛亮了起来:
“原来商品模块可以这么快完成!”
你指着屏幕上的分层代码说:
“记住,真正强大的不是框架本身,而是它让我们摆脱低级重复,
将创造力倾注在真正改变业务的复杂逻辑上”。

系列预告

下一篇:《SpringCloud alibaba:微服务架构生死局》。

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言