MyBatis 入门

对于XML和annotation的SQL语句做一些梳理和总结

Posted by Haiming on October 11, 2019

高产似母猪之中的母猪……

MyBatis 因为其支持定制化 SQL,存储过程等等,而且避免了几乎所有的JDBC 代码和手动设置参数,以及获取结果集等等这些操作,深受广大被折磨的程序员的欢迎.

由于环境配置已经存在,那么我就直接略过环境配置的环节,直接上干货啦!

1. XML 映射文件

官方说明是:由于MyBatis 的映射语句的强大,其映射器的XML 文件变得相对简单,相对于同功能的JDBC 代码,可以发现其省略了将近 95% 的代码. MyBatis 为了聚焦于 SQL 而建,用来尽可能的减少麻烦.

SQL 映射文件只有几个很少的顶级元素.下面是按照其应该被定义的顺序列出:

  • Cache: 对给定 命名空间 的缓存配置
  • Cache-ref : 对其他命名空间的 缓存配置 的引用
  • resultMap: “最复杂且最强大”, 用来描述如何从 数据库结果集 之中加载对象.
  • sql: 可被其他语句引用的 可重用语句块
  • insert: 映射插入语句
  • update: 映射更新语句
  • delete: 映射删除语句
  • select: 映射查询语句

1.1 select

查询语句在任何情况之下都是使用最频繁的功能。同样,MyBatis 的基本原则之一,就是将焦点和努力放在查询和结果映射的原因,简单查询的 select 元素是非常简单的,比如:

<select id="selectPerson" parameterType="int" resultType="hashmap">
	SELECT * FROM PERSON WHERE ID = #{id}
</select>

这个语句的意义是:

  • 名字被称为 selectPerson
  • 接受一个 int 或者 Integer 类型的参数
  • 返回一个 HashMap 类型的对象

其中的键是列名,值就是结果行之中的对应值。

注意其参数符号为

#{id}

这相当于告诉 MyBatis 创建一个有预处理语句(PreparedStatement) 的参数,在 JDBC 之中,这样的一个参数在 SQL 之中就会由一个“?”来表示,并且被传递到一个新的预处理语句之中,类似于这样:

//下面是类似功能的 JDBC 代码,而不是 MyBatis 代码。
String selectPerson = "SELECT * FROM PERSON WHERE ID=?";
PreparedStatement ps = conn.prepareStatement(selectPerson);
ps.setInt(1,id);

MyBatis 还有更多的地方节省时间和细节,这些会在后面的小节之中呈现。

Select 允许配置很多的属性来配置每条语句的作用细节。

<select
  id="selectPerson"
  parameterType="int"
  parameterMap="deprecated"
  resultType="hashmap"
  resultMap="personResultMap"
  flushCache="false"
  useCache="true"
  timeout="10"
  fetchSize="256"
  statementType="PREPARED"
  resultSetType="FORWARD_ONLY">

下面是各种元素及其描述:

属性 描述
id 在命名空间中唯一的标识符,可以被用来引用这条语句。
parameterType 将会传入这条语句的参数类的完全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler) 推断出具体传入语句的参数,默认值为未设置(unset)。
parameterMap 这是引用外部 parameterMap 的已经被废弃的方法。请使用内联参数映射和 parameterType 属性。
resultType 从这条语句中返回的期望类型的类的完全限定名或别名。 注意如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身。可以使用 resultType 或 resultMap,但不能同时使用。
resultMap 外部 resultMap 的命名引用。结果集的映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂映射的情形都能迎刃而解。可以使用 resultMap 或 resultType,但不能同时使用。
flushCache 将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:false。
useCache 将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true。
timeout 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖驱动)。
fetchSize 这是一个给驱动的提示,尝试让驱动程序每次批量返回的结果行数和这个设置值相等。 默认值为未设置(unset)(依赖驱动)。
statementType STATEMENT,PREPARED 或 CALLABLE 中的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
resultSetType FORWARD_ONLY,SCROLL_SENSITIVE, SCROLL_INSENSITIVE 或 DEFAULT(等价于 unset) 中的一个,默认值为 unset (依赖驱动)。
databaseId 如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有的不带 databaseId 或匹配当前 databaseId 的语句;如果带或者不带的语句都有,则不带的会被忽略。
resultOrdered 这个设置仅针对嵌套结果 select 语句适用:如果为 true,就是假设包含了嵌套结果集或是分组,这样的话当返回一个主结果行的时候,就不会发生有对前面结果集的引用的情况。 这就使得在获取嵌套的结果集的时候不至于导致内存不够用。默认值:false
resultSets 这个设置仅对多结果集的情况适用。它将列出语句执行后返回的结果集并给每个结果集一个名称,名称是逗号分隔的。

