
老铁们前面几篇我们搞定了 MyBatis 的基础操作增删改查信手拈来。但现实世界哪有这么简单比如你要做一个用户管理系统用户注册时有些字段是选填的你不能每次都用固定的SQL用户列表页有一堆筛选条件你不能写几十个不同的查询方法批量删除要支持勾选多条数据你不能一条条循环删吧如果这时候你还用死SQL硬怼那你的代码里就会充斥大量if-else拼SQL字符串又丑又容易出SQL注入。这时候 MyBatis 的动态SQL就该登场了——它让你像写 Java 代码一样在 SQL 里加条件判断、循环遍历让 SQL 语句自动适应业务变化。1. 从“注册用户”说起选填字段怎么处理假设你在开发一个用户注册功能表单里有用户名、密码、年龄、性别、手机号。其中用户名和密码是必填其他都是选填。如果用户没填性别你是直接把性别字段null插入数据库还是干脆不插这个字段1.1 死板的方式固定字段列表如果你写死SQLinsertintouserinfo(username,password,age,gender,phone)values(?,?,?,?,?)当用户没填性别时性别字段就会被插入null。如果数据库里性别字段有默认值比如默认0表示“未知”你插个null进去可能就覆盖了默认值逻辑上不完美。而且万一哪天数据库加了非空约束直接报错。1.2 灵活的方式根据情况决定插哪些字段我们要让 SQL 自己判断如果传了性别就插性别字段没传就不插。这就像做菜客人点啥你就放啥调料。在 MyBatis 里可以用if标签来实现insertidinsertUserinsert into userinfo ( username, password, age,iftestgender ! nullgender,/ifphone ) values ( #{username}, #{password}, #{age},iftestgender ! null#{gender},/if#{phone} )/insert注意当调用这个方法时如果你传的userInfo对象的gender是null那么gender,和#{gender},这两个片段就不会被拼接到 SQL 里。最终生成的 SQL 就是insertintouserinfo(username,password,age,phone)values(?,?,?,?)完美避开了性别字段。注意test里的gender是 Java 对象里的属性名不是数据库列名。MyBatis 在解析时会通过 OGNL 表达式去调用userInfo.getGender()来取值判断。2. 当选填字段越来越多怎么办如果后来需求变了年龄也成了选填手机号也是选填甚至你还要支持将来加更多的选填字段那么上面这种写法就会变得很难看——字段列表和值列表都得对应着写if而且逗号的位置要非常小心最后一个字段后面不能有逗号。稍不注意就会拼出语法错误。举个例子这时候就需要一个更聪明的“管家”——trim标签。2.1trim帮你自动处理括号和逗号我们改造一下插入语句insertidinsertUserINSERT INTO userinfotrimprefix(suffix)suffixOverrides,iftestusername ! nullusername,/ififtestpassword ! nullpassword,/ififtestage ! nullage,/ififtestgender ! nullgender,/ififtestphone ! nullphone,/if/trimVALUEStrimprefix(suffix)suffixOverrides,iftestusername ! null#{username},/ififtestpassword ! null#{password},/ififtestage ! null#{age},/ififtestgender ! null#{gender},/ififtestphone ! null#{phone},/if/trim/insertprefix(在所有内容前面加一个(suffix)在所有内容后面加一个)suffixOverrides,如果最后一个字符是逗号就自动去掉这样不管哪些字段有值最终都能拼出一个完美的(字段1,字段2,...)和对应的(值1,值2,...)而且绝对不会多逗号。是不是省心多了3. 多条件查询别再WHERE 11了很多老项目里为了应付动态查询条件会写SELECT * FROM user WHERE 11然后后面跟着一堆AND name #{name}的拼凑。这种写法虽然能跑但11这种“万能条件”总让人觉得怪怪的。MyBatis 提供了where标签专门解决这个痛点。引入3.1 用where优雅地拼条件假设你的用户列表页可以根据年龄、性别、是否删除delete_flag来筛选每个条件都是可选的。你可以这样写selectidqueryUsersresultTypeUserInfoselect id, username, age, gender, delete_flag from userinfowhereiftestage ! nulland age #{age}/ififtestgender ! nulland gender #{gender}/ififtestdeleteFlag ! nulland delete_flag #{deleteFlag}/if/where/selectwhere会做两件事如果里面有任何if成立它就会自动生成where关键字。它会自动去掉第一个条件前面的and或or避免where and age 18这样的语法错误。如果没有任何条件where直接消失SQL 变成SELECT ... FROM userinfo照样能跑。这个可比手写WHERE 11优雅多了语义清晰一看就知道这里是动态条件。4. 动态更新只改传过来的字段用户修改个人资料时通常只传修改的字段后端应该只更新这些字段没传的保持原样。如果用死updata user set username?, age?, gender? where id?你不得不把原来的值都取出来填回去非常麻烦。MyBatis 的set标签就是为这个场景设计的。4.1 用set动态生成 SET 子句updateidupdateUserupdata userinfosetiftestusername ! nullusername #{username},/ififtestage ! nullage #{age},/ififtestdeleteFlag ! nulldelete_flag #{deleteFlag},/if/setwhere id #{id}/updateset会自动在 SQL 中插入set关键字并且自动去掉最后一个多余的逗号。比如只有username有值生成的 SQL 就是updata userinfosetusername?whereid?干净利落不会有多余的逗号。如果什么都没改所有if都不成立set就会生成空内容导致 SQL 语法错误。所以业务上要确保至少有一个字段被更新或者用其他逻辑兜底。5. 批量操作用foreach搞定 IN 查询你有没有遇到过这种需求在管理后台勾选几条数据点击“批量删除”。前端传一个 id 列表比如[1,2,3]。这时候我们需要生成delete from user where id in (1,2,3)这样的 SQL。MyBatis 的foreach就是专门干这个的。5.1 遍历集合生成 IN 子句deleteiddeleteByIdsdelete from user_info where id inforeachcollectionidsitemidseparator,open(close)#{id}/foreach/deletecollection指定要遍历的集合可以是方法参数名比如ids。如果你在接口里用了Param(ids)这里就写ids。item当前遍历到的元素的临时变量名。separator元素之间的分隔符这里是逗号。open遍历结果的开头字符这里是(。close遍历结果的结尾字符这里是)。如果传的是[1,2,3]最终会生成deletefromuserinfowhereidin(1,2,3)完美。foreach不仅可以用在 IN 查询还可以用在批量插入、批量更新等场景比如insertidbatchInsertINSERT INTO userinfo (username, password) VALUESforeachcollectionlistitemuserseparator,(#{user.username}, #{user.password})/foreach/insert一次插入多条效率比循环单条插入高得多。6. 重复代码怎么办抽取 SQL 片段你可能会发现很多查询里都要写id, username, age, gender, phone, delete_flag, create_time, update_time这一长串字段。如果表结构变了比如加了一个字段你得到处改。MyBatis 允许你用sql定义可复用的片段然后用include引入。6.1 定义公共字段列表sqlidallColumnsid, username, age, gender, phone, delete_flag, create_time, update_time/sql6.2 在查询中引用selectidfindAllresultTypeUserInfoSELECTincluderefidallColumns/FROM userinfo/selectselectidfindByIdresultTypeUserInfoSELECTincluderefidallColumns/FROM userinfo WHERE id #{id}/select以后如果表里加了字段只需要修改sql片段所有引用它的地方都自动生效。是不是有点像写代码时抽方法一样7. 注解方式虽然能用但别太执着我知道有些老铁喜欢用注解觉得省事。注解里也可以写动态 SQL只要把整个 SQL 用script标签包起来里面就能写 XML 标签了。比如Select(scriptSELECT * FROM userinfowhere if testage ! null AND age #{age}/if/where/script)ListUserInfoqueryByAge(Param(age)Integerage);虽然能用但当你把一堆 XML 标签塞进 Java 字符串里阅读和维护起来都挺痛苦的。所以除非你的项目规定了只能写注解否则我更推荐把动态 SQL 放到 XML 文件里清爽格式也好看。8. 动态SQL的底层故事简单了解你可能好奇MyBatis 是怎么把这些 XML 标签变成真正的 SQL 的其实它内部会把这些标签解析成一个SQL 节点树然后根据传入的参数遍历这棵树决定哪些节点要执行哪些跳过。整个过程类似于一个简单的模板引擎最终生成完整的 SQL 字符串再交给数据库执行。所以动态 SQL 的性能一般比静态 SQL 略低但在绝大多数业务场景下这点损耗可以忽略不计带来的开发便利性却是巨大的。9. 总结动态SQL就是让你“懒”得有道理场景用什么标签核心作用插入时字段可选iftrim动态决定包含哪些字段自动处理括号和逗号查询时条件可选where自动生成 WHERE去除多余 AND/OR更新时只改部分字段set自动生成 SET去除末尾逗号批量删除/插入foreach遍历集合生成 IN 子句或批量 VALUES复用公共 SQLsqlinclude一处定义多处引用避免重复学会了这些我们以后再写 MyBatis 的 SQL就能真正做到“以不变应万变”。无论前端传什么条件后端都能优雅地拼出正确的 SQL。这才是框架该有的样子——把复杂留给自己把简单留给开发者。老铁们如果你觉得这篇文章对你有帮助别忘了 点赞⭐ 收藏 关注下一篇咱们用 MyBatis 配合 Spring MVC 完成一个完整的 Web 小项目把前面学的知识串起来