四、动态SQL
使用ORM的人都知道,根据不同条件拼装SQL时会遇到很多麻烦,例如缺少空格、去掉最后列名的逗号等问题
MyBatis采用 OGNL(Object-Graph Navigation Language)表达式实现动态SQL,更加简便
4.1、if标签
根据标签中的test表达式(下面会详细讲解),如果符合表达式,则拼接在语句中;否则忽略
用于Where条件
<select id="selectTestIf" parameterType="com.LTSolution.ShopApp.Model.Product" resultMap="BaseResultMap"> select * from Product where 1=1 <if test="name != null and name != ''"> and Name like '%'+#{name}+'%' </if> <if test="code != null and code != ''"> and Code like '%'+#{code}+'%' </if> order by code </select>
注意:where 1=1 :当2个条件都没有时,如果不加1=1,会产生语句“select * from Product where”,是一个错误语句;可以使用Where标签解决(下面会讲解)
用于Update更新
<update id="updateTestIf"> update Product set <if test="name != null and name != ''"> Name = #{name}, </if> <if test="brand != null and brand != ''"> Brand = #{brand}, </if> Code = #{code} where Code = #{code} </update>
注意:赋值Code = #{code},也是为了防止拼装出来的语句有误;可以使用Where标签或者Set标签解决(下面会讲解)
用于Insert插入
动态插入,如果实体的值为空,则不插入,这样可以让字段的值使用数据库的默认值,而不是实体中带的空值。
<update id="insertTestIf" parameterType="com.LTSolution.ShopApp.Model.Product"> insert into Product( Code,TypeId, <if test="brand != null and brand != ''"> Brand, </if> Name) values( #{code},#{typeid}, <if test="brand != null and brand != ''"> #{brand}, </if> #{name} ) </update>
注意,字段和数值都必须有对应的if,否则会报导致数值数量和字段数量不一致
4.2、choose标签
此标签提供了类似if...else...的功能;在此标签中必须至少包含一个when,0或1个otherwise
<select id="selectTestChoose" resultMap="BaseResultMap"> select * from Product where 1=1 <choose> <when test="code != null and code != ''"> and Code = #{code} </when> <when test="name != null and name != ''"> and Name = #{name} </when> <otherwise> and 1=2 </otherwise> </choose> order by code </select>
如果有编号,按编号查找;没有编号,按名称查找;都没有则查不到。
4.3、where、set 、 trim标签
他们都用来解决类似拼装时是否去掉第一个and,是否去掉最后一个逗号等问题
where标签
where 标签的作用:若标签中有内容,则自动插入一个where,否则不插入任何语句 ;如果 where 后面的字符串是以 AND 和 OR 开头的,就将它们剔除。
修改上面if标签中where例子,即可省掉where 1=1这个语句
<select id="selectTestIf" parameterType="com.LTSolution.ShopApp.Model.Product" resultMap="BaseResultMap"> select * from Product <where> <if test="name != null and name != ''"> and Name like '%'+#{name}+'%' </if> <if test="code != null and code != ''"> and Code = #{code} </if> </where> order by code </select>
set标签
set标签的作用:若标签中有内容,则自动插入一个set ;如果 set 后面的字符串是以逗号结尾的,就将这个逗号剔除。
修改上面if标签中update例子如下
<update id="updateTestIf"> update Product <set> <if test="name != null and name != ''"> Name = #{name}, </if> <if test="brand != null and brand != ''"> Brand = #{brand}, </if> Code = #{code}, </set> where Code = #{code} </update>
trim标签
where和set标签的功能都可以用trim标签来实现,并且在底层就是通过TrimSqlNode实现的
-
用trim标签实现where和set
<select id="selectTestIf" parameterType="com.LTSolution.ShopApp.Model.Product" resultMap="BaseResultMap"> select * from Product <trim prefix="WHERE" prefixOverrides="AND |OR "> <if test="name != null and name != ''"> and Name like '%'+#{name}+'%' </if> <if test="code != null and code != ''"> and Code = #{code} </if> </trim> order by code </select>
这里的AND和OR后面的空格不能省略,为了避免匹配到andes、orders等单词。
实际的prefixeOverrides包含“AND”、“OR”、“AND\n”、“OR\n”、“AND\r”、“OR\r”、“AND\t”、“OR\t”,不仅仅是上面提到的两个带空格的前缀。
<update id="updateTestIf"> update Product <trim prefix="SET" suffixOverrides=","> <if test="name != null and name != ''"> Name = #{name}, </if> <if test="brand != null and brand != ''"> Brand = #{brand}, </if> Code = #{code}, </trim> where Code = #{code} </update>
- trim标签有如下属性
- prefix:当trim元素内包含内容时,会给内容增加prefix指定的前缀。
- prefixOverrides:当trim元素内包含内容时,会把内容中匹配的前缀字符串去掉。
- suffix:当trim元素内包含内容时,会给内容增加suffix指定的后缀。
- suffixOverrides:当trim元素内包含内容时,会把内容中匹配的后缀字符串去掉。
四、foreach标签
SQL语句中有时会使用IN关键字,例如id in(1,2,3)。使用${ids}方式直接获取值的写法不能防止SQL注入;使用#{}加上foreach标签的写法,可以避免SQL注入。
foreach可以对数组、Map或实现了Iterable接口(如List、Set)的对象进行遍历。
数组在处理时会转换为List对象,因此foreach遍历的对象可以分为两大类:Iterable类型和Map类型。
foreach包含以下属性:
collection:必填,值为要迭代循环的属性名。这个属性值的情况有很多(下面讲解)。
item:变量名,值为从迭代对象中取出的每一个值。
index:索引的属性名,在集合数组情况下值为当前索引值,当迭代循环的对象是Map类型时,这个值为Map的key(键值)。
open:整个循环内容开头的字符串。
close:整个循环内容结尾的字符串。
separator:每次循环的分隔符。
这两种类型在遍历循环时情况不一样,例子如下:
1、实现in集合或数组
例子:使用传入的编号集合查找对应商品
//接口 List<Product> selectTestForeach(List<String> idList);
<select id="selectTestForeach" resultMap="BaseResultMap"> select * from Product where code in <foreach collection="list" open="(" close=")" separator="," item="id" index="i"> #{id} </foreach> order by code </select>
collection的设置:
1、只有一个数组参数或者集合参数
参数会先转换成Map类型,再进行处理
参数为集合时,转换成Map中一个key为colletion的值,此时写colletion="collection"
参数为List时,在上面基础上再添加一个key为list的值,此时写colletion="collection"或colletion="list"
参数为数组时,不同于上面2种情况,会转换成Map中一个key为array的值,此时写colletion="array"
但是推荐使用@Param来指定参数的名称,这样就可以写colletion="参数名"
2、多个参数
使用@param来命名,写colletion="参数名"
3、参数是Map类型
将collection指定为Map中的key
若要循环Map,可以使用@Param,然后colletion="参数名";或直接使用colletion="_parameter"
4、参数是一个对象
colletion="对象属性名"
若对象多层嵌套,可以使用属性 . 属性(集合和数组可以使用下标取值)的方式,作为collection的值
2、批量插入
如果数据库支持批量插入,就可以通过foreach来实现。批量插入是SQL-92新增的特性,目前支持的数据库有DB2、SQLServer2008及以上版本、PostgreSQL8.2及以上版本、MySQL、SQLite3.7.11及以上版本、H2。
sql语法:insert into table1 (c1,c2,c3..) values (v1,v2,v3..),(v1,v2,v3..),(v1,v2,v3..)
<update id="insertTestForeach"> insert into Product(Code,TypeId,Name) values <foreach collection="list" item="product" separator=","> (#{product.code},#{product.typeid},#{product.name}) </foreach> </update>
通过item指定了循环变量名后,在引用值的时候使用的是“属性.属性”的方式,如product.code
批量插入还可以返回自增主键id,这里不介绍
3、动态update
介绍当参数类型是Map的时候,如何用foreach进行动态update
// 接口 int updateTestForeach(Map<String, Object> map);
<update id="updateTestForeach"> update Product set <foreach collection="_parameter" item="val" index="key" separator=","> ${key} = #{val} </foreach> where Code = #{code} </update>
五、bind标签
bind标签可以使用OGNL表达式创建一个变量并绑定到上下文中
例如连接函数concat,不同数据库用法不同,可以改成用bind标签的OGNL来表示,即可消除不同数据库不同写法的影响,而且可以防止sql注入
bind标签有2个属性,name为给上下文引用的变量名,value为OGNL表达式
<select id="selectTestIf" parameterType="com.LTSolution.ShopApp.Model.Product" resultMap="BaseResultMap"> select * from Product <where> <if test="name != null and name != ''"> <bind name="nameLike" value="'%'+name+'%'" /> and Name like #{nameLike} </if> </where> order by code </select>
六、多数据库支持
使用if标签以及由MyBatis提供的databaseIdProvider数据库厂商标识配置
MyBatis根据映射语句中的databaseId属性,对不同的数据库执行对应的语句;MyBatis会加载不带databaseId属性以及匹配当前数据库databaseId属性的语句;如果同时找到同id,不带databaseId属性和带databaseId属性的语句,不带databaseId属性的语句会被舍弃掉。
1、设置
首先,需要在mybatis-config.xml文件中加入配置
<databaseIdProvider type="DB_VENDOR"/>
在这个设置下,系统通过DatabaseMetaData#getDatabaseProductName()方法获取数据库名称,但是根据不同数据库版本,返回的名称会不同,比较难管理,所以有以下方式
<databaseIdProvider type="DB_VENDOR"> <property name="SQL Server" value="sqlserver"/> <property name="DB2" value="db2"/> <property name="Oracle" value="oracle" /> <property name="MySQL" value="mysql"/> <property name="PostgreSQL" value="postgresql"/> <property name="Derby" value="derby"/> <property name="HSQL" value="hsqldb"/> <property name="H2" value="h2"/> </databaseIdProvider>
通过配置,只要DatabaseMetaData#getDatabaseProductName()的返回值部分字符匹配某个属性的name,就会返回对应的value值
注意!Spring Boot中不可以使用使用配置文件的形式,应该添加一个Bean
package com.LTSolution.ShopApp; import java.util.Properties; import org.apache.ibatis.mapping.DatabaseIdProvider; import org.apache.ibatis.mapping.VendorDatabaseIdProvider; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MyBatisBean { @Bean public DatabaseIdProvider getDatabaseIdProvider() { DatabaseIdProvider databaseIdProvider = new VendorDatabaseIdProvider(); Properties properties = new Properties(); properties.setProperty("Oracle", "oracle"); properties.setProperty("MySQL", "mysql"); properties.setProperty("DB2", "db2"); properties.setProperty("Derby", "derby"); properties.setProperty("H2", "h2"); properties.setProperty("HSQL", "hsql"); properties.setProperty("Informix", "informix"); properties.setProperty("SQL Server", "sqlserver"); properties.setProperty("PostgreSQL", "postgresql"); properties.setProperty("Sybase", "sybase"); properties.setProperty("Hana", "hana"); databaseIdProvider.setProperties(properties); return databaseIdProvider; } }
2、修改语句
方式1:对select,insert,delete,update,selectKey,sql标签,设置databaseId属性。MyBatis会根据数据库执行对应的语句
<select id="selectTestIf" databaseId="mysql"> .... </select> <select id="selectTestIf" databaseId="sqlserver"> .... </select>
方式2:使用_databaseId参数。为了避免大量重复的SQL,可以使用_databaseId参数进行对不同的部分进行判断。
<select id="selectTestIf" parameterType="com.LTSolution.ShopApp.Model.Product" resultMap="BaseResultMap"> select * from Product <where> <if test="name != null and name != ''"> <if test="_databaseId == 'mysql'"> and Name like concat('%'+#{name}+'%') </if> <if test="_databaseId == 'sqlserver'"> and Name like '%'+#{name}+'%' </if> </if> </where> order by code </select>
七、OGNL用法
在MyBatis的动态SQL和${}形式的参数中都用到了OGNL表达式。MyBatis常用的OGNL表达式如下:
1.e1 or e2
2.e1 and e2
3.e1==e2或e1 eq e2
4.e1 !=e2或e1 neq e2
5.e1 lt e2:小于
6.e1 lte e2:小于等于,其他表示为gt(大于)、gte(大于等于)
7.e1+e2、e1*e2、e1/e2、e1-e2、e1%e2
8.!e或not e:非,取反
9.e.method( args ):=调用对象方法
10.e.property:对象属性值
11.e1[e2]:按索引取值( List、数组和Map)
12.@class@method(args):调用类的静态方法
13.@class@field:调用类的静态字段值