1.2 insert,update 和 delete

其实格式和以前都几乎相同。 那么下面是一个例子:

<insert
  id="insertAuthor"
  parameterType="domain.blog.Author"
  flushCache="true"
  statementType="PREPARED"
  keyProperty=""
  keyColumn=""
  useGeneratedKeys=""
  timeout="20">

<update
  id="updateAuthor"
  parameterType="domain.blog.Author"
  flushCache="true"
  statementType="PREPARED"
  timeout="20">

<delete
  id="deleteAuthor"
  parameterType="domain.blog.Author"
  flushCache="true"
  statementType="PREPARED"
  timeout="20">  
属性 描述
id 命名空间中的唯一标识符,可被用来代表这条语句。
parameterType 将要传入语句的参数的完全限定类名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器推断出具体传入语句的参数,默认值为未设置(unset)。
parameterMap 这是引用外部 parameterMap 的已经被废弃的方法。请使用内联参数映射和 parameterType 属性。
flushCache 将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:true(对于 insert、update 和 delete 语句)。
timeout 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖驱动)。
statementType STATEMENT,PREPARED 或 CALLABLE 的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
useGeneratedKeys (仅对 insert 和 update 有用)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系数据库管理系统的自动递增字段),默认值:false。
keyProperty (仅对 insert 和 update 有用)唯一标记一个属性,MyBatis 会通过 getGeneratedKeys 的返回值或者通过 insert 语句的 selectKey 子元素设置它的键值,默认值:未设置(unset)。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
keyColumn (仅对 insert 和 update 有用)通过生成的键值设置表中的列名,这个设置仅在某些数据库(像 PostgreSQL)是必须的,当主键列不是表中的第一列的时候需要设置。如果希望使用多个生成的列,也可以设置为逗号分隔的属性名称列表。
databaseId 如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有的不带 databaseId 或匹配当前 databaseId 的语句;如果带或者不带的语句都有,则不带的会被忽略。

下面是示例:

