大纲 前言 本文的所有案例代码,若没有特别说明,默认都基于以下的表结构和 JavaBean 类编写,可以直接从 GitHub 下载对应章节 mybatis-lesson-15
。
1 2 3 4 5 6 7 8 CREATE TABLE `t_employee` ( `id` int (11 ) NOT NULL AUTO_INCREMENT, `last_name` varchar (255 ) DEFAULT NULL , `gender` char (1 ) DEFAULT NULL , `email` varchar (255 ) DEFAULT NULL , `dept_id` int (11 ) DEFAULT NULL , PRIMARY KEY (`id`) ) ENGINE= InnoDB DEFAULT CHARSET= utf8;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class Employee { private Long id; private String lastName; private String gender; private String email; private Department department; ...... @Override public String toString () { return "Employee [id=" + id + ", lastName=" + lastName + ", gender=" + gender + ", email=" + email + ", department=" + department + "]" ; } }
动态 SQL 动态 SQL 是 MyBatis 强大特性之一,极大的简化拼装 SQL 的操作。 动态 SQL 标签和使用 JSTL 或其他类似基于 XML 的文本处理器相似。 MyBatis 采用功能强大的基于 OGNL 的表达式来简化操作,并提供了 if
、trim (where, set)
、foreach
、choose (when, otherwise)
动态 SQL 标签。 if 标签 1 2 3 4 5 public interface EmployeeMapper { public List<Employee> getEmpsByConditionIf (Employee employee) ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <mapper namespace ="com.clay.mybatis.dao.EmployeeMapper" > <select id ="getEmpsByConditionIf" resultType ="com.clay.mybatis.bean.Employee" > select id, last_name as lastName, gender, email from t_employee where <if test ="id != null" > id = #{id} </if > <if test ="lastName != null and lastName.trim() != ''" > and last_name = #{lastName} </if > <if test ="email != null and email.trim() != ''" > and email = #{email} </if > <if test ="gender == 1 or gender == 0" > and gender = #{gender} </if > </select > </mapper >
特别注意
上述的写法,如果传递进来的 JavaBean 参数的 id
属性刚好为 null
时,MyBatis 执行后会提示 SQL 存在语法错误(SQL 语句中有多余的 and
关键字),如下所示:
1 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'and last_name = 'Jim'
where 标签 解决上述 if
标签带来的 SQL 拼接出错问题,一般有以下两种方法:
第一种方法:在 where
关键字后面加上 1 = 1
,同时指定 if
标签里的内容都以 and
或者 or
关键字开头 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <mapper namespace ="com.clay.mybatis.dao.EmployeeMapper" > <select id ="getEmpsByConditionIf" resultType ="com.clay.mybatis.bean.Employee" > select id, last_name as lastName, gender, email from t_employee where 1 = 1 <if test ="id != null" > and id = #{id} </if > <if test ="lastName != null and lastName.trim() != ''" > and last_name = #{lastName} </if > <if test ="email != null and email.trim() != ''" > and email = #{email} </if > <if test ="gender == 1 or gender == 0" > and gender = #{gender} </if > </select > </mapper >
第二种方法:使用 where
标签替代 where
关键字 提示
where
标签用于封装查询条件,一般是和 if
标签一起使用where
标签只会在子标签返回任何内容的情况下,才自动插入 where
关键字使用 where
标签动态拼接 SQL 语句时,会自动将查询条件头部多出来的 and
或者 or
关键字去掉 特别注意,where
标签不会自动将查询条件尾部多出来的 and
或者 or
关键字去掉 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <mapper namespace ="com.clay.mybatis.dao.EmployeeMapper" > <select id ="getEmpsByConditionIf" resultType ="com.clay.mybatis.bean.Employee" > select id, last_name as lastName, gender, email from t_employee <where > <if test ="id != null" > id = #{id} </if > <if test ="lastName != null and lastName.trim() != ''" > and last_name = #{lastName} </if > <if test ="email != null and email.trim() != ''" > and email = #{email} </if > <if test ="gender == 1 or gender == 0" > and gender = #{gender} </if > </where > </select > </mapper >
trim 标签 除了上述的两种方法之外,还可以使用 trim
标签,以此解决 where
标签不能自动将查询条件尾部多出来的 and
或者 or
关键字去掉的问题。trim
标签用于自定义字符串内容的截取,其标签体中的内容是 SQL 字符串拼接后的结果。trim
标签拥有以下属性:
prefix
:给拼接后的整个字符串加一个前缀prefixOverrides
:去掉拼接后整个字符串头部多余的字符suffix
:给拼接后的整个字符串加一个后缀suffixOverrides
:去掉拼接后整个字符串尾部多余的字符1 2 3 4 5 public interface EmployeeMapper { public List<Employee> getEmpsByConditionTrim (Employee employee) ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <mapper namespace ="com.clay.mybatis.dao.EmployeeMapper" > <select id ="getEmpsByConditionTrim" resultType ="com.clay.mybatis.bean.Employee" > select id, last_name as lastName, gender, email from t_employee <trim prefix ="where" suffixOverrides ="and | or" > <if test ="id != null" > id = #{id} and </if > <if test ="lastName != null and lastName.trim() != ''" > last_name = #{lastName} and </if > <if test ="email != null and email.trim() != ''" > email = #{email} and </if > <if test ="gender == 1 or gender == 0" > gender = #{gender} </if > </trim > </select > </mapper >
choose 标签 choose
标签是按顺序判断其内部 when
标签中的 test
条件是否成立。它和 if - else
不太相同,choose
标签类似于 Java 中的 switch
语句用法,只要有判断条件成立,其它判断将得不到执行,如果所有条件都不成立则会执行 otherwise
标签中的内容。
1 2 3 4 5 public interface EmployeeMapper { public List<Employee> getEmpsByConditionChoose (Employee employee) ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <mapper namespace ="com.clay.mybatis.dao.EmployeeMapper" > <select id ="getEmpsByConditionChoose" resultType ="com.clay.mybatis.bean.Employee" > select id, last_name as lastName, gender, email from t_employee <where > <choose > <when test ="id != null" > id = #{id} </when > <when test ="lastName != null and lastName.trim() != ''" > last_name = #{lastName} </when > <when test ="email != null and email.trim() != ''" > email = #{email} </when > <otherwise > gender = 1 </otherwise > </choose > </where > </select > </mapper >
set 标签 1 2 3 4 5 public interface EmployeeMapper { public void updateEmp (Employee employee) ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <mapper namespace ="com.clay.mybatis.dao.EmployeeMapper" > <update id ="updateEmp" parameterType ="com.clay.mybatis.bean.Employee" > update t_employee set <if test ="lastName != null and lastName.trim() != ''" > last_name = #{lastName}, </if > <if test ="email != null and email.trim() != ''" > email = #{email}, </if > <if test ="gender == 1 or gender == 0" > gender = #{gender} </if > where id = #{id} </update > </mapper >
特别注意
上述 SQL 更新语句的写法,如果传递进来的 JavaBean 参数的 last_name
不为 null
,且 gender
属性刚好为 null
时,MyBatis 执行后会提示 SQL 存在语法错误(SQL 语句中存在多余的逗号),如下所示:
1 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'where id = 1'
提示
set
标签用于 SQL 更新语句中,一般是和 if
标签一起使用set
标签可以解析为 set
关键字,并去除 SQL 更新语句中尾部多余的逗号为了解决上面 SQL 拼接出错的问题,可以使用 set
标签来解决:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <mapper namespace ="com.clay.mybatis.dao.EmployeeMapper" > <update id ="updateEmp" parameterType ="com.clay.mybatis.bean.Employee" > update t_employee <set > <if test ="lastName != null and lastName.trim() != ''" > last_name = #{lastName}, </if > <if test ="email != null and email.trim() != ''" > email = #{email}, </if > <if test ="gender == 1 or gender == 0" > gender = #{gender} </if > </set > where id = #{id} </update > </mapper >
或者也可以使用 trim
标签来解决问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <mapper namespace ="com.clay.mybatis.dao.EmployeeMapper" > <update id ="updateEmp" parameterType ="com.clay.mybatis.bean.Employee" > update t_employee <trim prefix ="set" suffixOverrides ="," > <if test ="lastName != null and lastName.trim() != ''" > last_name = #{lastName}, </if > <if test ="email != null and email.trim() != ''" > email = #{email}, </if > <if test ="gender == 1 or gender == 0" > gender = #{gender} </if > </trim > where id = #{id} </update > </mapper >
foreach 标签 属性说明 foreach
标签可用于遍历集合,并拥有以下属性:
属性 必填 描述 collection 是 表示迭代集合的名称 item 是 表示本次迭代获取的元素,若 collection
为 List、Set 或者数组,则表示其中的元素;若 collection
为 Map,则代表 Map 的 value
open 否 表示该语句以什么字符开始,最常用的是左括弧 (
字符,Mybatis 会将该字符拼接到整体的 SQL 语句之前,并且只拼接一次 close 否 表示该语句以什么字符结束,最常用的是右括弧 )
,MyBatis 会将该字符拼接到整体的 SQL 语句之后,并且只拼接一次 separator 否 MyBatis 会在每次迭代后给 SQL 语句拼接上 separator
属性指定的字符(例如逗号 ,
) index 否 在 List、Set 和数组中,index
表示当前迭代的位置(索引),在 Map 中,index
代表的是 Map 的 key
在使用 foreach
标签的时候,最关键也是最容易出错的就是 collection
属性,该属性是必须指定的,但是在不同情况下,该属性的值是不一样的,主要有以下 3 种情况:
如果传入的是单参数且参数类型是一个 List 的时候,collection
的属性值默认是 list
如果传入的是单参数且参数类型是一个数组的时候,collection
的属性值默认是 array
如果传入的参数有多个的时候,就需要把它们封装成一个 Map 了,当然单参数也可以封装成 Map。实际上在传入参数的时候,MyBatis 的底层代码也是会自动把参数封装成一个 Map 的,Map 的 key
就是参数名,所以这个时候 collection
属性值就是传入的 List 或 Array 对象在自己封装的 Map 里面的 key
提示
在 MyBatis 中,使用 List 传递参数的详细教程可看这里 。
数据批量查询案例 1 2 3 4 5 public interface EmployeeMapper { public List<Employee> getEmpsByConditionByForeach (List<Long> ids) ; }
1 2 3 4 5 6 7 8 9 10 11 12 <mapper namespace ="com.clay.mybatis.dao.EmployeeMapper" > <select id ="getEmpsByConditionByForeach" resultType ="com.clay.mybatis.bean.Employee" > select id, last_name as lastName, gender, email from t_employee where id in <foreach collection ="list" item ="id" open ="(" separator ="," close =")" > #{id} </foreach > </select > </mapper >
或者使用 @Param
注解指定参数名称:
1 2 3 4 5 public interface EmployeeMapper { public List<Employee> getEmpsByConditionByForeach (@Param("ids") List<Long> ids) ; }
1 2 3 4 5 6 7 8 9 10 11 12 <mapper namespace ="com.clay.mybatis.dao.EmployeeMapper" > <select id ="getEmpsByConditionByForeach" resultType ="com.clay.mybatis.bean.Employee" > select id, last_name as lastName, gender, email from t_employee where id in <foreach collection ="ids" item ="id" open ="(" separator ="," close =")" > #{id} </foreach > </select > </mapper >
MySQL 数据库 数据批量插入案例一 1 2 3 4 5 public interface EmployeeMapper { public void addEmps (@Param("emps") List<Employee> emps) ; }
1 2 3 4 5 6 7 8 9 10 11 12 <mapper namespace ="com.clay.mybatis.dao.EmployeeMapper" > <insert id ="addEmps" > insert into t_employee(last_name, gender, email) values <foreach collection ="emps" item ="emp" separator ="," > (#{emp.lastName}, #{emp.gender}, #{emp.email}) </foreach > </insert > </mapper >
数据批量插入案例二 1 2 3 4 5 public interface EmployeeMapper { public void addEmps (@Param("emps") List<Employee> emps) ; }
1 2 3 4 5 6 7 8 9 10 11 <mapper namespace ="com.clay.mybatis.dao.EmployeeMapper" > <insert id ="addEmps" > <foreach collection ="emps" item ="emp" > insert into t_employee(last_name, gender, email) values(#{emp.lastName}, #{emp.gender}, #{emp.email}); </foreach > </insert > </mapper >
特别注意
默认情况下 MyBatis 不允许多条语句同时执行,因此需要在连接 MySQL 的 url
中增加 allowMultiQueries=true
配置内容,否则上述 SQL 执行时会出错。 如果批量插入大量的数据,上述的批量插入方式可能会执行失败,这是由于 MySQL 对 SQL 语句的长度有限制导致的。 Oracle 数据库 Oracle 不支持使用 insert ... values(),()...
的方式批量插入数据,但支持使用 begin ... end;
或者中间表的方式来实现数据的批量插入。
数据批量插入案例一 1 2 3 4 5 public interface EmployeeMapper { public void addEmps (@Param("emps") List<Employee> emps) ; }
1 2 3 4 5 6 7 8 9 10 11 <mapper namespace ="com.clay.mybatis.dao.EmployeeMapper" > <insert id ="addEmps" databaseId ="oracle" > <foreach collection ="emps" item ="emp" open ="begin" close ="end;" > insert into t_employee(id, last_name, gender, email) values(employees_seq.nextval, #{emp.lastName}, #{emp.gender}, #{emp.email}); </foreach > </insert > </mapper >
最终会发出类似下面的 SQL 语句到 Oracle 数据库:
1 2 3 4 5 begin insert into t_employee(id, last_name, gender, email) values (1 , 'Jim' , '1' , 'jim@gmail.com' ); insert into t_employee(id, last_name, gender, email) values (2 , 'Tom' , '1' , 'tom@gmail.com' ); insert into t_employee(id, last_name, gender, email) values (3 , 'Peter' , '1' , 'peter@gmail.com' ); end ;
数据批量插入案例二 1 2 3 4 5 public interface EmployeeMapper { public void addEmps (@Param("emps") List<Employee> emps) ; }
1 2 3 4 5 6 7 8 9 10 11 <mapper namespace ="com.clay.mybatis.dao.EmployeeMapper" > <insert id ="addEmps" databaseId ="oracle" > insert into t_employee(id, last_name, gender, email) <foreach collection ="emps" item ="emp" separator ="union" open ="select employees_seq.nextval, lastName, gender, email from (" close =")" > select #{emp.lastName} lastName, #{emp.gender} gender, #{emp.email} email from dual </foreach > </insert > </mapper >
最终会发出类似下面的 SQL 语句到 Oracle 数据库:
1 2 3 4 5 6 7 8 insert into t_employee(id, last_name, gender, email)select employees_seq.nextval, lastName, gender, email from ( select 'Jim' lastName, '1' gender, 'jim@gmail.com' email from dual union select 'Tom' lastName, '1' gender, 'tom@gmail.com' email from dual union select 'Peter' lastName, '1' gender, 'peter@gmail.com' email from dual )
script 标签 若要在带注解的 DAO 接口中使用动态 SQL 语句,则可以使用 script
标签来实现,例如:
1 2 3 4 5 6 7 8 9 10 11 @Update({"<script>", "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}", "</script>"}) public void updateAuthorValues (Author author) ;
多数据库支持 若在 MyBatis 的全局配置文件中配置了 databaseIdProvider
标签(数据库厂商标识),那么可以在动态 SQL 中使用名为 _databaseId
的变量(MyBatis 内置参数)来为不同的数据库构建特定的语句。_databaseId
内置参数的详细使用教程,可以查看这里 。
1 2 3 4 5 6 7 8 9 <configuration > <databaseIdProvider type ="DB_VENDOR" > <property name ="SQL Server" value ="sqlserver" /> <property name ="DB2" value ="db2" /> <property name ="MySQL" value ="mysql" /> <property name ="Oracle" value ="oracle" /> </databaseIdProvider > </configuration >
1 2 3 4 5 6 7 8 9 10 11 <insert id ="insert" > <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 >
插入脚本语言 MyBatis 从 3.2
版本开始支持插入脚本语言,这允许开发者插入一种语言驱动,并基于这种语言来编写动态 SQL 查询语句。
1 2 3 4 5 6 7 public interface LanguageDriver { ParameterHandler createParameterHandler (MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) ; SqlSource createSqlSource (Configuration configuration, XNode script, Class<?> parameterType) ; SqlSource createSqlSource (Configuration configuration, String script, Class<?> parameterType) ; }
实现自定义语言驱动后,就可以在 mybatis-config.xml
文件中将它设置为默认语言 1 2 3 4 5 6 <typeAliases > <typeAlias type ="org.sample.MyLanguageDriver" alias ="myLanguage" /> </typeAliases > <settings > <setting name ="defaultScriptingLanguage" value ="myLanguage" /> </settings >
或者使用 lang
属性为特定的 SQL 语句指定语言 1 2 3 <select id ="selectBlog" lang ="myLanguage" > SELECT * FROM BLOG </select >
或者在 Mapper 接口上添加 @Lang
注解 1 2 3 4 5 6 7 public interface Mapper { @Lang(MyLanguageDriver.class) @Select("SELECT * FROM BLOG") List<Blog> selectBlog () ; }
提示
MyBatis 支持使用 Apache Velocity 作为动态语言,更多细节请参考 MyBatis-Velocity 项目。
前面介绍的所有 xml
标签都由默认 MyBatis 语言提供,而它由语言驱动 org.apache.ibatis.scripting.xmltags.XmlLanguageDriver
(别名为 xml
)所提供。