ARTICLE DETAIL

资讯详情

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

Mybatis七种传参方式详解:从参数解析到实战避坑

Mybatis七种传参方式详解:从参数解析到实战避坑 1. 传参的本质为什么Mybatis对参数这么挑剔做Java后端这几年Mybatis的传参方式几乎出现在每一次面试和每一次实际开发里。很多新人一开始最懵的就是明明Java方法里就一个参数为什么XML里写#{name}有时候能拿到值有时候直接报BindingException明明传的是一个对象为什么写成#{id}就取不到这些问题绕来绕去根子都在Mybatis的参数解析机制上。Mybatis本身是一个半自动ORM框架SQL语句和Java方法之间的桥梁就是Mapper接口。你调用接口方法时传入的Java对象要经过ParamNameResolver和TypeHandler等一系列组件才能最终变成PreparedStatement里的实际参数。这个过程中参数命名、参数数量、参数类型都会直接影响解析结果。所以搞懂七种传参方式本质上就是搞懂Mybatis如何看待你递过来的那堆数据。这七种方式从最简单的单个参数到复杂的混合参数对象覆盖面足够应对日常工作里的查询、插入、批量更新和动态SQL场景。不管你是刚学Mybatis的初学者还是已经写了几年业务的老手把这些规则彻底吃透至少能少掉一半以上的Caused by: org.apache.ibatis.binding.BindingException异常。这篇就按我实际项目里最常用的顺序把七种方式逐个拆开顺便把参数解析、TypeHandler以及和缓存相关的一些坑也一并说清楚。1.1 从Java到SQL的映射链路参数名是怎么丢失的先看一个最简单的情况public interface UserMapper { User selectById(Integer id); }对应XMLselect idselectById resultTypecom.example.User SELECT * FROM user WHERE id #{id} /select你可能会觉得方法参数变量名叫idXML里#{id}自然就能取到。这个想法在单个参数场景下成立因为Mybatis对单个非注解参数做了特殊处理——不定名直接用#{任意名字}都能取到唯一的那个参数值。但是一旦方法里有多个参数public interface UserMapper { User selectByNameAndAge(String name, Integer age); }此时XML里写#{name}、#{age}还能行吗答案是不行。原因在于Java编译器在默认情况下不会保留方法参数的变量名除非编译时加了-parameters参数Mybatis拿到的是arg0、arg1或者param1、param2这类占位名。如果你强行用#{name}Mybatis会告诉你Parameter name not found. Available parameters are [arg1, arg0, param1, param2]。这个报错信息的价值很大它直接告诉了你可用参数有哪些。但很多人第一次看到这个异常会慌以为自己的XML写错了其实只是没搞清楚Mybatis的参数命名策略。要解决多参数问题最简单的办法就是用Param注解显式指定名字这也是后面要说的第二种方式。1.2 七种传参方式的整体视角在逐一展开之前先把七种方式列个总表心里有个轮廓。传参方式使用场景典型写法关键点单个基本类型参数根据ID查询、删除单条记录User selectById(Integer id)XML里#{任意名字}均可Param注解多参数多条件查询、少量参数User select(Param(name) String name, Param(age) Integer age)注解命名后可用自定义名字POJO实体对象插入、更新、复杂查询条件int insert(User user)直接使用属性名Map参数动态拼条件、表结构灵活ListUser list(MapString, Object params)用put与其他端方式结合List集合参数批量查询、批量插入int batchInsert(ListUser list)配合foreach遍历数组参数IN查询、批量删除int deleteByIds(Integer[] ids)foreach的collection写array混合参数查询条件分页额外字段ListUser page(Param(user) User user, Param(pageNum) int pageNum)各参数独立命名后引用别名这七种方式不是互斥的实际项目里经常混着用比如List参数配合Param或者POJO对象配合额外参数。理解了各自原理组合才能用得得心应手。2. 七种传参方式逐一拆解下面我按代码示例、XML写法、背后原理、踩坑提示四个维度来写尽量让每一种方式都能直接“抄作业”。2.1 方式一单个基本类型参数这是最基础、也是很多新人的启蒙写法。User selectById(Integer id);XMLselect idselectById resultTypecom.example.User SELECT * FROM user WHERE id #{id} /select这个#{id}里的名字随意写都行哪怕是#{value}或者#{abc}Mybatis都能拿到唯一的那个参数。原因很简单当Mapper接口方法只有一个参数且没有加Param时Mybatis会把整个参数值包装成一个Mapkey不管叫什么都指向这个唯一值。实际项目里我通常会在单个参数场景下也写上Param并不是必须而是为了给将来扩展留后路。比如今天只有一个id明天需求要加一个status如果之前就写了Param(id)那么扩展时直接加参数并加注解即可不需要回头改XML里的变量名减少一次不必要的风险。这里提醒一点当你用#{id}时如果传入的id是IntegerMybatis会把值作为整数绑定到SQL上不需要引号如果传的是StringMybatis会自动加引号。这种类型推断能力来自TypeHandler后面第三章细说。2.2 方式二Param注解绑定命名多参数场景下Param是解决命名问题最直接的手段。ListUser selectByNameAndAge(Param(name) String name, Param(age) Integer age);XMLselect idselectByNameAndAge resultTypecom.example.User SELECT * FROM user WHERE name #{name} AND age #{age} /select加了Param(name)之后Mybatis会把这个参数以name为key存进参数MapXML里#{name}就能准确匹配。很多人问我既然有Param是不是所有情况都加注解最保险我的建议是两个及以上的业务参数一定要加。哪怕不加注解时也能用arg0和param1但可读性太差万一中间加了一个参数后面的arg1就变成了arg2所有XML都得跟着改非常容易漏。Param还有一个细节在XML的动态SQL里引用参数时同样使用Param定义的名字。比如select idselectByNameAndAge resultTypecom.example.User SELECT * FROM user where if testname ! null AND name #{name} /if if testage ! null AND age #{age} /if /where /select这里的testname ! null引用的也是Param指定的名字。如果在if里写testname ! null但参数没加注解、只靠默认的arg0那这里就要写成arg0 ! null非常丑。所以多参数场景下Param不仅是为了取参更是为了动态SQL里判断方便。2.3 方式三POJO实体对象当参数比较多、且正好能封装成一个对象时直接传实体类最省事。int insert(User user);XMLinsert idinsert parameterTypecom.example.User useGeneratedKeystrue keyPropertyid INSERT INTO user(name, age, email) VALUES(#{name}, #{age}, #{email}) /insert在没有使用Param的情况下Mybatis会把user对象本身作为参数对象XML里#{name}会调用user.getName()这个getter来取值。所以POJO传参的硬性要求是属性名必须和XML里的#{...}保持一致并且遵循JavaBean规范有对应的getter方法。这里有个很多人忽略的坑parameterType属性在Mybatis 3.x版本里其实可以被省略因为通过接口方法的参数类型能推断出来。但很多老项目里还留着parameterType这没问题只是要注意它是大小写不敏感的写com.example.User或者com.example.user都行Mybatis底层会做大小写归一化处理。POJO传参在插入、更新操作中非常好用。比如更新用户信息时常常只更新非空字段update idupdateUser parameterTypecom.example.User UPDATE user set if testname ! nullname #{name},/if if testage ! nullage #{age},/if if testemail ! nullemail #{email},/if /set WHERE id #{id} /update这个玩法几乎每个项目都会用到。不过要注意if test判空对基础类型有个大坑如果age是int而不是Integer那么它永远不可能为null判断age ! null恒为true这样即使没有传age也会把默认值0写进数据库。所以POJO里的字段凡是可能为空的务必使用包装类型。2.4 方式四Map参数Map传参的特点是灵活到没有约束适合表结构变化较多或者查询条件特别自由的场景。ListUser selectByCondition(MapString, Object params);XMLselect idselectByCondition resultTypecom.example.User SELECT * FROM user where if testname ! null AND name #{name} /if if testage ! null AND age #{age} /if /where /select调用的时候MapString, Object params new HashMap(); params.put(name, 张三); params.put(age, 18); userMapper.selectByCondition(params);Map里的key就相当于参数名。这种方式的好处是你不必为每个查询条件都建一个POJO类。比如一个报表查询可能从前端接收几十个筛选字段不可能为每个报表都建实体类这时候Map就是最通用的选择。但Map的缺点也很明显类型安全缺失。你在Map里put一个String值Mybatis拿到的是Object需要在TypeHandler解析时根据实际类型转换。如果put进去的类型和数据库字段类型不匹配容易在SQL执行阶段爆出ClassCastException或者TypeMismatchException。另外Map可读性差后续维护的人看一眼Mapper根本不知道有哪些key只能去业务层搜代码。我的建议是Map适合临时性、探索性的查询或者服务层到Mapper之间已经有工具类做好参数构造的场景。对于长期维护的核心业务实体尽量不要让Mapper接口用Map当唯一参数。如果确实混用建议在接口Javadoc里写清楚需要的key列表避免后来人瞎猜。2.5 方式五List集合批量参数批量操作是Mybatis使用率极高的场景而List参数是批量操作的核心传参方式。int batchInsert(Param(userList) ListUser userList);XMLinsert idbatchInsert INSERT INTO user(name, age) VALUES foreach collectionuserList itemuser separator, (#{user.name}, #{user.age}) /foreach /insert很多人问为什么这里要加Param(userList)原因在于如果方法只有一个List参数Mybatis是能识别的但collection属性必须写成list或者collectionMybatis的默认命名规则。一旦你加了Param(userList)就能在XML里用自定义的名字更直观。foreach是Mybatis动态SQL里极其重要的标签它的属性包括collection、item、index、open、close和separator。批量插入时separator,负责在每组值之间加逗号批量查询时可以用open(和close)包裹IN列表select idselectByIds resultTypecom.example.User SELECT * FROM user WHERE id IN foreach collectionids itemid open( close) separator, #{id} /foreach /select批量插入有一个需要特别留意的临界问题数据库SQL重放次数。如果一次性插入上千条数据虽然Mybatis拼接成的SQL是一条巨长的INSERT语句但对数据库来说解析压力很大。我实测过MySQL单条SQL插入500条以上的多条VALUES性能并不比拆分好多少反而可能出现max_allowed_packet超限。所以批量插入建议分批执行每批200~500条为宜。还有一点List传参时如果列表为空foreach会生成一个空的IN ()这在SQL里直接报语法错误。所以调用前一定要判断list ! null !list.isEmpty()或者在XML里配合if testlist ! null and list.size() 0做保护。2.6 方式六数组参数数组传参本质上和List类似区别在于Mapper方法参数类型为数组时Mybatis对collection属性的默认命名是array。ListUser selectByArray(Param(ids) Integer[] ids);如果没加注解XML应写成select idselectByArray resultTypecom.example.User SELECT * FROM user WHERE id IN foreach collectionarray itemid open( close) separator, #{id} /foreach /select加了注解就用注解名字foreach collectionids itemid ...数组参数在日常代码里的使用频率比List低但在某些前置框架的场景下有优势比如用数组给存储过程传参或者从某个工具类里直接拿到数组。我的经验是除非接口定义或者老代码要求否则优先使用List因为List的API更丰富配合Java 8的Stream也很方便数组的扩展性稍差。数组传参的另一个细节是如果数组是二维数组或者对象数组item可以直接引用数组元素的属性。比如ListUser selectByUsers(Param(users) User[] users);XMLforeach collectionusers itemuser separator, #{user.name} /foreach这种写法本质上和List 一致只是外层容器不同Mybatis的解析逻辑是统一的。所以你现在理解了Mybatis的foreach其实并不关心外层是List还是数组它只关心你通过collection属性传入的可迭代对象。2.7 方式七混合参数POJO Param 其他类型实际开发里最常用也最有技巧性的其实是混合参数。常见组合是一个对象承载主要查询条件外加若干独立参数做分页、状态、排序等。ListUser selectPage(Param(user) User user, Param(pageNum) int pageNum, Param(pageSize) int pageSize);XMLselect idselectPage resultTypecom.example.User SELECT * FROM user where if testuser.name ! null AND name #{user.name} /if if testuser.age ! null AND age #{user.age} /if /where LIMIT #{pageNum}, #{pageSize} /select这里最核心的规则是Param(user)之后的#{user.name}会先解析到参数Map里key为user的对象再调用该对象的getName()方法。如果你在方法里忘了加Param(user)那么Mybatis会把唯一的POJO参数本身当作参数Map的唯一value此时XML里应该直接写#{name}而不是#{user.name}。混合参数的一个优势是避免多次定义DTO。比如分页查询用PageHelper插件时可以不显式传分页参数但如果你需要手动控制可以把分页字段直接作为额外参数传入。还有业务场景中常见的“查询条件对象 操作人ID 是否需要软删除”这种模式用一个POJO传主要条件用两个独立参数传元信息非常清晰。混合参数也常用于动态排序select idselectList resultTypecom.example.User SELECT * FROM user where if testuser.name ! null AND name #{user.name} /if /where ORDER BY ${orderBy} ${orderType} /select注意排序字段这里我用了${}不是#{}。${}是字符串拼接直接替换SQL片段容易引发SQL注入。但如果orderBy和orderType是后端硬编码的白名单值比如固定写成create_time、DESC并且做了校验那么用${}是可以接受的。混合传参里独立参数也可以用于这类SQL片段场景前提是严格防注入。3. 参数解析背后的秘密ParamNameResolver与TypeHandler七种方式看完了很多人会问为什么Mybatis有时候把参数包装成Map有时候又直接取对象这就要聊到Mybatis的ParamNameResolver了。3.1 ParamNameResolver的解析顺序Mybatis在每次执行Mapper方法时都会通过ParamNameResolver将方法参数解析成一个ParamNameMap。具体规则是这样的如果参数使用了Param注解就以注解值为key。没有注解的参数会依次使用arg0、arg1如果编译时开启了-parameters参数则可能用真实变量名兼容性考虑不推荐依赖。同时无论是否用了ParamMybatis还会额外把param1、param2等作为key加入Map这也是很多老代码里写#{param1}也能取到值的原因。这个设计初看有点冗余实际是为了兼容旧版本。早在Mybatis 3.4.x版本之前直接写#{0}或者#{1}是有效的后来统一为arg和param两种命名。现在的新代码老老实实用Param是最稳妥的不要依赖arg0这类索引命名。再看ParamNameResolver的一个细节如果Mapper方法只有一个参数且这个参数没有加Param那么Mybatis会走一个特殊分支不包装成Map直接把参数对象本身作为传入数据。这就是为什么单个POJO参数时XML里直接写#{name}就能使用而不会报“name not found”的错。但如果加了Param(user)即使只有一个参数Mybatis依然会包装成Map此时就必须用#{user.name}。这个解析顺序直接影响XML的写法所以排查传参问题时第一件事就是看Mapper方法签名上有没有Param以及有几个参数。3.2 TypeHandler在传参过程中的角色参数怎么命名搞定了值怎么正确设置到PreparedStatement里就要看TypeHandler。TypeHandler负责Java类型和JDBC类型之间的互相转换。比如Java的String类型在预编译时通过ps.setString(index, value)写入Java的LocalDate类型在MySQL驱动5.x和8.x的转换行为就不一样。Mybatis内置了很多默认TypeHandler比如StringTypeHandler、IntegerTypeHandler、LocalDateTimeTypeHandler等。传参过程中#{name}表达式解析出的对象值会交给对应的TypeHandler处理。那么TypeHandler怎么选择默认是按Java参数的实际类型来匹配。比如#{age}如果age是Integer就用IntegerTypeHandler如果是Long就用LongTypeHandler。如果你传入的是一个复杂的自定义对象Mybatis会尝试通过OGNL表达式获取属性然后根据属性类型选TypeHandler。这里有一个和热词“typehandler工作流程图”相关的常见场景当Java类型和数据库类型不匹配时需要自定义TypeHandler。比如数据库存的是JSON字符串Java里对应的字段是一个Listpublic class User { private ListString favoriteTags; // 数据库存的是 [读书,运动] }如果直接用Map或POJO传参Mybatis默认的TypeHandler并不认识List怎么转成字符串。这时候可以自定义一个ListTypeHandler extends BaseTypeHandlerListString在setNonNullParameter方法里用JSON序列化器把List转为String再写入PreparedStatement。MappedTypes(List.class) public class ListStringTypeHandler extends BaseTypeHandlerListString { Override public void setNonNullParameter(PreparedStatement ps, int i, ListString parameter, JdbcType jdbcType) throws SQLException { ps.setString(i, JSON.toJSONString(parameter)); } // getNullableResult相关方法省略 }然后在Mapper XML的#{favoriteTags}上指定#{favoriteTags, typeHandlercom.example.handler.ListStringTypeHandler}也可以全局注册但全局注册容易影响所有List类型字段所以更推荐局部指定。从这个角度看传参方式不光是Param和POJO的问题参数值到了JDBC层面最终还是要靠TypeHandler把Java对象塞进数据库。理解这个链路后遇到奇怪的“SQL绑定参数类型错误”就不会一头雾水了。4. 实战中的选择策略与避坑指南七种方式原理都清楚了实际开发里怎么选择我按个人经验总结一套决策路径同时把常见的排查方法也放上来。4.1 如何选择合适的传参方式先看一张决策表基本覆盖日常逻辑条件推荐方式理由参数数量 3 且都是基本类型Param 多参数简单直观动态SQL判断用注解名参数正好对应一个实体字段POJO 对象可复用插入更新场景最合适参数数量很多且不固定Map灵活避免创建大量DTO批量操作插入/查询/删除List Paramforeach遍历方便可控性强主对象 少量扩展参数混合参数既保留POJO语义又补充独立参数这个表不是绝对真理。比如参数数量超过3个时有人坚持用Param也有人坚持建DTO。我更倾向于“业务语义优先”如果这些参数在概念上属于同一个实体使用POJO如果是跨领域的查询条件比如用户ID 商品分类 时间范围 店铺ID很难归属到某一个实体那直接定义查询DTO对象利用混合参数方式传入比Map更清晰。再说一个选择上的常见纠结#{}还是${}传参方式主要决定#{}怎么取参但${}在动态SQL中也有自己的用武之地。比如排序字段、表名这种结构性的部分不能用#{}因为预编译参数只会生成占位符没办法替换成表名。用${}前一定要做白名单校验。比如String orderBy create_time; // 白名单 if (allowedOrders.contains(orderBy)) { sql ORDER BY orderBy; }在实际编码里我见过太多人因为图方便把用户输入直接拼到${}里结果被SQL注入。这不是Mybatis的问题是使用姿势的问题。整理一份规范放在团队文档里很有必要我这里的建议就是#{}默认使用${}必须过白名单。4.2 常见异常与排查技巧这些异常几乎每个用Mybatis的人都遇到过这里统一梳理一下。异常1Parameter xxx not found这是七种传参方式里最常见的报错。报错信息类似Parameter name not found. Available parameters are [arg1, arg0, param1, param2]排查思路看Mapper方法有几个参数。如果只有一个参数且没加Param那么XML里随便用什么名字都能取到值不存在not found。如果是多个参数请检查是否加了Param注解以及XML里引用的名字是否和注解一致。检查引号问题。#{name}和#{ name }写法的解析不一样后者在某些版本下会当成字面字符串处理导致取不到参数值。异常2TooManyParametersException这个异常通常出现在动态SQL中错误原因一般是XML里的#{id}用到的参数名在方法参数Map中不存在或者因为参数名冲突导致Mybatis试图遍历太多参数。最常见的就是POJO内部有一个属性名和外面参数名重名比如ListUser select(Param(user) User user, Param(name) String name);XML里不小心写成WHERE name #{user.name}但你想用的是独立的name参数结果Mybatis尝试解析user对象的name属性如果user对象为null就报错或者解析出不期望的值。这种问题表面上看起来是“参数太多”实际是命名层次用错了。异常3BindingException: Invalid bound statement (not found)这个问题有时也被误判为传参问题其实是Mapper接口和XML的namespace或id不匹配。排查方法检查XML的namespace是否完全对应接口的全限定名。检查select标签的id是否和接口方法名一致。确认没有同名方法重载。Mybatis不允许Mapper接口方法重载因为XML映射不能基于参数的区分只能靠方法名和namespace唯一确定。异常4SQLSyntaxErrorException这类异常常常和foreach生成的SQL有关。比如集合为空生成了IN ()或者批量插入的字段顺序和数据库表不匹配。解决办法就是在foreach外层加if判断以及仔细核对字段列表。4.3 结合缓存与动态SQL的传参注意事项热词里有“mybatis缓存”“mybatis二级缓存实现”这些和传参也有隐含的联系。很多人不知道Mybatis的一级缓存和二级缓存的key有一部分是由传入参数对象生成的。如果传参对象没有正确实现equals和hashCode方法那么缓存命中会别别扭扭。具体来说一级缓存是SqlSession级别的同一次会话内相同SQL和相同参数的查询会走缓存。这里的“相同参数”在Mybatis内部通过CacheKey对象判断它除了拼接SQL语句本身还把所有参数值追加进key里。如果你传入的参数对象是一个自定义POJOMybatis会调用它的toString来生成缓存key的一部分。这就是为什么有人发现同一个POJO对象内部多了一个无意义字段缓存就失效了——因为toString变了。二级缓存是Mapper级别的默认序列化存储缓存结果。如果传参对象和结果对象没有实现Serializable接口使用二级缓存时会直接报NotSerializableException。很多项目开了二级缓存结果上线后过段时间就报错排查到最后发现是某个查询结果对象没实现序列化接口。所以在决定使用二级缓存前建议评估一下数据一致性要求Mybatis的二级缓存是本地缓存不是分布式缓存多节点部署后容易产生脏数据。再说动态SQL中的传参注意点。if判断变量是否为空时如果参数是Map判断的是Map的value是否为null如果参数是POJO判断的是POJO的属性是否为null。有的新手会写成if testname ! null and name ! 如果name是Integer那么name ! 会触发类型比较错误甚至稳稳地返回false导致条件永远不会拼进去。Integer类型的判断建议只写! null。概括成一条规则基本类型且非字符串的字段判空只写! null不要画蛇添足。最后说说和“mybatis xml高亮”相关的一个小点。IDE里高亮可以帮我们快速发现XML标签闭合问题但传参写法的错误高亮不一定能识别出来。比如#{user.name}和#{userName}这些语法上都是合法的OGNL表达式高亮不会报错但运行时就是取不到值。这种问题只能靠经验每次取不到参数时先回到方法签名看注解和参数类型再回到XML对照引用名按这个顺序排查一分钟内基本能定位。5. 一个综合案例把七种方式串起来单纯讲理论还是不够我把自己项目里一个真实的综合查询界面拿出来拆解把七种方式里的几种混合运用一下顺便展示传参在分页、条件拼接、批量操作里的完整链路。业务是后台用户管理页需要支持按用户名模糊查询、按年龄段筛选、按创建时间范围筛选、以及多选用户ID批量拉黑。Mapper接口设计如下public interface UserMapper { ListUser searchUsers(Param(condition) UserQueryDTO condition, Param(ids) ListInteger ids, Param(startTime) LocalDateTime startTime, Param(endTime) LocalDateTime endTime, Param(limit) int limit, Param(offset) int offset); }这里用了混合参数一个查询DTO、一个List、两个时间参数、两个分页参数总共6个参数。XML如下select idsearchUsers resultTypecom.example.User SELECT * FROM user where if testcondition.name ! null and condition.name ! AND name LIKE CONCAT(%, #{condition.name}, %) /if if testcondition.minAge ! null AND age gt; #{condition.minAge} /if if testcondition.maxAge ! null AND age lt; #{condition.maxAge} /if if teststartTime ! null AND create_time gt; #{startTime} /if if testendTime ! null AND create_time lt; #{endTime} /if if testids ! null and ids.size() 0 AND id IN foreach collectionids itemid open( separator, close) #{id} /foreach /if /where ORDER BY create_time DESC LIMIT #{offset}, #{limit} /select这里有几个细节值得注意condition.name的判空同时判断了字符串为空。因为模糊查询时空的字符串拼接进SQL没有意义。age gt;写法XML中大于号和小于号必须转义否则标签解析会出错。ids作为List判断size() 0避免生成空IN集合。时间字段是LocalDateTimeMybatis 3.8以上版本内置了对应的TypeHandler基本开箱即用。但如果数据库驱动版本过低可能出现LocalDateTime不支持的情况此时需要手动注册Jsr310TypeHandler或者升级驱动。在这个案例里你会发现POJODTO、List、单值参数三种方式同时共存这在复杂后台查询中非常典型。通过Param给每个参数起名XML里层次分明谁是谁清清楚楚。如果不用Param很难想象在XML里怎么引用这6个参数。还有一种情况更极端前后端定义了一套复杂的筛选条件字典前端传过来一个Mapkey可能是fieldName值是任意类型。此时如果为每个字段写#{params[fieldName]}也行但可读性极差。更推荐的做法是用Map传参配合OGNL索引if testparams[status] ! null AND status #{params[status]} /ifMap的key用单引号包起来OGNL才能正确解析字符串key。如果写成params[status]Mybatis会把它当成一个变量status去查找结果返回null条件就被忽略了。这个小细节我踩过一次排查了很久才恍然大悟。所以总结下来传参方式不仅是“能用就行”还关系到SQL的可维护性、缓存命中的稳定性以及动态SQL的健壮性。每一种方式背后都有相应的设计意图理解了这些意图你才能在面试时答出深度在项目中写出让人省心的代码。我自己在实际项目里的体会是传参方式的坑大多不是Mybatis本身的问题而是使用者在“多个参数”和“动态SQL”这两个场景下的概念混淆。把参数命名规则、解析器行为、TypeHandler的边界记住平时再配合IDEA的Debug去看ParamNameResolver构造出来的参数Map很多问题当场就能水落石出。多参数别嫌麻烦Param写清楚POJO字段用包装类型Map只当临时容器批量操作记得判空和分批排序字段务必白名单校验。这些规矩看似琐碎但每一行都是拿线上故障换来的值得你在下一个项目里落实。
返回列表