尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

MyBatis高级实战:动态SQL、插件开发与性能优化深度解析

MyBatis高级实战:动态SQL、插件开发与性能优化深度解析 1. 项目概述MyBatis的“奇巧淫技”到底是什么干了这么多年Java后端开发MyBatis这个老朋友几乎天天见。从最早的iBatis时代用到现在我敢说90%的开发者都只用了它30%的功能。大家日常就是写个Mapper接口配个XML然后select * from table where id #{id}。这没错MyBatis的核心设计就是让SQL回归本位简单直接。但如果你以为MyBatis就这点能耐那可就错过了太多好东西。所谓“奇巧淫技”并不是指那些偏门、冷僻、不推荐在生产环境使用的“黑魔法”。恰恰相反它们大多是MyBatis官方提供但被文档一笔带过、或者因为使用场景不那么普遍而被大家忽略的高级特性和最佳实践。这些技巧能让你在解决特定复杂问题时代码更优雅、性能更高效、排查问题更迅速。比如你知道怎么让MyBatis批量插入十万条数据时内存不炸、速度还快吗你知道怎么在XML里优雅地处理动态SQL的“大于等于”转义而不用在Java代码里拼接字符串吗又或者当你的updateById只想更新非空字段而忽略null值时该怎么配置才最稳妥这些就是我想跟你分享的。这篇文章不打算从零开始讲MyBatis怎么配置、怎么用那是新手教程的事。咱们聊的是你在熟练使用MyBatis之后如何把它“压榨”得更彻底写出更专业、更健壮的持久层代码。无论你是正在被复杂动态查询困扰还是对性能调优有要求或者只是想在下一次面试时能侃侃而谈MyBatis的底层原理这里都有你想看的内容。2. 核心原理与工作机制深度拆解在玩转那些高级技巧之前我们必须先回到原点把MyBatis的核心工作原理吃透。很多看似“神奇”的操作其本质都源于对这套机制的理解。你可以把MyBatis想象成一个高度定制化的SQL执行引擎它位于JDBC之上帮你处理了所有繁琐的重复劳动。2.1 从接口调用到SQL执行的完整链路当你调用userMapper.selectById(1)时背后发生了一系列精密的操作。首先MyBatis通过动态代理技术为你的Mapper接口生成了一个代理对象。这个代理对象并不包含任何具体的数据库操作逻辑它的核心是一个MapperMethod对象。MapperMethod内部封装了两个关键信息SqlCommand包含了要执行的SQL语句的ID即namespace.id和MethodSignature包含了方法参数、返回类型等信息。调用发生时代理对象会将请求转发给SqlSession。SqlSession是MyBatis的核心API你可以把它看作是一次数据库会话的指挥官。它手里有一份非常重要的地图——Configuration对象。这个全局配置对象是一个单例在应用启动时就被创建并初始化完毕里面缓存了所有解析好的Mapper XML文件、映射关系、以及各种插件Interceptor。SqlSession根据SqlCommand中的SQL ID从Configuration里找到对应的MappedStatement。这是一个重量级对象它完整描述了一条SQL语句的一切其SQL源码可能包含动态标签如if、foreach、参数映射关系、结果集映射关系、缓存策略、执行器类型等。接着SqlSession会委托给Executor执行器来执行。Executor主要有三种类型SIMPLE简单执行器每次执行都创建新的Statement、REUSE复用执行器复用预编译的Statement、BATCH批处理执行器。Executor会先查询一级缓存本地缓存SqlSession级别如果未命中则调用StatementHandler来创建JDBC的PreparedStatement对象。这里有个关键角色ParameterHandler。它负责将Java方法传入的参数按照MappedStatement中定义的映射规则设置到PreparedStatement的占位符?上。这也是#{}和${}产生区别的根本环节。#{}会被解析为?然后由ParameterHandler安全地设置参数值能有效防止SQL注入。而${}则是直接的字符串替换在SQL解析阶段就被替换成了实际的参数值。最后StatementHandler执行SQL并通过ResultSetHandler将返回的ResultSet按照结果映射规则转换成我们定义的Java对象无论是单个实体、List还是Map。至此一次完整的调用结束。注意理解这条链路至关重要。后续我们讨论的插件开发、缓存机制、批处理优化都是通过拦截或增强这条链路上的某个环节来实现的。比如打印SQL的插件通常就是拦截StatementHandler的prepare或parameterize方法。2.2 配置、映射与执行上下文的核心类解析很多面试官喜欢问“MyBatis的核心类有哪些”他们想听的其实就是对上面这条链路的抽象概括。我们来梳理一下SqlSessionFactoryBuilderSqlSessionFactory工厂的建造者和工厂本身。前者通过解析XML或注解配置来构建后者后者是生产SqlSession的工厂线程安全应用生命周期内通常只有一个实例。SqlSession代表一次数据库会话。它不是线程安全的因此最佳实践是在方法范围内使用用完即关或交由框架如Spring管理其生命周期。它提供了增删改查等基础API。ExecutorSQL执行器是SqlSession背后的真正执行者。它负责缓存维护、事务管理、以及将数据库操作委托给StatementHandler。插件常通过拦截它来实现分页、缓存等全局功能。MappedStatement映射语句。它封装了一条SQL的所有配置信息是Configuration这个“大仓库”里最重要的存货之一。一个namespace.id唯一对应一个MappedStatement。StatementHandler语句处理器。它负责创建Statement对象、参数化、执行SQL。它是JDBC层操作的直接封装。ParameterHandlerResultSetHandler参数处理器和结果集处理器。前者负责将Java参数转换成JDBC参数后者负责将JDBC返回的ResultSet转换成Java对象。理解这些类的职责你就能明白为什么修改某个配置会生效以及插件应该拦截哪个对象。例如你想在SQL执行前后打印日志拦截StatementHandler的query或update方法是最直接的。3. 动态SQL的实战技巧与避坑指南动态SQL是MyBatis最强大的特性之一它让我们能摆脱在Java代码中拼接SQL字符串的噩梦。但用好它也需要一些技巧。3.1 超越if和where的高级用法if、where、set、foreach是大家最熟悉的标签。但组合使用时有些细节需要注意。场景一如何优雅处理“大于等于”等符号的转义在XML中、、等字符有特殊含义直接写age 18会导致XML解析错误。常见的做法是使用转义字符gt;和lt;。if testminAge ! null AND age gt; #{minAge} /if if testmaxAge ! null AND age lt; #{maxAge} /if但这样写可读性稍差。另一种更优雅的方式是使用![CDATA[ ]]包裹整个SQL片段。但要注意CDATA块内不能再包含CDATA结束符]]且动态SQL标签如if在CDATA块内会失效。因此更推荐的做法是将可能包含特殊符号的条件判断单独用CDATA包裹或者直接使用转义。我个人更倾向于使用转义因为它更清晰且IDE通常会有高亮提示。场景二foreach批量操作时的性能陷阱foreach常用于IN查询或批量插入。但在处理大量数据比如上万条的IN查询时直接拼接成id in (1,2,3,...,10000)会导致SQL语句极长数据库解析效率下降还可能触及数据库URL或包的长度限制。SELECT * FROM user WHERE id IN foreach collectionids itemid open( separator, close) #{id} /foreach对于超长列表更好的方案是分批次查询或者考虑使用临时表、子查询等方式。对于批量插入务必与ExecutorType.BATCH配合使用才能获得真正的性能提升否则只是多条INSERT语句的循环执行。3.2choose、trim与bind的妙用choose相当于Java里的switch-case在多种互斥条件中选择一个非常方便。trim标签比where和set更灵活可以自定义前缀、后缀以及要忽略的字符。例如一个复杂的更新语句update idupdateUserSelective UPDATE user trim prefixSET suffixOverrides, if testusername ! nullusername #{username},/if if testemail ! nullemail #{email},/if if teststatus ! nullstatus #{status},/if updated_time NOW() /trim WHERE id #{id} /update这里我们用trim确保了即使所有if条件都不成立updated_time NOW()这句也会被设置并且会自动去掉最后一个多余的逗号。bind标签允许你在XML中创建一个变量并将其绑定到上下文。它有两个非常实用的场景模糊查询的简洁写法避免在参数中手动拼接%。select idselectByKeyword bind namepattern value% keyword % / SELECT * FROM product WHERE name LIKE #{pattern} OR description LIKE #{pattern} /select复用复杂的OGNL表达式如果某个判断条件非常复杂可以用bind计算一次后面多处引用提高可读性。4. 结果映射的深水区与高级玩法结果映射ResultMap是将数据库列与Java对象属性关联起来的桥梁。除了基本的result映射它还有一些高级特性可以解决复杂问题。4.1 处理“一对多”、“多对一”的N1查询问题经典的关联查询场景查询订单Order及其明细OrderItem。一种做法是在OrderMapper.xml中配置一个复杂的ResultMap使用collection标签。resultMap idOrderWithItemsResultMap typeOrder id propertyid columnorder_id/ result propertyorderNo columnorder_no/ collection propertyitems ofTypeOrderItem id propertyid columnitem_id/ result propertyproductName columnproduct_name/ result propertyquantity columnquantity/ /collection /resultMap select idselectOrderWithItems resultMapOrderWithItemsResultMap SELECT o.id as order_id, o.order_no, i.id as item_id, i.product_name, i.quantity FROM order o LEFT JOIN order_item i ON o.id i.order_id WHERE o.id #{orderId} /select这种方式通过单条SQL联表查询解决了问题。但有时出于分库分表或查询性能的考虑我们可能需要分开查询。这时collection标签的select属性就派上用场了它可以指定另一个Mapper查询来填充集合。但这会引发著名的“N1查询问题”先查1个订单再为这个订单查N个明细。MyBatis提供了FetchType.LAZY懒加载来缓解但需要在配置中开启全局懒加载或使用fetchTypelazy属性并且要确保SqlSession在获取数据时依然存在在Web应用中通常通过OpenSessionInView模式或确保在事务内完成所有数据获取来解决。4.2 类型处理器与枚举映射的最佳实践TypeHandler类型处理器是MyBatis用于在Java类型和JDBC类型之间进行转换的组件。自定义TypeHandler非常有用。场景优雅地处理数据库字段与Java枚举的映射。假设数据库user表有一个status字段TINYINT对应Java枚举类UserStatusACTIVE(1), DISABLED(0)。默认情况下MyBatis不知道如何转换。我们有几种方案使用MyBatis内置的枚举处理器在配置中设置defaultEnumTypeHandlerorg.apache.ibatis.type.EnumOrdinalTypeHandler它会使用枚举的ordinal()值声明顺序从0开始进行映射。但这很脆弱修改枚举顺序会导致数据错乱。使用EnumTypeHandler它会使用枚举的name()字符串进行映射。这要求数据库存的是字符串如ACTIVE。自定义TypeHandler推荐实现BaseTypeHandler接口完全掌控映射逻辑。例如让status字段的整数值与枚举的自定义code属性映射。MappedTypes(UserStatus.class) MappedJdbcTypes(JdbcType.TINYINT) public class UserStatusTypeHandler extends BaseTypeHandlerUserStatus { Override public void setNonNullParameter(PreparedStatement ps, int i, UserStatus parameter, JdbcType jdbcType) throws SQLException { ps.setInt(i, parameter.getCode()); // 写入数据库的是枚举的code } Override public UserStatus getNullableResult(ResultSet rs, String columnName) throws SQLException { int code rs.getInt(columnName); return UserStatus.fromCode(code); // 从code还原枚举 } // ... 其他重载方法 }然后在mybatis-config.xml中注册这个处理器或者在Mapper XML的resultMap中指定result columnstatus propertystatus typeHandlercom.example.handler.UserStatusTypeHandler/这种方式最健壮逻辑清晰且与数据库存储解耦。5. 插件开发深入MyBatis执行腹地插件Interceptor是MyBatis的扩展利器。通过实现Interceptor接口我们可以拦截四大核心对象Executor、StatementHandler、ParameterHandler、ResultSetHandler的方法调用从而实现分页、数据权限过滤、SQL性能监控、自定义日志等全局功能。5.1 实现一个SQL执行时间统计插件假设我们想监控所有SQL的执行耗时超过一定阈值就告警。我们可以拦截StatementHandler的query和update方法。Intercepts({ Signature(type StatementHandler.class, method query, args {Statement.class, ResultHandler.class}), Signature(type StatementHandler.class, method update, args {Statement.class}) }) Component public class SqlCostTimeInterceptor implements Interceptor { private static final long THRESHOLD 1000; // 阈值1秒 Override public Object intercept(Invocation invocation) throws Throwable { long startTime System.currentTimeMillis(); try { // 继续执行原方法即执行SQL return invocation.proceed(); } finally { long cost System.currentTimeMillis() - startTime; StatementHandler statementHandler (StatementHandler) invocation.getTarget(); BoundSql boundSql statementHandler.getBoundSql(); String sql boundSql.getSql(); if (cost THRESHOLD) { // 记录慢SQL日志可以发送到监控系统 log.warn(Slow SQL detected, cost: {}ms, SQL: {}, cost, sql); } // 也可以记录所有SQL的执行时间 log.debug(SQL executed, cost: {}ms, cost); } } Override public Object plugin(Object target) { return Plugin.wrap(target, this); } Override public void setProperties(Properties properties) { // 可以从配置文件中读取阈值等参数 } }关键点Intercepts和Signature注解指定了要拦截的目标类和方法。invocation.proceed()代表执行原方法在其前后加上计时逻辑即可。通过statementHandler.getBoundSql()可以获取到即将执行的SQL此时参数#{}已被替换为?和参数对象。插件的生效需要在MyBatis配置中注册如果使用Spring Boot通常通过Component自动扫描即可。5.2 分页插件原理浅析与自定义思考像PageHelper这样的分页插件其核心原理就是拦截Executor的query方法。在查询执行前插件会根据分页参数页码、每页条数和数据库方言MySQL、Oracle等对原SQL进行重写生成一条查询总数的COUNT语句和一条带LIMIT/ROWNUM的分页查询语句。然后先执行COUNT查询再执行分页查询最后将结果封装到PageInfo对象中。如果你想自己实现一个简单的分页理解这个流程就够了。但强烈建议直接使用成熟的PageHelper因为它处理了各种数据库的兼容性、线程安全、以及Order by、嵌套查询等复杂情况。实操心得开发插件时务必注意性能。插件的intercept方法会在每次SQL执行时都被调用里面的逻辑要尽可能轻量。避免进行复杂的IO操作或远程调用。另外插件的执行顺序由配置顺序决定多个插件可能形成责任链要清楚自己的插件在链中的位置。6. 缓存机制剖析与正确使用姿势MyBatis提供了一级缓存和二级缓存。理解它们是避免踩坑和进行性能优化的基础。6.1 一级缓存SqlSession级别的“临时备忘录”一级缓存默认开启它是SqlSession级别的。在同一个SqlSession中执行两次相同的查询相同的SQL和参数第二次会直接返回缓存的结果不会访问数据库。SqlSession session sqlSessionFactory.openSession(); UserMapper mapper session.getMapper(UserMapper.class); User user1 mapper.selectById(1); // 访问数据库 User user2 mapper.selectById(1); // 从一级缓存获取 session.close();失效条件执行了任何增删改操作insert、update、delete无论是否影响当前缓存的数据整个一级缓存都会被清空。这是为了保持数据一致性。手动调用sqlSession.clearCache()。对MapperXML中的select标签配置flushCachetrue。不同的SqlSession之间缓存不共享。避坑指南在Spring管理的环境中通常将SqlSession的生命周期与事务绑定如使用Transactional。这意味着在同一个事务方法内多次查询相同数据会命中一级缓存。但如果你在两次查询之间执行了更新操作缓存会被清空第二次查询会再次访问数据库。这有时会导致性能预期不一致。另外在分布式环境下一级缓存完全无效因为它无法跨JVM共享。6.2 二级缓存namespace级别的“共享黑板”二级缓存是Mappernamespace级别的多个SqlSession可以共享。它默认是关闭的需要显式开启。开启步骤在全局配置mybatis-config.xml中设置setting namecacheEnabled valuetrue/Spring Boot中默认就是true。在需要开启二级缓存的Mapper.xml文件中添加cache/标签。对应的实体类必须实现Serializable接口因为缓存数据可能需要序列化后存储或传输。工作机制当SqlSession关闭或提交时它的一级缓存内容才会被“移交”到二级缓存中。因此其他SqlSession才能看到被缓存的数据。风险与注意事项脏读风险这是二级缓存最大的问题。如果应用有多个实例或者同一个实例中多个SqlSession修改了同一份数据二级缓存无法感知会导致读到旧数据。因此在读写频繁、对一致性要求高的场景下不建议使用二级缓存。配置复杂可以通过cache标签配置回收策略LRU、FIFO等、刷新间隔、缓存大小等。作用域二级缓存是namespace级别的。如果两个Mapper操作同一张表但namespace不同它们的缓存是隔离的可能导致数据不一致。可以通过cache-ref来引用其他namespace的缓存但不推荐会使依赖关系变得复杂。个人建议对于大多数Web应用尤其是分布式微服务架构我倾向于关闭二级缓存。数据一致性带来的麻烦远大于缓存带来的性能收益。性能优化应优先考虑数据库索引、SQL优化、以及使用Redis等集中式缓存。一级缓存由于其作用域小在事务内自动管理可以保留。7. 性能优化与批量操作实战当数据量上来后批量操作的效率天差地别。MyBatis对批量操作有很好的支持。7.1 真正的批处理ExecutorType.BATCH很多人误以为在foreach里循环执行insert就是批处理。其实那只是多次单条执行每次都要网络IO、数据库解析SQL、执行、返回结果效率很低。真正的批处理需要将Executor类型设置为BATCH。// 1. 获取一个批处理模式的SqlSession SqlSession session sqlSessionFactory.openSession(ExecutorType.BATCH); UserMapper mapper session.getMapper(UserMapper.class); try { for (int i 0; i 10000; i) { User user new User(...); mapper.insert(user); // 每积累一定数量如500条提交一次避免内存溢出 if (i % 500 0) { session.flushStatements(); } } // 最终提交事务 session.commit(); } catch (Exception e) { session.rollback(); throw e; } finally { session.close(); }原理在BATCH模式下PreparedStatement会被复用。mapper.insert(user)并不会立即执行而是将SQL和参数预编译并添加到批处理队列中。当调用session.flushStatements()或session.commit()时才会一次性将队列中的所有操作发送到数据库执行这大大减少了网络往返和数据库开销。对应的Mapper XMLinsert idinsert INSERT INTO user (name, email) VALUES (#{name}, #{email}) /insert注意这里仍然是单条INSERT语句。BATCH执行器优化的是执行方式而不是SQL本身。7.2 批量插入的SQL优化对于MySQL等数据库还可以使用INSERT INTO ... VALUES (...), (...), ...的语法来进一步提升批量插入速度。这需要我们在XML中动态生成SQL。insert idbatchInsert parameterTypejava.util.List INSERT INTO user (name, email) VALUES foreach collectionlist itemitem separator, (#{item.name}, #{item.email}) /foreach /insert然后在Java中一次性传入一个ListUser。这种方式比BATCH模式更快因为它将多条数据合并为一条SQL语句。但是有局限性1. SQL长度可能超限通过分批解决2. 某些数据库对单条SQL的参数个数有限制3. 如果某一行数据有问题可能导致整个批次失败。如何选择数据量不大几百几千条追求简单使用foreach生成多值INSERT语句。数据量巨大数万以上或需要更好的错误处理希望部分失败不影响全部使用ExecutorType.BATCH模式。终极方案对于海量数据导入考虑使用数据库原生的批量加载工具如MySQL的LOAD DATA INFILE。7.3 分页查询的深度优化分页查询尤其是深度分页limit 100000, 20是性能杀手。因为数据库需要先扫描并跳过前面的10万条记录。优化方案一基于主键的“上一页/下一页”式分页适合无限滚动或只有“上一页/下一页”的场景。记录上一页最后一条记录的主键ID。select idselectUsersAfterId resultTypeUser SELECT * FROM user WHERE id #{lastId} ORDER BY id ASC LIMIT #{pageSize} /select这种方式效率极高但无法直接跳转到任意页码。优化方案二子查询优化对于需要跳转的深度分页可以尝试SELECT * FROM user WHERE id (SELECT id FROM user ORDER BY id LIMIT 100000, 1) ORDER BY id LIMIT 20;先通过子查询快速定位到起始位置的ID再进行查询比直接limit 100000, 20要好一些。关于PageHelper的pageSize-1网络热词里有人问设置pageSize为-1是不是就能查全部这取决于PageHelper的具体版本和实现。在早期某些版本中这或许是一个“后门”参数用于绕过分页。但这绝不是官方推荐的做法而且可能在新版本中失效。正确的做法是如果你需要不分页查询全部应该使用一个不同的Mapper方法里面不包含分页逻辑或者使用PageHelper.startPage(1, Integer.MAX_VALUE)这种更明确但依然不优雅的方式。最清晰的设计是分页查询和全量查询应该是两个独立的接口。8. 集成Spring Boot的现代实践与问题排查现在几乎都是Spring Boot的天下了集成MyBatis变得异常简单但也有一些坑需要注意。8.1 配置打印执行的SQL日志这是调试中最常用的功能。在Spring Boot中不再需要复杂的Log4j配置只需在application.yml中设置MyBatis和对应数据库驱动包的日志级别即可。logging: level: # 你项目Mapper接口所在的包 com.example.mapper: DEBUG # MyBatis核心执行器的日志会显示SQL、参数、结果行数 org.apache.ibatis: INFO # 显示JDBC层面的具体操作包括SQL执行和事务提交等信息最全 org.springframework.jdbc.core.JdbcTemplate: DEBUG # 数据库连接池如HikariCP的日志看连接获取释放 com.zaxxer.hikari.HikariConfig: DEBUG com.zaxxer.hikari: TRACE更精细的控制可以使用MyBatis官方提供的mybatis-log-plugin等IDE插件它们可以将控制台输出的预编译SQL带?还原成带实际参数的完整SQL便于直接拷贝到数据库客户端执行。但生产环境切记关闭DEBUG日志否则日志量会爆炸。8.2 经典问题排查实录问题一updateById更新时字段为null的值被忽略没有更新到数据库。这通常是使用了MyBatis-PlusMP的updateById方法并且实体类字段上使用了MP的TableField(strategy FieldStrategy.IGNORED)注解或者全局配置了field-strategy: ignored。MP的默认策略是NOT_NULL即当字段为null时不加入SET语句。如果你想用null值覆盖数据库中的现有值需要将策略改为IGNORED或者在更新时使用UpdateWrapper明确设置set(column, null)。如果是原生MyBatis则需要检查你的动态set标签或trim标签确保null值对应的条件被包含进去。例如如果你的XML中写的是if testemail ! nullemail #{email},/if那么传入的email为null时这个字段就不会被更新。问题二查询返回List但size()为1里面元素却是null。这个问题非常诡异。通常发生在结果映射ResultMap配置不正确或者数据库查询返回了行可能有默认值或某些列有值但无法映射到你的Java对象上。MyBatis会为每一行结果尝试创建一个对象如果创建失败比如构造函数异常或所有属性都无法映射这个对象可能就是null但List的大小依然等于结果集的行数。排查步骤首先打开DEBUG日志查看实际执行的SQL和返回的结果集确认数据库是否真的返回了数据。检查你的resultType或resultMap是否正确。特别是使用resultMap时检查id和result的column和property是否一一对应大小写是否敏感数据库列名通常不区分大小写但Java属性名区分。检查实体类是否有默认构造函数或者是否使用了正确的构造器映射。尝试将resultType改为java.util.Map看是否能正确接收到数据。如果能那问题肯定出在结果映射上。问题三集成Spring Boot后事务不生效。确保以下几点主启动类上有SpringBootApplication并且扫描到了你的Mapper接口通常通过MapperScan。在需要事务的方法或类上添加Transactional注解。确认你调用事务方法的方式。如果是在同一个类中一个非事务方法A调用了带有Transactional注解的方法B由于Spring AOP基于代理的实现B方法的事务会失效。这是因为A调用的是this.B()而不是代理对象的B()。解决方法是自我注入Autowired自己或将该方法移到另一个Bean中。9. MyBatis-Plus 高效开发锦囊MyBatis-PlusMP是在MyBatis基础上的强大增强工具包“为简化开发而生”。它确实能极大提升CRUD效率。9.1 Lambda查询与更新类型安全的优雅之道MP的Lambda表达式查询是防止“魔法字符串”的利器。// 传统方式字段名是字符串容易写错重构不友好 QueryWrapperUser wrapper new QueryWrapper(); wrapper.eq(name, 张三).gt(age, 18); // Lambda方式编译期检查IDE智能提示 LambdaQueryWrapperUser lambdaWrapper new LambdaQueryWrapper(); lambdaWrapper.eq(User::getName, 张三).gt(User::getAge, 18); // 甚至更简洁的链式调用 ListUser list userService.lambdaQuery() .eq(User::getStatus, 1) .like(User::getName, 张) .list();更新操作同样支持Lambda避免字段名拼写错误。// 更新所有状态为0的用户的名字 userService.lambdaUpdate() .set(User::getName, 新名字) .eq(User::getStatus, 0) .update();9.2 自动填充与逻辑删除的配置心得自动填充用于自动设置create_time、update_time等字段。在实体类字段上添加TableField(fill FieldFill.INSERT)或FieldFill.INSERT_UPDATE。实现MetaObjectHandler接口重写insertFill和updateFill方法。Component public class MyMetaObjectHandler implements MetaObjectHandler { Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, createTime, LocalDateTime.class, LocalDateTime.now()); this.strictInsertFill(metaObject, updateTime, LocalDateTime.class, LocalDateTime.now()); } Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, updateTime, LocalDateTime.class, LocalDateTime.now()); } }注意strictInsertFill和strictUpdateFill是MP 3.3.0推荐的方法它会进行严格的类型匹配。确保实体类字段类型与这里指定的类型完全一致如都是LocalDateTime。逻辑删除在全局配置中开启逻辑删除。mybatis-plus: global-config: db-config: logic-delete-field: deleted # 全局逻辑删除字段名 logic-delete-value: 1 # 逻辑已删除值 logic-not-delete-value: 0 # 逻辑未删除值在实体类对应字段上添加TableLogic注解如果字段名不是deleted可用此注解指定。此后调用mapper.deleteById()实际上会执行UPDATE table SET deleted 1 WHERE id ?。所有的select方法会自动加上AND deleted 0条件。踩坑提醒开启逻辑删除后如果你需要查询包含已删除的数据需要使用Wrapper手动过滤条件或者使用MP的selectList方法并传入一个不包含逻辑删除条件的Wrapper。关联查询时也要特别注意如果主表关联了副表副表的逻辑删除条件可能会被自动加上需要检查生成的SQL是否符合预期。
返回列表