ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

MyBatis与MyBatis-Plus核心区别及SpringBoot集成实践

MyBatis与MyBatis-Plus核心区别及SpringBoot集成实践 做Java后端天天跟数据库打交道MyBatis和MyBatis-Plus下面直接叫MP是绕不开的两个名字。很多人一开始会以为MP就是MyBatis的升级版其实这是个挺常见的误解。MyBatis是底层SQL映射框架负责把Java方法和SQL绑定起来而MP是在MyBatis之上做了一层“开箱即用”的封装把常用的CRUD、分页、逻辑删除、字段自动填充这些都内置好了。两者不是竞争关系而是“基座”和“增强工具”的关系。这篇文章我会结合自己实际项目里的经验把它们的核心区别、选型思路、SpringBoot集成方式、常见坑和面试高频题一次讲清楚。不管是刚入门的新手还是准备面试或者正在做技术选型的朋友都能从里面找到点有用的东西。1. 为什么纠结这两个核心区别与选型思路1.1 从JDBC到MyBatis我们到底在用什么先回到最底层。Java操作数据库原生的方式就是JDBC写起来极其痛苦要手动加载驱动、获取Connection、写PreparedStatement、处理ResultSet、关闭资源。后来出现了MyBatis它干了三件大事把SQL语句和Java代码解耦SQL写到XML或注解里不再散落在代码中。自动完成参数映射和结果集映射JavaBean和数据库字段之间的转换交给框架处理。内置了连接池、事务管理、缓存等能力开发效率比JDBC高出一个量级。所以MyBatis的本质是一个“半自动”的ORM框架。半自动的意思是SQL还是要自己写但参数处理和结果映射不用管。这个设计给它带来了极高的灵活性也带来了一个痛点如果真的只是简单的增删改查也要写一堆重复XML。Mapper接口里加五个方法XML里就要配五个对应id的SQL很机械。这时候MyBatis-Plus出现了。它不改变MyBatis的底层机制而是在MyBatis之上封装了一套通用能力。最核心的就是BaseMapper接口里面已经实现了insert、deleteById、selectById、selectList等方法。你只要让自己的Mapper接口继承BaseMapper然后什么都不用写就有了最基础的CRUD能力。这就是它叫“Plus”的原因不是替代MyBatis而是帮我把MyBatis中那些繁琐的重复劳动砍掉。1.2 MyBatis-Plus到底“Plus”在哪用一句话概括MyBatis解决的是“SQL映射”的问题MP解决的是“SQL都懒得写”的问题。MP的增强点主要集中在以下几个方面通用Mapper继承BaseMapper单表CRUD不用写SQL和XML。条件构造器通过QueryWrapper或LambdaQueryWrapper以链式方法拼接查询条件不用记XML里的动态SQL标签。分页插件基于MyBatis的拦截器机制实现物理分页传入Page对象即可。逻辑删除TableLogic注解删除时自动改成update语句。自动填充TableField(fill FieldFill.INSERT)等注解让createTime、updateTime自动赋值。乐观锁Version注解配合插件实现版本号判断。代码生成器一键生成Entity、Mapper、Service、Controller全套代码。这些功能不是MyBatis没有而是MP把它们做成了“约定大于配置”的默认能力。比如逻辑删除原生MyBatis里你要自己写UPDATE语句MP只要在实体字段上加一个注解然后全局配置一下delete值查询的时候它会自动拼接deleted0的条件。这件事在代码里完全透明能省下不少事。1.3 什么时候用MyBatis什么时候直接上MP结合我自己经手的项目选型逻辑其实很简单如果项目是单表操作为主、表结构相对规范可以无脑选MP开发效率提升非常明显。如果项目里大量复杂报表查询、多表关联、动态SQL非常考验数据库特性那一定要保留MyBatis原生的XML写法MP只用来做单表CRUD。如果项目里既有简单CRUD又有复杂查询那两者完全可以共存这也是目前很多团队的实际状态。如果团队里有严格的SQL审查规范、DBA要求所有SQL必须人工编写以控制执行计划那可以直接用MyBatisMP的自动SQL有时候让人不放心。所以我不太建议把MP当成万能药。它确实快但“自动化”意味着“不可见”在一些对SQL有极致要求的场景里控制力下降反而会成为风险点。2. 核心功能对比从CRUD到复杂查询差别比你想的大2.1 CRUDMP的BaseMapper省掉的不只是XML原生MyBatis写一个简单的按ID查询需要做两步Mapper接口里声明一个方法XML里写一段select语句。表一多这些模板代码会大量堆积。MP的BaseMapper直接帮我做了这个事public interface UserMapper extends BaseMapperUser { // 没有声明任何方法但已经有了 insert/deleteById/selectById/selectList 等 }调用的时候User user userMapper.selectById(1); userMapper.insert(user); userMapper.deleteById(2);这里的selectById、insert是BaseMapper里自带的MP在启动时通过MyBatis的Mapper注册机制为这些通用方法自动生成了对应SQL不需要我去XML里写。这个机制的核心在于泛型参数UserMP通过反射拿到实体的表名、字段名、主键名然后动态拼出SQL。有一点值得注意BaseMapper里所有方法都是单表操作永远不要想着用它来写关联查询。关联查询还是老老实实在XML里写或者用自定义Mapper方法。把简单和复杂分开代码结构才会清晰。2.2 条件构造器QueryWrapper与LambdaQueryWrapper实战这是很多初学者最容易懵的地方。QueryWrapper是什么其实就是MP提供的一个拼接查询条件的“积木盒”。举个实际场景查询名字叫“张三”且年龄大于18的用户列表只要性别是男。用LambdaQueryWrapper写LambdaQueryWrapperUser wrapper Wrappers.lambdaQuery(); wrapper.eq(User::getName, 张三) .gt(User::getAge, 18) .eq(User::getGender, 男); ListUser users userMapper.selectList(wrapper);这里eq代表等于gt代表大于方法名本身就是语义。用Lambda表达式User::getName好处是字段名不会写错因为它是类型安全的编译期就能发现错误。而如果使用老的QueryWrapperQueryWrapperUser wrapper new QueryWrapper(); wrapper.eq(name, 张三) .gt(age, 18) .eq(gender, 男);这样做SQL字段名是字符串一旦表结构修改这里就容易出现“运行时才发现列名不对”的问题。所以我个人在项目里是强制要求用LambdaQueryWrapper的。条件构造器里还有一个很好用的or方法。比如查询名字是“张三”或者年龄大于20的用户wrapper.eq(User::getName, 张三) .or() .gt(User::getAge, 20);一般我用的时候都会加括号控制优先级否则or可能会和后面的条件发生意外组合。比如再往后跟一个status1实际SQL会变成name张三 OR age20 AND status1逻辑就错了。遇到这种情况可以用lambda内部嵌套wrapper.and(w - w.eq(User::getName, 张三).or().gt(User::getAge, 20)) .eq(User::getStatus, 1);这个细节在参考网上一些踩坑帖的时候经常能看到也是我实际项目里踩过的坑必须拿出来说。2.3 分页插件PageHelper和MP分页配置对比分页是另一个高频需求。原生MyBatis时代很多人用PageHelper通过ThreadLocal方式在SQL执行前自动拼接limit语句。用起来确实方便但有个痛点PageHelper生效的前提是调用PageHelper.startPage()之后必须紧跟一条查询如果中间穿插了其它MyBatis操作分页可能被污染。MP的分页插件则更“刻意”。它采用传入Page对象的方式在Mapper方法参数里带上PageMyBatis在解析时就能拿到分页参数执行完SQL后Page对象里会带上total、current、size这些信息。示例PageUser page new Page(1, 10); PageUser result userMapper.selectPage(page, wrapper); ListUser users result.getRecords(); long total result.getTotal();使用MP分页时需要先注册一个分页插件Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }这个插件依赖MyBatis的Interceptor机制可以在SQL执行前拦截Executor把limit参数拼到SQL里去。这里要注意DbType必须配置正确否则不同数据库的方言拼接逻辑不一样很容易出问题。2.4 逻辑删除、自动填充、乐观锁这些“隐形”功能这三个功能属于用了就回不去的类型但也最容易在配置上出幺蛾子。逻辑删除指的是删除数据时不真正执行DELETE而是将记录标记为已删除。MP的做法是在实体字段上加TableLogic注解然后配置全局删除值和未删除值。比如TableLogic private Integer deleted;配置文件里这样写mybatis-plus: global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0这样我调用deleteById时MP会执行一条UPDATE语句把deleted置为1。后续所有查询MP都会自动追加deleted0条件。但要注意如果我在XML里自定义了SQL且没有手动加deleted0条件那么逻辑删除对这条SQL是无效的。所以逻辑删除适合纯MP操作自定义SQL里要自己注意。自动填充用来处理create_time、update_time这类字段。实体里加上TableField(fill FieldFill.INSERT) private Date createTime; TableField(fill FieldFill.INSERT_UPDATE) private Date updateTime;再实现一个MetaObjectHandlerComponent public class MyMetaObjectHandler implements MetaObjectHandler { Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, createTime, Date.class, new Date()); this.strictInsertFill(metaObject, updateTime, Date.class, new Date()); } Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, updateTime, Date.class, new Date()); } }这样在insert和update时公共字段不用每个实体都手动set统一入口维护代码干净不少。乐观锁用来解决并发修改的问题。实体里加Version字段注册OptimisticLockerInnerInterceptor插件更新时MP会生成类似“UPDATE xxx SET name?, versionversion1 WHERE id? AND version?”的SQL。如果更新影响行数为0说明数据已被别人改过需要业务层处理冲突。这三个功能背后都是MP对SQL的“改写”理解了这一点排查问题的时候就容易想到是不是MP把SQL改了而我没注意到。3. 实战集成SpringBoot里把两者玩明白3.1 依赖引入与配置别再把两个混在一起很多新手会同时引入mybatis-spring-boot-starter和mybatis-plus-boot-starter这其实容易引起类冲突。如果你决定用MP只需要引入一个dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.x/version /dependencyMP的starter已经包含了MyBatis的核心依赖不需要再单独加。还有一个细节如果项目里用了SpringBoot 3.x要注意选mybatis-plus-spring-boot3-starter不同版本的包路径不一样直接照抄老版本依赖很容易启动报错。配置文件里常用的是mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml type-aliases-package: com.example.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImplmap-underscore-to-camel-case设置为true后数据库下划线字段会自动映射到Java驼峰属性。这个配置在原生MyBatis和MP里都生效但MP的全局配置里还多了一些db-config字段所以要注意不要和MyBatis的configuration混为一谈。3.2 代码生成器表结构自动生成实体、Mapper、ServiceMP的代码生成器是一大亮点。官网文档虽然写得复杂但其实核心步骤很固定。以3.5.1版本为例FastAutoGenerator.create(jdbc:mysql://localhost:3306/test, root, password) .globalConfig(builder - builder.author(jason).outputDir(src/main/java)) .packageConfig(builder - builder.parent(com.example).entity(entity).mapper(mapper).service(service).controller(controller)) .strategyConfig(builder - builder.addInclude(user, order).addTablePrefix(t_, sys_)) .execute();这里addInclude指定要生成哪几张表addTablePrefix指定表前缀比如sys_user会生成User实体自动去掉sys_前缀。生成的Entity上会自动加上TableName、TableId等注解Mapper接口继承BaseMapper。用这个工具可以大大减少建实体和Mapper的机械工作。有一点必须提醒代码生成器生成的是“初始版本”后续表结构变更时如果重新生成会覆盖手工改动。我一般会关掉覆盖选项或者直接把生成的代码当成模板重要业务表还是手工调整字段。3.3 打印SQL的正确姿势配置项与Log插件排查问题时最需要看到真实执行的SQL和参数。最简单的方式是开启MyBatis的stdout日志mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl这样控制台会直接打印SQL语句参数通过问号占位下面preparing和parameters两行分别展示SQL和参数列表。缺点是日志比较啰嗦生产环境不建议开。更优雅的方式是用IDEA插件MyBatis Log Free它能拦截控制台里的Preparing和Parameters日志直接帮你还原成一条可执行的完整SQL方便复制到数据库客户端里调试。这个插件是我平时排查MP生成SQL时的必备工具。如果你用的是logback或log4j2那可以直接配置mapper接口包名为TRACE级别日志logging: level: com.example.mapper: debug这样也只会打印mapper相关的SQL日志比全局stdout更可控。3.4 当表不存在自动建表这个热点到底靠不靠谱最近“springboot mybatis 当表不存在自动建表”这个话题热度不低很多人想实现启动时检查表是否存在不存在就自动建表。这里我要泼盆冷水MyBatis和MP本身都不提供自动建表能力这个功能要么依赖数据库工具要么自己写初始化逻辑。我的做法是在系统启动时用Spring的ApplicationRunner或CommandLineRunner调用JdbcTemplate执行一段DDL检查Component public class TableInitRunner implements ApplicationRunner { Resource private DataSource dataSource; Override public void run(ApplicationArguments args) { try (Connection conn dataSource.getConnection()) { DatabaseMetaData meta conn.getMetaData(); try (ResultSet rs meta.getTables(null, null, user, new String[]{TABLE})) { if (!rs.next()) { // 执行 CREATE TABLE 语句 } } } } }这种方式适合小型项目或演示环境。生产环境我强烈建议用正式的数据表管理工具比如Flyway或Liquibase把表结构变更纳入版本管理。自动建表适合关键业务表缺失时的兜底恢复但不应该成为常态依赖。需要注意的是有些MP相关的工具类或第三方库会宣称支持自动建表但本质还是根据实体类生成建表SQL。这类方案看起来方便实际字段类型、索引、分表分区都很难精确控制不适合复杂业务。简单场景用用可以核心表千万别图省事。4. 避坑指南多模块、Lombok、若依和缓存那些事4.1 多模块工程下MapperScan扫描路径怎么配现在的项目基本都是Maven多模块结构比如api模块、service模块、mapper模块。MP在多模块下最常遇到的问题就是Mapper接口扫描不到。一般会在启动类或配置类上配置MapperScan指定mapper接口所在包SpringBootApplication MapperScan(com.example.project.**.mapper) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }这里有个坑如果mapper接口分散在多个模块的多个包下MapperScan写成固定一个包路径会漏掉其它模块。解决方法是写多个扫描路径或者用一个公共的包名统一管理比如所有模块的mapper都放在com.example.common.mapper底下扫描就简单了。还有一种场景是父模块扫描到了子模块里的Class但子模块的Mapper XML文件没有放到mapper-locations指定的路径下导致“Invalid bound statement (not found)”错误。这时候要检查target目录下是否有对应的XML文件如果没打进去多半是resources插件没把src/main/java下的xml文件打包需要在pom里额外配置 。4.2 Lombok和MP实体类的相爱相杀MP的实体类经常配合Lombok用比如Getter Setter ToString等。Lombok帮我们生成getter/setterMP的LambdaQueryWrapper依赖的是实体字段对应的getter方法所以两者配合起来很流畅。但有些坑需要注意实体类上用了Builder后lombok会生成一个全参构造器如果没有别的构造器MyBatis通过反射实例化对象时会失败因为找不到默认构造器。解决办法是手动加NoArgsConstructor和AllArgsConstructor。Accessors(chain true)开启链式setterMP的部分场景可能会有兼容问题比如在某些序列化场景下出现字段无法赋值。不建议全局开启。使用TableField(exist false)标记非数据库字段时Lombok的Getter Setter正常使用没问题但如果用了Builder也留意上面说的默认构造器问题。我之前遇到过一个诡异问题实体类引用了LombokJSON序列化时少字段后面排查发现是类上同时用了TableField(exist false)和JsonProperty但Lombok生成的getter和Jackson的命名映射对不上。这类问题往往不是MP本身的bug而是多个库之间的注解冲突。4.3 我在若依框架里用MP的踩坑记录若依RuoYi是国内很多团队使用的后台管理脚手架早期版本默认用的是MyBatis后面也有人把它改成MP来用。若依自带了一套BaseController里面封装了很多分页、导出等通用逻辑它依赖于原生的PageHelper和自定义SQL。如果在若依里引入MP最好注意这几点若依的BaseEntity里有createBy、createTime、updateTime等公共字段MP自动填充会和若依的手动set冲突。若依默认是在Service里自己set值所以MP的自动填充可以不配否则两者重叠反而会乱。若依的通用查询条件是基于Map的它会在调用Mapper前判断条件是否为空然后手动拼SQL。MP的LambdaQueryWrapper在这种架构里用得不多不要强行把若依原有的Mapper改成BaseMapper否则业务代码兼容成本太高。若依的一些Controller会继承BaseController里面用到PageDomain和TableDataInfoMP的Page对象不能直接转换过去最好做一个适配方法。我的建议是改造若依这种完整脚手架之前先摸清楚它在哪一层做了数据访问封装。能局部引入MP就局部引入不要一上来就把Mapper全部替换。很多团队把若依和MP一起用最后都是“MyBatis写常规SQL MP做简单表操作”的混合模式这样兼容性最好。4.4 MyBatis缓存一级二级缓存到底开不开MyBatis的一级缓存默认是开启的作用范围是SqlSession。在Spring管理下每次Mapper操作通常都对应一个新的SqlSession所以一级缓存的生命周期很短基本没什么感知。二级缓存默认是关闭的需要全局配置和Mapper XML里的 标签配合才能开启。在实际项目里我对缓存的态度是“谨慎”。MP虽然集成了MyBatis内置的BaseMapper方法也会走MyBatis的缓存流程但缓存不是白开的缓存的数据更新时机依赖缓存更新策略如果不同Mapper写了相同的SQL很容易出现“数据改了缓存没清”的问题。分布式环境下节点之间没有缓存同步机制需要引入Redis等外部缓存MyBatis的二级缓存意义就会减弱。如果表数据实时性要求高比如余额、库存这种缓存开起来就是给自己挖坑。所以“MyBatis缓存”这个面试常客讲解时我会说一级缓存默认存在但作用有限二级缓存能不开就不开。业务缓存尽量交给Redis或Caffeine这类外部组件控制力更好出问题也更容易排查。5. 动态SQL与全局配置面试官常问的底层细节5.1 if标签语法和where拼接动态SQL的常见坑MyBatis最强的地方就是动态SQL 标签几乎人人会用但坑也最多。典型场景多条件查询参数为空时不拼接条件。select idselectByCondition resultTypecom.example.entity.User SELECT * FROM user where if testname ! null and name ! AND name #{name} /if if testage ! null AND age gt; #{age} /if /where /select标签会智能去掉第一个多余的AND或OR这一点非常实用。如果不用 自己写WHERE 11来支持动态拼接也能跑但SQL不是那么优雅而且有的DBA不喜欢。用 时必须注意字符串判断空用name ! null and name ! 数字类型只要判断null。XML里比较特殊字符比如大于号要写成小于号写成否则XML解析会直接报错。如果条件里用了list或array参数配合 遍历时collection属性的值是list、array或者自定义参数名千万别写错。和MP的LambdaQueryWrapper比XML动态SQL的优点是灵活适合多表关联或数据库特有语法缺点是写起来冗长不如Wrapper直观。两种方式我都会用简单单表查询用Wrapper复杂报表或关联查询用XML。5.2 mybatis-config.xml中的核心标签与配置顺序很多人只会在SpringBoot里写配置很少关心MyBatis的全局配置文件mybatis-config.xml。面试官喜欢问里面有哪些标签其实只要看着DTD顺序记住类型就好读取外部属性文件用来在配置里占位。全局配置项比如mapUnderscoreToCamelCase、cacheEnabled、lazyLoadingEnabled。给实体类起别名这样在XML里写resultType时可以不用全限定类名。类型处理器负责Java类型和JDBC类型之间的转换。自定义结果对象工厂。拦截器配置MyBatis插件都挂在这里。环境配置包括事务管理器和数据源。多数据库厂商支持让同一套XML根据数据库类型选择不同SQL。扫描Mapper文件或接口。日常开发中SpringBoot通过mybatis-plus.configuration.*或mybatis.configuration.*配置项就能覆盖settings里的内容不一定直接编辑xml。但理解这些标签的含义有助于排查“为什么我这个配置没生效”这样的问题。5.3 源码层面MyBatis的执行流程与MP的增强原理先串一遍MyBatis的执行流程从Mapper接口调用到底层SQL执行大概是这样注入的Mapper其实是一个动态代理对象调用某个方法时代理类通过MapperMethod找到对应MapperStatement。MapperStatement里保存了SQL的id、SQL来源XML或注解、参数映射、结果映射、语句类型等信息。执行器Executor负责执行SQL它有BaseExecutor和CachingExecutor等实现二级缓存就是通过装饰器模式套在Executor外面实现的。执行过程中会经过StatementHandler、ParameterHandler、ResultSetHandler一系列处理器分别负责创建JDBC Statement、设置参数、处理结果集。MP的源码重点在于它是如何增强MyBatis的。核心是MybatisPlusInterceptor它实现了MyBatis的Interceptor接口通过拦截Executor和StatementHandler在SQL执行前后进行改写。比如分页插件就是在Executor执行query方法前先解析参数里的Page对象然后拼接limit乐观锁插件则是在执行update时把实体里的version字段加到where条件和set子句中。还有一个地方值得注意MP在Mapper注册阶段会通过继承BaseMapper的接口自动注册一批内部方法。这些方法都有对应预先生成的SQL串比如selectById对应的SQL就是“SELECT ... FROM user WHERE id?”。如果我们把BaseMapper里的某个方法在XML里重写XML里的SQL会覆盖默认实现。所以有时候项目里看到自定义XML里的selectById生效就是这个原因。理解源码不一定能立刻带来开发效率提升但排查奇怪问题时会非常有帮助。比如某个查询莫名多出了LIMIT或者delete语句变成了update第一时间就会想到是不是MP插件和逻辑删除在起作用。6. 高频面试题速答MyBatis和MyBatis-Plus的区别6.1 面试题合集这些问题你答得上来吗面试中关于数据库访问层的题目翻来覆去就那几个方向。整理一份高频清单供临时抱佛脚MyBatis和MBIbatis-Plus的区别是什么 答MyBatis是SQL映射框架需要手写SQL和XMLMP在它之上封装了通用Mapper、条件构造器、分页插件等单表CRUD不用写SQL。MP为什么能省掉SQL 答通过泛型反射拿到实体类上的TableName、TableId等注解动态生成通用SQL利用MyBatis的Mapper注册机制把这些SQL注册到MapperStatement。实现一个分页插件思路是什么 答实现Interceptor接口拦截Executor的query方法在方法执行前修改BoundSql追加limit参数再通过反射或重新创建BoundSql执行。#{}和${}有什么区别 答#{}是预编译参数占位用PreparedStatement的?占位${}是字符串拼接一般用于动态表名、列名有SQL注入风险必须谨慎使用。MyBatis的一级、二级缓存知道吗 答一级缓存是SqlSession级别默认开启二级缓存是Mapper级别跨SqlSession需要配置 开启实际项目中要结合分布式缓存场景慎重使用。有没有遇到过Mapper方法无法注入的问题 答常见的因为扫描路径不对或XML没打包进target目录检查MapperScan、mapper-locations、pom资源过滤配置。聊聊你用过的MyBatis插件 MP的分页插件、乐观锁插件都有接触本质上是用Interceptor机制在Executor层做SQL改写。碰到这些问题时不要只背书可以结合项目里的例子比如“我在分页插件上踩过数据库方言的坑”这类经验。面试官往往更喜欢听有血有肉的回答。6.2 我自己的判断项目里怎么选、怎么答如果项目允许我现在的选择是以MP为默认开发底座复杂查询用XML原生SQL简单查询用LambdaQueryWrapper。这样既能享受MP的高效率又能保留MyBatis的灵活性。具体落地时我会有几条规矩禁止在XML里写单表简单查询统一用Wrapper。多表关联、子查询、批量插入、复杂更新一律走XML。XML里所有查询字段都要列出具体列名禁止SELECT *。逻辑删除字段在自定义SQL里要自己加判断不能依赖MP拦截。Mapper接口只做数据访问不写业务逻辑条件构造器的使用范围限定在Service层。这套规矩在多个项目里验证过效率和可维护性都不错。如果你还在纠结选MyBatis还是MP不妨先拿一个中小型模块实验三周用真实业务验证一下再决定比自己空想靠谱得多。结尾最后分享一点实战体会我踩过最大的坑是“过度依赖MP的自动化”。有一段时间看到MP能自动生成SQL就把所有表操作都丢给它结果在复杂统计场景里跑出全表扫描慢查询一堆。后来我才意识到MP省的是重复劳动不是思考。简单操作交给它复杂操作自己掌控SQL这是最舒服的状态。另外如果你的团队正在做技术升级建议先统一规范再升级框架。比如先约定好多模块扫描路径、实体类字段命名规则、全局配置项再切到MP否则上线后一定会遇到一堆“莫名其妙”的问题。最后再分享一个调试技巧遇到MP生成的SQL和自己预期不一致时把日志里的preparing和parameters复制到数据库工具里手动执行排错速度会快很多。
返回列表