<insert id="insertAuthor">
  insert into Author (id,username,password,email,bio)
  values (#{id},#{username},#{password},#{email},#{bio})
</insert>

<update id="updateAuthor">
	update Author set
  username = #{username},
  password = #{password},
  email = #{email},
  bio = #{bio}
 where id = #{id}
</update>

<delete id="deleteAuthor">
	delete from Author where id = #{id}
</delete>

可见,其插入语句的配置规则更丰富,在插入语句之中,也有一些额外属性和子元素用来处理主键的生成,而且有多种生成方式。

有自动生成主键方式的数据库,可以设置useGeneratedKeys="true", 再把 keyProperty 设置到目标属性上面就可以了。例如,上面的 Author 表已经对 id 进行了自动生成的列类型,那么可以修改为:

<insert id="insertAuthor" useGeneratedKeys="true"
    keyProperty="id">
  insert into Author (username,password,email,bio)
  values (#{username},#{password},#{email},#{bio})
</insert>

如果支持多行插入,那么也可以传入一个集合,并且返回自动生成的主键。

<insert id="insertAuthor" useGeneratedKeys="true"
    keyProperty="id">
  insert into Author (username, password, email, bio) values
  <foreach item="item" collection="list" separator=",">
    (#{item.username}, #{item.password}, #{item.email}, #{item.bio})
  </foreach>
</insert>

如果不支持自动生成类型的数据库,也就是不支持自动生成主键的 JDBC 驱动,有另外一种方式来生成主键。下面是一个简单实例,可以看到在其中,<selectKey> 的部分代码作为一种简单的生成 Key 的方法。

<insert id="insertAuthor">
  <selectKey keyProperty="id" resultType="int" order="BEFORE">
    select CAST(RANDOM()*1000000 as INTEGER) a from SYSIBM.SYSDUMMY1
  </selectKey>
  insert into Author
    (id, username, password, email,bio, favourite_section)
  values
    (#{id}, #{username}, #{password}, #{email}, #{bio}, #{favouriteSection,jdbcType=VARCHAR})
</insert>

上面这段代码对于不支持自动生成类型的数据库而言,其中的 selectKey 之中的语句会首先运行, Author 的 ID 会被设置,然后插入语句才会被调用。 换句话说,其实我们自己书写了一个和数据库之中自动生成主键类似的行为,但是是在xml 之中,所以我们保持了 Java 代码的简洁。

selectKey 的代码叙述如下:

<insert id="insertAuthor">
  <selectKey keyProperty="id" resultType="int" order="BEFORE">
    select CAST(RANDOM()*1000000 as INTEGER) a from SYSIBM.SYSDUMMY1
  </selectKey>
  insert into Author
    (id, username, password, email,bio, favourite_section)
  values
    (#{id}, #{username}, #{password}, #{email}, #{bio}, #{favouriteSection,jdbcType=VARCHAR})
</insert>
属性 描述
keyProperty selectKey 语句结果应该被设置的目标属性。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
keyColumn 匹配属性的返回结果集中的列名称。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
resultType 结果的类型。MyBatis 通常可以推断出来,但是为了更加精确,写上也不会有什么问题。MyBatis 允许将任何简单类型用作主键的类型,包括字符串。如果希望作用于多个生成的列,则可以使用一个包含期望属性的 Object 或一个 Map。
order 这可以被设置为 BEFORE 或 AFTER。如果设置为 BEFORE,那么它会首先生成主键,设置 keyProperty 然后执行插入语句。如果设置为 AFTER,那么先执行插入语句,然后是 selectKey 中的语句 - 这和 Oracle 数据库的行为相似,在插入语句内部可能有嵌入索引调用。
statementType 与前面相同,MyBatis 支持 STATEMENT,PREPARED 和 CALLABLE 语句的映射类型,分别代表 PreparedStatement 和 CallableStatement 类型。

1.3 sql

其可以被用来定义可重用的SQL 代码段。这些代码不仅可以被包含在其他语句之中,还可以被静态的设置参数。在不同的包含语句之中,可以设置不同的值到参数占位符上面。比如:

<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>

这个 SQL 语句可以被包含在其他语句之中,比如:

<select id="selectUsers" resultType="map">
  select
    <include refid="userColumns"><property name="alias" value="t1"/></include>,
    <include refid="userColumns"><property name="alias" value="t2"/></include>
  from some_table t1
    cross join some_table t2
</select>

属性值,也可以被用来 include 元素的 内部语句或者是 refid 属性之中,比如:

<sql id="sometable">
  ${prefix}Table
</sql>

<sql id="someinclude">
  from
    <include refid="${include_target}"/>
</sql>

<select id="select" resultType="map">
  select
    field1, field2, field3
  <include refid="someinclude">
    <property name="prefix" value="Some"/>
    <property name="include_target" value="sometable"/>
  </include>
</select>

1.4 参数

其实之前见到的所有语句之中,使用的都是简单参数

<select id="selectUsers" resultType="User">
  select id, username, password
  from users
  where id = #{id}
</select>

这个就是使用了一个非常简单的命名参数映射,参数类型设置成 int, 其就可以被设置成任何内容。简单类型原始数据类型 ,比如 Integer 和 String, 因为没有相关属性,其会完全用参数值来替代。但是如果是在传入一个复杂对象的情况之下,就会:

<insert id="insertUser" parameterType="User">
  insert into users (id, username, password)
  values (#{id}, #{username}, #{password})
</insert>

其如果是 User 类型的参数对象传递到了语句之中,这三个属性将会被查找,在查找之后会传入预处理语句的参数之中。

JBDC 要求,如果一个列允许 null 值,且会传递值是 null 的参数,那么必须要指定 JDBC Type。

数值类型还有一个小数保留位数的设置:

#{height,javaType=double,jdbcType=NUMERIC,numericScale=2}

1.5 字符串替换

默认情况下,使用 #{} 格式的语法会导致 MyBatis 创建 PrepareStatement 参数占位符, 并且安全的设置参数(就像使用 ? 一样),这样更安全迅速,也是首选做法。但是有的时候想在 SQL 之中插入一个不转义的字符串,比如 ORDER BY,就可以这样使用:

ORDER BY ${columnName}

这个对于有多种情况的select 特别有用,比如按照 列名 筛选,就不需要对于每个列都创建一个方法

@Select("select * from user where id = #{id}")
User findById(@Param("id") long id);

@Select("select * from user where name = #{name}")
User findByName(@Param("name") String name);

@Select("select * from user where email = #{email}")
User findByEmail(@Param("email") String email);

// and more "findByXxx" method

而是:

@Select("select * from user where ${column} = #{value}")
User findByColumn(@Param("column") String column, @Param("value") String value);

这种情况下,${column} 会被直接替换,而 #{value} 会被 ? 进行预处理,因此就可以像下面这样来达到查找不同列的目的:

User userOfId1 = userMapper.findByColumn("id", 1L);
User userOfNameKid = userMapper.findByColumn("name", "kid");
User userOfEmail = userMapper.findByColumn("email", "noone@nowhere.com");

但是这种方式可能会导致潜在的 SQL 注入攻击,因此要么直接不允许用户输入这些字段,要么自行进行 转义并检验

1.6 id & result

<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>

这些是结果映射最基本的内容。idresult 元素都将一个列的值映射到一个简单数据类型(String, int, double, Date 等)的属性或字段。

这两者之间的唯一不同是,id 元素表示的结果将是对象的标识属性,这会在比较对象实例时用到。 这样可以提高整体的性能,尤其是进行缓存和嵌套结果映射(也就是连接映射)的时候。

两个元素都有一些属性:

属性 描述
property 映射到列结果的字段或属性。如果用来匹配的 JavaBean 存在给定名字的属性,那么它将会被使用。否则 MyBatis 将会寻找给定名称的字段。 无论是哪一种情形,你都可以使用通常的点式分隔形式进行复杂属性导航。 比如,你可以这样映射一些简单的东西:“username”,或者映射到一些复杂的东西上:“address.street.number”。
column 数据库中的列名,或者是列的别名。一般情况下,这和传递给 resultSet.getString(columnName) 方法的参数一样。
javaType 一个 Java 类的完全限定名,或一个类型别名(关于内置的类型别名,可以参考上面的表格)。 如果你映射到一个 JavaBean,MyBatis 通常可以推断类型。然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证行为与期望的相一致。
jdbcType JDBC 类型,所支持的 JDBC 类型参见这个表格之后的“支持的 JDBC 类型”。 只需要在可能执行插入、更新和删除的且允许空值的列上指定 JDBC 类型。这是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 编程,你需要对可能存在空值的列指定这个类型。
typeHandler 我们在前面讨论过默认的类型处理器。使用这个属性,你可以覆盖默认的类型处理器。 这个属性值是一个类型处理器实现类的完全限定名,或者是类型别名。

1.7 构造方法

主要用在DTO 层面。

有些情况下,对于很少改变的值或者属性,都适合使用不可变类。 构造方法允许在初始化的时候设置属性的值,但是不暴露 getter 或者 setter

看看下面这个构造方法:

public class User {
   //...
   public User(Integer id, String username, int age) {
     //...
  }
//...
}

为了将结果注入构造方法,MyBatis 需要通过某种方式定位相应的构造方法。 在下面的例子中,MyBatis 搜索一个声明了三个形参的的构造方法,参数类型以 java.lang.Integer, java.lang.Stringint 的顺序给出。

<constructor>
   <idArg column="id" javaType="int"/>
   <arg column="username" javaType="String"/>
   <arg column="age" javaType="_int"/>
</constructor>

当你在处理一个带有多个形参的构造方法时,很容易搞乱 arg 元素的顺序。 从版本 3.4.3 开始,可以在指定参数名称的前提下,以任意顺序编写 arg 元素。 为了通过名称来引用构造方法参数,你可以添加 @Param 注解,或者使用 ‘-parameters’ 编译选项并启用 useActualParamName 选项(默认开启)来编译项目。下面是一个等价的例子,尽管函数签名中第二和第三个形参的顺序与 constructor 元素中参数声明的顺序不匹配。

<constructor>
   <idArg column="id" javaType="int" name="id" />
   <arg column="age" javaType="_int" name="age" />
   <arg column="username" javaType="String" name="username" />
</constructor>
属性 描述
column 数据库中的列名,或者是列的别名。一般情况下,这和传递给 resultSet.getString(columnName) 方法的参数一样。
javaType 一个 Java 类的完全限定名,或一个类型别名(关于内置的类型别名,可以参考上面的表格)。 如果你映射到一个 JavaBean,MyBatis 通常可以推断类型。然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证行为与期望的相一致。
jdbcType JDBC 类型,所支持的 JDBC 类型参见这个表格之前的“支持的 JDBC 类型”。 只需要在可能执行插入、更新和删除的且允许空值的列上指定 JDBC 类型。这是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 编程,你需要对可能存在空值的列指定这个类型。
typeHandler 我们在前面讨论过默认的类型处理器。使用这个属性,你可以覆盖默认的类型处理器。 这个属性值是一个类型处理器实现类的完全限定名,或者是类型别名。
select 用于加载复杂类型属性的映射语句的 ID,它会从 column 属性中指定的列检索数据,作为参数传递给此 select 语句。具体请参考关联元素。
resultMap 结果映射的 ID,可以将嵌套的结果集映射到一个合适的对象树中。 它可以作为使用额外 select 语句的替代方案。它可以将多表连接操作的结果映射成一个单一的 ResultSet。这样的 ResultSet 将会将包含重复或部分数据重复的结果集。为了将结果集正确地映射到嵌套的对象树中,MyBatis 允许你 “串联”结果映射,以便解决嵌套结果集的问题。想了解更多内容,请参考下面的关联元素。
name 构造方法形参的名字。从 3.4.3 版本开始,通过指定具体的参数名,你可以以任意顺序写入 arg 元素。参看上面的解释。

2. 缓存

MyBatis 之中有一个查询缓存机制,可以缓存会话之中的数据。

要使用全局之中的二级缓存,只需要在 SQL 之中加入一行:

<cache/>

下面是来自https://blog.csdn.net/u012373815/article/details/47069223 的关于 MyBatis 的缓存的内容~

一级缓存是SqlSession级别的缓存。在操作数据库时需要构造 sqlSession对象,在对象中有一个(内存区域)数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。

一级缓存的作用域是同一个SqlSession,在同一个sqlSession中两次执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。当一个sqlSession结束后该sqlSession中的一级缓存也就不存在了。Mybatis默认开启一级缓存。

二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession去操作数据库得到数据会存在二级缓存区域,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。

二级缓存是多个SqlSession共享的,其作用域是mapper的同一个namespace

二级缓存的规则:

  • 映射语句文件中的所有 select 语句的结果将会被缓存。
  • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
  • 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
  • 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
  • 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
  • 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

3. 动态 SQL

动态 SQL 可以摆脱需要拼接 SQL 语句的痛苦。

3.1 if

动态 SQL 要做的事根据条件来选择 包含where子句 的一部分,比如

<select id="findActiveBlogWithTitleLike"
     resultType="Blog">
  SELECT * FROM BLOG
  WHERE state = ‘ACTIVE’
  <if test="title != null">
    AND title like #{title}
  </if>
</select>

这条语句就提供了可选的查找功能,当 title 字段不为空的时候,不仅筛选所有状态为 active 的 blog, 还要对 title 进行模糊查找并且返回 BLOG 的结果。但是 title 字段如果为空,就不加后面的字段了。

多个条件筛选也没问题

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’
  <if test="title != null">
    AND title like #{title}
  </if>
  <if test="author != null and author.name != null">
    AND author_name like #{author.name}
  </if>
</select>

3.2 choose, when,otherwise

类似于 if…else if…else if, 这样的选择结构体。上面 3.1 之中的 if 举例无法做筛选,只要对应字段有那么一定会被 拼接好。但是在这个结构体之中,可以对于条件做筛选,类似于 if…else if ….finally的写法。

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’
  <choose>
    <when test="title != null">
      AND title like #{title}
    </when>
    <when test="author != null and author.name != null">
      AND author_name like #{author.name}
    </when>
    <otherwise>
      AND featured = 1
    </otherwise>
  </choose>
</select>

3.3 trim, where,set

但是前面这两种还是不能解决下面的问题: 如果所有的 if 条件全都没有命中,那么会出现 SQL 语句语法错误的问题。比如:

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG
  WHERE
  <if test="state != null">
    state = #{state}
  </if>
  <if test="title != null">
    AND title like #{title}
  </if>
  <if test="author != null and author.name != null">
    AND author_name like #{author.name}
  </if>
</select>

如果就像之前讲的: 一个 if 条件都没有命中,那么最后拼接出来的 SQL 语句会变成:

SELECT * FROM BLOG
WHERE

那么这种语句不可以简单的用条件句式解决了。可以直接用下面的方法,自定义一个处理方式来解决。注意其中的 <where>

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG
  <where>
    <if test="state != null">
         state = #{state}
    </if>
    <if test="title != null">
        AND title like #{title}
    </if>
    <if test="author != null and author.name != null">
        AND author_name like #{author.name}
    </if>
  </where>
</select>

<where> 会在至少有一个子元素的条件返回 SQL 子句的情况下,才去插入 where 语句。并且其会自动去除语句开头的 “AND” 或者 “OR”。

甚至在这个功能之中,我们可以自定义一个 where 的 trim 的处理方式。 比如,和自己默认的 where 元素等价的 自定义 trim 元素为:

<trim prefix="WHERE" prefixOverrides="AND |OR ">
  ...
</trim>

此处要注意 prefixOverrides 之中的空格,其会忽略通过管道分割之中的文本序列,作用就是

  • 移除指定在 prefixOverrides 之中的内容
  • 插入 prefix 属性之中指定的内容

我们在本节的小标题之中还提到了:set,set 元素用于动态包含需要更新的列,逻辑筛选过程和之前提到的差不多。

<update id="updateAuthorIfNecessary">
  update Author
    <set>
      <if test="username != null">username=#{username},</if>
      <if test="password != null">password=#{password},</if>
      <if test="email != null">email=#{email},</if>
      <if test="bio != null">bio=#{bio}</if>
    </set>
  where id=#{id}
</update>

这部分代码的功能我想大家都看明白了,也就是当传入的参数不为空的时候才对对应的列进行更新,其中<set> 就是起到更新功能的作用。

官方的说法是:set 元素会动态前置 SET 关键字,也会删掉无关的逗号。(比如最后一个 if 没有匹配上而产生的遗留逗号。)

set 等价的 trim 元素为:

<trim prefix="SET" suffixOverrides=",">
  ...
</trim>

注意和之前不同,这里是 suffixOverrides, 而不是之前的 prefixOverrides。 这个命令会删除之后的逗号。

3.4 foreach

foreach 的功能非常强大,其允许指定一个集合并且声明元素之内使用的 集合项(item) 和索引变量(index)。其也允许在元素之间添加分隔符。

<select id="selectPostIn" resultType="domain.blog.Post">
  SELECT *
  FROM POST P
  WHERE ID in
  <foreach item="item" index="index" collection="list"
      open="(" separator="," close=")">
        #{item}
  </foreach>
</select>

3.5 bind

bind 可以从 OGNL 之中创建一个本来没有的变量,并且将其使用在上下文。比如:

<select id="selectBlogsLike" resultType="Blog">
  <bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
  SELECT * FROM BLOG
  WHERE title LIKE #{pattern}
</select>

3.6 多数据库支持

很多情况下我们不同的数据使用的是不同种类的数据库,MyBatis 当然也支持对于不同种类数据库的分别处理,比如:

  <selectKey keyProperty="id" resultType="int" order="BEFORE">
    <if test="_databaseId == 'oracle'">
      select seq_users.nextval from dual
    </if>
    <if test="_databaseId == 'db2'">
      select nextval for seq_users from sysibm.sysdummy1"
    </if>
  </selectKey>
  insert into users values (#{id}, #{name})
</insert>

上面的语句就分别对于 Oracle 数据库和 DB2 数据库进行不同的语句操作。