MyBatis动态if/trim/where/set标签实例详细说明的重点在于把前置条件、操作顺序和容易误判的地方分清楚。
动态SQL是Mybatis的强大特性之一,能够完成不同的sql拼接
官网文档为:
本节是对上一章内容经行的扩充

在注册用户时,我们经常会遇到必填字段和非必填字段的情况。例如,性别(gender)可能是一个非必填字段,程序需要根据用户是否填写该字段来动态生成SQL语句。这时就可以使用MyBatis的<if>标签来实现条件判断。
// 根据条件插入用户信息Integer insertUserByCondition(UserInfo userInfo);
<insert id="insertUserByCondition"> INSERT INTO userinfo ( username, `password`, age, <if test="gender != null"> gender, </if> phone ) VALUES ( #{username}, #{password}, #{age}, <if test="gender != null"> #{gender}, </if> #{phone} )</insert>@Insert("<script>" + "INSERT INTO userinfo (username, `password`, age, " + "<if test='gender!=null'>gender,</if> " + "phone) " + "VALUES(#{username}, #{password}, #{age}, " + "<if test='gender!=null'>#{gender},</if> " + "#{phone})" + "</script>")Integer insertUserByCondition(UserInfo userInfo);注意事项:
test="gender != null"中的gender是传入Java对象的属性名,不是数据库字段名。<script></script>标签包裹动态SQL,但IDEA不会进行格式检查,容易出错,建议初学者使用XML方式。当有多个字段都可能成为选填项时,使用多个<if>标签会导致SQL语句末尾出现多余的逗号。这时可以使用<trim>标签结合<if>标签,对多个字段进行动态生成。
<insert id="insertUserByCondition"> INSERT INTO userinfo <trim prefix="(" suffix=")" suffixOverrides=","> <if test="username != null"> username, </if> <if test="password != null"> `password`, </if> <if test="age != null"> age, </if> <if test="gender != null"> gender, </if> <if test="phone != null"> phone, </if> </trim> VALUES <trim prefix="(" suffix=")" suffixOverrides=","> <if test="username != null"> #{username}, </if> <if test="password != null"> #{password}, </if> <if test="age != null"> #{age}, </if> <if test="gender != null"> #{gender}, </if> <if test="phone != null"> #{phone} </if> </trim></insert>在SQL动态解析时,第一个<trim>部分会进行如下处理:
prefix配置,开始部分加上(suffix配置,结束部分加上)<if>组织的语句都以,结尾,在最后拼接好的字符串还会以,结尾,会基于suffixOverrides配置去掉最后一个,@Insert("<script>" + "INSERT INTO userinfo " + "<trim prefix='(' suffix=')' suffixOverrides=','>" + "<if test='username!=null'>username,</if>" + "<if test='password!=null'>password,</if>" + "<if test='age!=null'>age,</if>" + "<if test='gender!=null'>gender,</if>" + "<if test='phone!=null'>phone,</if>" + "</trim> " + "VALUES " + "<trim prefix='(' suffix=')' suffixOverrides=','>" + "<if test='username!=null'>#{username},</if>" + "<if test='password!=null'>#{password},</if>" + "<if test='age!=null'>#{age},</if>" + "<if test='gender!=null'>#{gender},</if>" + "<if test='phone!=null'>#{phone}</if>" + "</trim>" + "</script>")Integer insertUserByCondition(UserInfo userInfo);在实际业务中,我们经常需要根据不同的筛选条件动态组装WHERE子句。<where>标签可以智能地处理这种情况,避免SQL语法错误。
需求:传入用户对象,根据属性做WHERE条件查询,用户对象中属性不为null的,都作为查询条件。例如username为"a",则查询条件为WHERE username="a"。
// 根据条件查询用户List<UserInfo> queryByCondition(UserInfo userInfo);
<select id="queryByCondition" resultType="com.example.demo.model.UserInfo"> SELECT id, username, age, gender, phone, delete_flag, create_time, update_time FROM userinfo <where> <if test="age != null"> AND age = #{age} </if> <if test="gender != null"> AND gender = #{gender} </if> <if test="deleteFlag != null"> AND delete_flag = #{deleteFlag} </if> </where></select><trim prefix="where" prefixOverrides="and">替换,但这种方式在子元素都没有内容时,WHERE关键字也会保留@Select("<script>" + "SELECT id, username, age, gender, phone, delete_flag, create_time, update_time " + "FROM userinfo " + "<where>" + " <if test='age != null'> AND age = #{age} </if>" + " <if test='gender != null'> AND gender = #{gender} </if>" + " <if test='deleteFlag != null'> AND delete_flag = #{deleteFlag} </if>" + "</where>" + "</script>")List<UserInfo> queryByCondition(UserInfo userInfo);在更新操作中,我们经常需要根据传入的对象属性来动态更新字段。<set>标签可以智能地处理UPDATE语句中的SET部分,避免多余的逗号问题。
需求:根据传入的用户id属性,修改其他不为null的属性。只更新有值的字段,避免将null值更新到数据库。
// 根据条件更新用户信息Integer updateUserByCondition(UserInfo userInfo);
<update id="updateUserByCondition"> UPDATE userinfo <set> <if test="username != null"> username = #{username}, </if> <if test="age != null"> age = #{age}, </if> <if test="deleteFlag != null"> delete_flag = #{deleteFlag}, </if> </set> WHERE id = #{id}</update><trim prefix="set" suffixOverrides=",">替换,实现相同的功能@Update("<script>" + "UPDATE userinfo " + "<set>" + " <if test='username!=null'>username=#{username},</if>" + " <if test='age!=null'>age=#{age},</if>" + " <if test='deleteFlag!=null'>delete_flag=#{deleteFlag},</if>" + "</set>" + "WHERE id=#{id}" + "</script>")Integer updateUserByCondition(UserInfo userInfo);test属性中的字段名是Java对象的属性名<set>标签也会自动处理末尾的逗号在XML映射文件中配置SQL时,经常会遇到很多重复的SQL片段,导致代码冗余。<include>标签结合<sql>标签可以解决这个问题,通过抽取可重用的SQL片段来提高代码的复用性和可维护性。
在开发过程中,多个SQL语句可能包含相同的字段列表或条件片段,例如:
<!-- 查询所有用户 --><select id="queryAllUser" resultMap="BaseMap"> SELECT id, username, age, gender, phone, delete_flag, create_time, update_time FROM userinfo</select><!-- 根据ID查询用户 --><select id="queryById" resultType="com.example.demo.model.UserInfo"> SELECT id, username, age, gender, phone, delete_flag, create_time, update_time FROM userinfo WHERE id = #{id}</select>可以看到,两个查询语句都包含了相同的字段列表,这违反了DRY(Don't Repeat Yourself)原则。
<sql>标签用于定义可重用的SQL片段:
<sql id="allColumn"> id, username, age, gender, phone, delete_flag, create_time, update_time</sql>
特性说明:
<include>标签通过refid属性引用已定义的SQL片段:
<!-- 查询所有用户 --><select id="queryAllUser" resultMap="BaseMap"> SELECT <include refid="allColumn"/> FROM userinfo</select><!-- 根据ID查询用户 --><select id="queryById" resultType="com.example.demo.model.UserInfo"> SELECT <include refid="allColumn"/> FROM userinfo WHERE id = #{id}</select><include>标签还可以结合<property>标签传递参数:
<!-- 定义带表名前缀的SQL片段 --><sql id="userColumns"> ${alias}.id, ${alias}.username, ${alias}.age</sql><!-- 使用带参数的SQL片段 --><select id="queryUserWithAlias" resultType="com.example.demo.model.UserInfo"> SELECT <include refid="userColumns"> <property name="alias" value="u"/> </include> FROM userinfo u</select>baseColumns、whereConditions等场景一:多表查询字段复用
<sql id="userBaseColumns"> u.id, u.username, u.email, u.create_time</sql><sql id="orderBaseColumns"> o.id as order_id, o.order_no, o.amount, o.status</sql><select id="queryUserWithOrders" resultMap="UserOrderMap"> SELECT <include refid="userBaseColumns"/>, <include refid="orderBaseColumns"/> FROM user u LEFT JOIN orders o ON u.id = o.user_id WHERE u.id = #{userId}</select>场景二:复杂条件复用
<sql id="activeUserCondition"> delete_flag = 0 AND status = 1</sql><select id="queryActiveUsers" resultType="com.example.demo.model.UserInfo"> SELECT * FROM userinfo WHERE <include refid="activeUserCondition"/></select><select id="countActiveUsers" resultType="java.lang.Integer"> SELECT COUNT(*) FROM userinfo WHERE <include refid="activeUserCondition"/></select>