如何在MyBatis注解中提取嵌套字段构建动态SQL?
嘿,你遇到的这个问题我之前也碰到过——直接在@Select注解里嵌套${metadata.attributes.${metadata.idAttribute}...}这种写法确实走不通,MyBatis的OGNL在注解字符串里不支持这种嵌套的动态变量解析。下面给你两种靠谱的解决方案,按需选就行:
方案一:Java层提前提取字段(最省心的方式)
这种方法简单直接,不用折腾MyBatis的复杂语法,先在调用Mapper之前,把需要的id字段名和version字段名从Metadata里抠出来,单独作为参数传进去:
第一步:调整Mapper接口
@Select("SELECT ${versionField} " + "FROM ${metadata.table} " + "WHERE ${idField} = #{id}") public byte getVersion(@Param("metadata") Metadata metadata, @Param("id") long id, @Param("idField") String idField, @Param("versionField") String versionField);
第二步:调用前先提取字段
// 先找到对应id的Attribute对象 Attribute idAttribute = metadata.getAttributes().stream() .filter(attr -> attr.getName().equals(metadata.getIdAttribute())) .findFirst() .orElseThrow(() -> new IllegalArgumentException("找不到匹配的id属性")); // 提取id字段名 String idField = idAttribute.getFields().stream() .filter(field -> "id".equals(field.getName())) .map(Field::getName) .findFirst() .orElseThrow(() -> new IllegalArgumentException("id字段不存在")); // 提取version字段名 String versionField = idAttribute.getFields().stream() .filter(field -> "version".equals(field.getName())) .map(Field::getName) .findFirst() .orElseThrow(() -> new IllegalArgumentException("version字段不存在")); // 最后调用Mapper byte version = yourMapper.getVersion(metadata, id, idField, versionField);
这种方式逻辑清晰,调试起来也方便,出问题一眼就能看到哪里错了,适合大多数常规场景。
方案二:用MyBatis XML动态SQL(更优雅的进阶方式)
如果不想在Java代码里处理字段提取,想把这部分逻辑放在SQL层面,可以用MyBatis的XML映射文件,它的OGNL支持更强大的集合操作:
第一步:写XML映射文件(比如ObjectMapper.xml)
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.your.package.ObjectMapper"> <select id="getVersion" resultType="byte"> SELECT <!-- 从id对应的Attribute里筛选出name为version的字段,取它的name值 --> ${metadata.attributes.![#this.name == metadata.idAttribute].fields.![#this.name == 'version'].name} FROM ${metadata.table} WHERE <!-- 同样筛选出id字段的name值 --> ${metadata.attributes.![#this.name == metadata.idAttribute].fields.![#this.name == 'id'].name} = #{id} </select> </mapper>
第二步:修改Mapper接口(去掉@Select注解)
public byte getVersion(@Param("metadata") Metadata metadata, @Param("id") long id);
这里用到了OGNL的投影语法(![表达式]),它会遍历集合,挑出符合条件的元素,然后提取对应的属性。不过要注意,得确保你的Metadata数据是规范的——每个筛选条件都只能匹配到一个元素,不然SQL会因为多个字段名报错。
为啥注解里的写法不行?
简单说,MyBatis处理@Select里的${}时,是把整个${xxx}当成一个完整的属性名去解析的,它不会先解析内部的${metadata.idAttribute}得到值,再去访问对应的属性。所以${metadata.attributes.${metadata.idAttribute}}这种嵌套写法会被当成一个不存在的属性,肯定报错。
总结一下:场景简单就用方案一,省心;要是想让SQL层面更灵活,就用方案二的XML动态SQL。
内容的提问来源于stack exchange,提问作者efitapia

