【基础技术与框架】一篇文章搞掂:MyBatis

四、动态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:调用类的静态字段值

猜你喜欢

转载自www.cnblogs.com/LiveYourLife/p/9770784.html