MyBatis聚焦于SQL Mapping,支持动态SQL,这使得MyBatis很强大。当然针对不同的数据厂商会有特殊函数的情况,也可以使用动态SQL功能实现差异化调用。
一、XMLScriptBuilder解析配置 在SQL Mapping构建阶段会根据需要初始化一个XMLScriptBuilder的实例。XMLScriptBuilder会解析生成SqlSource对象。SqlSource需要一个重要的对象:SqlNode。XMLScriptBuilder的parseDynamicTags(XNode node)会解析配置文件来构建必要的SqlNode对象。SqlNode正是MyBatis的重要特性,即动态SQL的关键对象。(Ctrl+alt+h)
当XML节点类型为CDATA_SECTION_NODE和TEXT_NODE时,节点会被当做静态配置,其他节点则会被XMLScriptBuilder的处理器NodeHandler处理成动态的SqlNode。MyBatis用策略模式实现对配置文件中不同标签的解析,如下所示是动态SQL与处理器的映射关系。private void initNodeHandlerMap() { nodeHandlerMap.put(“trim”, new TrimHandler()); nodeHandlerMap.put(“where”, new WhereHandler()); nodeHandlerMap.put(“set”, new SetHandler()); nodeHandlerMap.put(“foreach”, new ForEachHandler()); nodeHandlerMap.put(“if”, new IfHandler()); nodeHandlerMap.put(“choose”, new ChooseHandler()); nodeHandlerMap.put(“when”, new IfHandler()); nodeHandlerMap.put(“otherwise”, new OtherwiseHandler()); nodeHandlerMap.put(“bind”, new BindHandler()); } 二、动态SQL配置 (1)trim标签
trim标签一般用于去除sql语句中多余的and关键字,逗号,或者给sql语句前拼接 “where“、“set“以及“values(“ 等前缀,或者添加“)“等后缀,可用于选择性插入、更新、删除或者条件查询等操作。 以下是trim标签中涉及到的属性: prefix :给sql语句拼接的前缀 suffix:给sql语句拼接的后缀 prefixOverrides:去除sql语句前面的关键字或者字符,该关键字或者字符由prefixOverrides属性指定,假设该属性为"AND",当sql语句的开头为"AND",trim标签将会去除该"AND" suffixOverrides:去除sql语句后面的关键字或者字符,该关键字或者字符由suffixOverrides属性指定1.使用trim标签去除多余的and关键字
SELECT * FROM BLOG WHERE state = #{state} AND title like #{title} AND author_name like #{author.name} 如果这些条件没有一个能匹配上会发生什么?最终这条 SQL 会变成这样:SELECT * FROM BLOG WHERE 这会导致查询失败。如果仅仅第二个条件匹配又会怎样?这条 SQL 最终会是这样:
SELECT * FROM BLOG WHERE AND title like ‘someTitle’ 可以使用where标签来解决这个问题,where 元素只会在至少有一个子元素的条件返回 SQL 子句的情况下才去插入“WHERE”子句。而且,语句的开头为“AND”或“OR”,where 元素也会将它们去除。
SELECT * FROM BLOG state = #{state} AND title like #{title} AND author_name like #{author.name} trim标签也可以完成相同的功能,写法如下: state = #{state} AND title like #{title} AND author_name like #{author.name} 2.使用trim标签去除多余的逗号 insert into role ( roleName, note )values( #{roleName,jdbcType=VARCHAR}, #{note,jdbcType=VARCHAR} ) 如果note等于null,sql语句会变成如下:INSERT INTO role(role_name,) VALUES(roleName,) 插入将会失败。 使用trim标签可以解决此问题,只需做少量的修改,如下所示:
insert into role roleName, note values #{roleName,jdbcType=VARCHAR}, #{note,jdbcType=VARCHAR} 其中最重要的属性是 suffixOverrides="," 表示去除sql语句结尾多余的逗号(2)where标签
where当只有在一个以上的if条件有值的情况下才去插入“WHERE”子句。而且,若最后的内容是“AND”或“OR”开头的,where也知道如何将他们去除。(3)set标签
set标签元素主要是用在更新操作的时候,它的主要功能和 where 标签元素其实是差不多的,主要是在包含的语句前输出一个 set,然后如果包含的语句是以逗号结束的话将会把该逗号忽略,如果 set 包含的内容为空的话则会出错。有了 set 元素就可以动态的更新那些修改了的字段。 <update id="updateUser_if_set" parameterType="com.pojo.User"> UPDATE user <set> <if test="username!= null and username != '' "> username = #{username}, </if> <if test="sex!= null and sex!= '' "> sex = #{sex}, </if> <if test="birthday != null "> birthday = #{birthday}, </if> </set> WHERE user_id = #{userid}; </update>(4)foreach标签
foreach循环遍历集合,主要用在sql的in条件中。foreach元素的属性主要有 item、index、 collection、open、separator、close。 1.collection:表示要做foreach循环的对象 2. item:表示集合中每一个元素或者该集合的对象,支持对象点属性的方式获取属性。 3. index: 表示循环的下标,从0开始 4. open: 表示以什么开始 5. separator: 表示每次进行迭代之间以什么符号作为分隔符 6. close:表示以什么结束 <delete id="deleteUserByForeach" parameterType="java.util.ArrayList"> delete from user where id in <foreach collection="list" item="id" open="(" close=")" separator="," index="index"> #{id} </foreach> </delete>(5)if标签
if标签判断条件是否符合,如果符合就把sql片段加入到sql语句中 select * from commodity where 1 = 1 and date = #{date} <select id="getListByStartDateAndEndDate" resultMap="BaseResultMap"> select * from commodity where 1 = 1 <if test="#{startDate}.toString() != #{endDate}.toString()"> and date between #{startDate} and #{endDate} </if> </select>(6)choose标签、when标签 otherwise标签
有时候我们并不想应用所有的条件,而只是想从多个选项中选择一个。而使用if标签时,只要test中的表达式为 true,就会执行 if 标签中的条件。MyBatis 提供了 choose 元素。if标签是与(and)的关系,而 choose 是或(or)的关系。choose标签是按顺序判断其内部when标签中的test条件是否成立,如果有一个成立,则 choose 结束。当 choose 中所有 when 的条件都不满足时,则执行 otherwise 中的sql。类似于Java 的 switch 语句,choose 为 switch,when 为 case,otherwise 则为 default。 例如下面例子,同样把所有可以限制的条件都写上,方面使用。choose会从上到下选择一个when标签的test为true的sql执行。安全考虑,我们使用where将choose包起来,放置关键字多于错误。 <select id="getUserList_choose" resultMap="resultMap_user" parameterType="com.yiibai.pojo.User"> SELECT * FROM User u <where> <choose> <when test="username !=null "> u.username LIKE CONCAT(CONCAT('%', #{username, jdbcType=VARCHAR}),'%') </when> <when test="sex != null and sex != '' "> AND u.sex = #{sex, jdbcType=INTEGER} </when> <when test="birthday != null "> AND u.birthday = #{birthday, jdbcType=DATE} </when> <otherwise> <!-- 当所有条件都不满足时则执行这里 --> </otherwise> </choose> </where> </select>(6)bind标签
bind 标签可以使用 OGNL 表达式创建一个变量井将其绑定到上下文中。在前面的例子中, UserMapper.xml 有一个 selectByUser 方法,这个方法用到了 like 查询条件,部分代码如下 。
and uname like concat ('毛', #{userName} ,'毡') 使用concat 函数连接字符串,在 MySQL 中,这个函数支持多个参数,但在 Oracle 中只 支持两个参数。由于不 同数据库之间的语法差异 ,如果更换数据库,有些 SQL 语句可能就需要 重写。针对这种情况,可 以使用 bind 标签来避免由于更换数据库带来的一些麻烦。将上面的 方法改为 bind 方式后,代码如下。 and uname like # {userNameLike} bind 标签的两个属性都是必选项,name为绑定到上下文的变量名, value 为 OGNL 表达式。创建一个 bind 标签的变量后 ,就可以在下面直接使用,使用 bind 拼接字符串不仅可 以避免因更换数据库而修改 SQL,也能预防 SQL 注入。