知识工作插件系统:插件化架构设计与实操落地
2026/10/21 23:57:10
适合零基础到进阶:解释每个动态标签的作用、使用场景、常见坑,并给出基于本项目的示例(User/Student/Teacher)。代码含详尽注释,便于直接参考。
#{}预编译占位符,防 SQL 注入)。<if>:条件成立则拼接片段。<where>:自动处理首个条件前的AND/OR并在无条件时去掉WHERE。<set>:更新语句中自动去掉末尾逗号。<trim>:自定义前缀/后缀及多余字符的裁剪(prefixOverrides、suffixOverrides)。<choose>/<when>/<otherwise>:相当于 SQL 里的 if/else if/else。<foreach>:循环,常用于IN (...)、批量插入。<bind>:定义局部变量,构造更灵活的条件(本文示例主要用前几个标签)。entity.User/Student/Teacher(字段:id, name, age, birthday, gender, phone)。UserDao、StudentDao、TeacherDao。UserMapper.xml、StudentMapper.xml、TeacherMapper.xml。UserTest、StudentTest、TeacherTest。<where> + <if>)示例(User):
<selectid="dtFindUser"parameterType="entity.User"resultType="entity.User">select * from user<where><!-- 有条件时自动加 WHERE 并去掉首个 AND/OR --><iftest="name != null">and name like concat('%', #{name}, '%')<!-- 仅当 name 非空才拼 --></if><iftest="gender != null">and gender = #{gender}</if><iftest="phone != null">and phone like concat('%', #{phone}, '%')</if></where></select>要点:
<where>会:WHEREAND/OR#{}是预编译占位符,安全防注入。User对象,非 null 的字段才会生效。扩展到 Student/Teacher 只需改表名和类型,已在StudentMapper.xml/TeacherMapper.xml中实现。
<set> + <if>)示例(User):
<updateid="dtUpdate"parameterType="entity.User">update user<set><!-- 自动去掉最后一个逗号,避免 SQL 语法错误 --><iftest="name != null">name = #{name},</if><iftest="gender != null">gender = #{gender},</if><iftest="phone != null">phone = #{phone},</if><iftest="age != null">age = #{age},</if><iftest="birthday != null">birth_date = #{birthday},</if></set>where id = #{id}<!-- 必填,防止全表更新 --></update>要点:
<set>自动去掉最后一个逗号,避免语法错误。Student/Teacher 对应方法:dtUpdateStudent、dtUpdateTeacher。
<trim>)<selectid="trFindUser"parameterType="entity.User"resultType="entity.User">select * from user<trimprefix="where"prefixOverrides="and | or"><!-- 自定义 where,裁掉首个 AND/OR --><iftest="name != null">and name like concat('%', #{name}, '%')</if><iftest="gender != null">and gender = #{gender}</if><iftest="phone != null">and phone like concat('%', #{phone}, '%')</if></trim></select>要点:
prefix="where":有条件时自动加WHERE。prefixOverrides="and | or":去掉首个多余的AND/OR。<updateid="trUpdateUser"parameterType="entity.User">update user<trimprefix="set"suffixOverrides=","><!-- 功能类似 set,但可自定义裁剪 --><iftest="name != null">name = #{name},</if><iftest="gender != null">gender = #{gender},</if><iftest="phone != null">phone = #{phone},</if><iftest="age != null">age = #{age},</if><iftest="birthday != null">birth_date = #{birthday},</if></trim>where id = #{id}</update>suffixOverrides=",":去掉最后的逗号,等效于<set>,但更自由(可自定义前后缀)。Student/Teacher 已有同名的trFind*/trUpdate*版本。
<choose>/<when>/<otherwise>)示例(User):
<selectid="selectUserByChoose"resultType="entity.User"parameterType="entity.User">select * from user<where><choose><!-- 命中第一个 when 后就停止,适合按优先级选条件 --><whentest="name != null">name = #{name}</when><whentest="gender != null">gender = #{gender}</when><whentest="phone != null">phone = #{phone}</when><otherwise>id = #{id}</otherwise></choose></where></select>要点:
<when>,后续不会再执行。Student/Teacher 中有对应的selectStudentByChoose/selectTeacherByChoose。
<foreach>)<deleteid="deleteMore">delete from user where id in<foreachitem="id"collection="ids"index="index"open="("close=")"separator=",">#{id}<!-- 逐个绑定,防注入 --></foreach></delete>要点:
collection="ids"对应接口参数名@Param("ids") List<Integer> ids。open/close/separator控制括号与分隔符。<insertid="insertMore">insert into user (name, gender, phone, age, birth_date) values<foreachitem="user"collection="users"index="index"separator=",">(#{user.name}, #{user.gender}, #{user.phone}, #{user.age}, #{user.birthday})<!-- 拼成多行 values --></foreach></insert>要点:
collection="users"对应接口参数名@Param("users") List<User> users。(...),用逗号分隔。Student/Teacher 有同名的批量方法:deleteMore、insertMore。
安全写法:
<selectid="likeByName2"resultType="entity.User"parameterType="java.lang.String">select * from user where name like concat('%', #{value}, '%')</select>#{}预编译占位符,安全。${}直接拼接,除非做白名单校验的动态表/列名。(本项目中likeByName使用${value}的写法仅作对比示例,实际推荐likeByName2。)
<selectKey>)<insertid="insertGetId"parameterType="entity.User">insert into user (name, gender, phone, age, birth_date) values(#{name}, #{gender}, #{phone}, #{age}, #{birthday})<selectKeykeyProperty="id"resultType="java.lang.Integer"order="AFTER">select last_insert_id()</selectKey></insert>要点:
order="AFTER":先插入,再查自增主键。keyProperty="id":把生成的主键回填到入参对象的id字段。insertGetId。@TestpublicvoiddtFindUser(){Useruser=newUser();user.setName("张");// 只给 name,动态 where 会生成 name like '%张%'List<User>users=mapper.dtFindUser(user);users.forEach(System.out::println);}@TestpublicvoiddtUpdate(){Useruser=newUser();user.setId(13);user.setName("张三");// 只更新 nameintcount=mapper.dtUpdate(user);System.out.println(count);}@TestpublicvoiddeleteMore(){List<Integer>ids=newArrayList<>();ids.add(1);ids.add(2);intcount=mapper.deleteMore(ids);System.out.println(count);}@TestpublicvoidinsertMore(){List<User>users=newArrayList<>();users.add(newUser("批量1",18,newDate(),"男","1111"));users.add(newUser("批量2",19,newDate(),"女","2222"));intcount=mapper.insertMore(users);System.out.println(count);}运行说明:
SqlMapConfig.xml已注册对应 Mapper。@After里session.commit(),写操作会提交。@Param。${}注入风险:优先用#{};若必须用${}(动态列/表),先做白名单校验。otherwise限制)。<set>或suffixOverrides=","处理;不要手写逗号。<when>后不会再看其他条件,顺序要慎重。#{} 和 ${}区别?
#{}预编译占位,防注入;${}字符串直拼,易注入,仅在动态表/列名等必须场景使用且要白名单。<where>和<trim prefix="where">有何不同?
<where>自动加WHERE并去掉首个 AND/OR;<trim>更灵活,可自定义前后缀与裁剪规则。<set>与<trim prefix="set" suffixOverrides=",">的区别?
<trim>可自定义更复杂的裁剪逻辑,<set>更简洁专用于 UPDATE。<choose>适用场景?
批量操作用什么标签?
<foreach>,常见用于IN (...)和批量插入。如何防止动态更新把所有字段清空?
<set>包裹<if>,确保where id = ...必填。selectKey的作用?
order可选 BEFORE/AFTER。动态 SQL 是否影响性能?
<where> + <if>,优先级用<choose>,动态更新用<set>/<trim>,批量用<foreach>。@Param。#{}不用${};动态表/列名必须白名单。commit();或openSession(true)。<setting name="logImpl" value="STDOUT_LOGGING"/>便于排查。掌握动态 SQL 的几个标签组合,就能写出可维护、可扩展的查询和更新语句。建议多练习:
choose做优先级控制,避免多重 if 嵌套。祝学习顺利,面试稳稳拿下! 😊