
1.概述
这一次想把MyBatis的XML声明SQL的方式大概说一下。使用的demo可以参考:
《spring boot整合Mybatis3.5.4使用XML定义SQL》(https://blog.csdn.net/weixin_40763897/article/details/106069932)
MyBatis可以通过注解使用声明,也可以xml文件来声明SQL。前者简单,不灵活,后者不仅方便灵活,还方便优。通过XML来编写映射的SQL也是MyBatis所推荐的。MyBatis的一个映射器类就对象一个xml文件,xml文件写SQL语句。
xml文件的根元素是mapper,mppper元素可以包含以下子元素:
★ cache:指定的命名空间的缓存配置
★ cache-ref:引用其他命名空间缓存配置
★ resultMap:描述如何从数据库结果集中加载到对象中
★ parameterMap:在MyBatis3.5.4中弃用了!
★ sql:定义可重用的SQL块
★ insert:用于声明INSERT语句
★ update:用于声明UPDATE语句
★ delete:用于声明DELETE语句
★ select:用于声明SELECT语句
下面逐一讲解。

2.元素讲解
2.1.cache
默认情况下:
<cache/>
只启用本地会话缓存,只在会话期间缓存数据。有如下特点:
● 所有查询出来的结果集都会被缓存起来;
● 所有在映射声明文件中的insert、update、delete语句都会被放到缓存里;
● 使用最近最少使用的(LRU)逐出算法 ;
● 缓存不会按任何基于时间的计划刷新
● 缓存可以存储1024个列表或对象的个引用;
● 缓存可读可写,意味着缓存的对象是不共享的,因此能够被调用者安全地修改,因为不存在其他调用者或线程潜在的修改。
缓存配置只会对cache标记所在的映射文件中的语句有起作用。如果不想用默认的配置,可以修改以上缓存的属性,如:
<cacheeviction="FIFO"flushInterval="60000"size="512"readOnly="true"/>
创建一个FIFO缓存,并且每隔60秒就刷新一次,最多可以存储512个返回的结果对象或列表或对象的引用,而且缓存只能读不能写。因为写可能会在多个调用者或线程之间才生冲突。
缓存用的逐出策略有以下这些:
■ LRU(默认的):最近最少使用,移除那些长时间不使用的对象
■ FIFO:先进先出, 按照他们进入缓存的顺序移除对象
■ SOFT:软引用,基于垃圾收集器状态和软引用移除对象。
■ WEAK:弱引用, 更积极地基于垃圾收集器状态和弱引用规则移除对象
cache元素的属性:
| 属性 | 默认值 | 值 |
| eviction | LRU | LRU,FIFO,SOFT,WEAK |
| flushInterval | 无 | 正整数值,单位秒 |
size | 1024 | 正整数值 |
| readOnly | false | true,false |
2.1.1.自定义cache
使用自定义的cache:
<cache type="com.domain.something.MyCustomCache"/>
自定义cache要实现org.apache.ibatis.cache.Cache接口,MyBatis的Cache接口是这样的:
public interface Cache {String getId();int getSize();void putObject(Object key, Object value);Object getObject(Object key);boolean hasKey(Object key);Object removeObject(Object key);void clear();}
实现Cache接口:
public class MyCustomCache implements Cache{// ...}
点到为止,更多内容请到网上查阅!

2.2.cache-ref
引用其他命名空间的缓存配置。
<cache-ref namespace="com.wong.mybatis.SomeMapper"/>

resultMap是MyBatis中最重要的元素,因为将结果集映射到对象中,基本都用resultMap来完成。使用resultMap可以让你省去90%的JDBC代码,resultMap其甚至可以实现一些JDBC做不到的事。MyBatis自动创建ResultMap,基于名称自动映射列到JavaBean的属性上, 如果列名和JavaBean的属性匹配不上,我们可以在列名上使用select子句别名(标准SQL特性)来创建标签匹配:
<select id="selectUsers" resultType="User">selectuser_id as "id",user_name as "userName",hashed_password as "hashedPassword"from some_tablewhere id = #{id}</select>
除了上面这种给列名取个与JavaBean属性匹配得了的别名外,还可以使用<resultMap>标签来建立数据库表的列名与JavaBean属性的对应关系:
(1)第一步:使用resultMap标签建立列名与属性名的对应关系
<resultMap id="userResultMap" type="User"><id property="id" column="user_id" /><result property="username" column="user_name"/><result property="password" column="hashed_password"/></resultMap>
(2)第二步:引用resultMap
<select id="selectUsers" resultMap="userResultMap">select user_id, user_name, hashed_passwordfrom some_tablewhere id = #{id}</select>

resultMap不能和resultType同时使用,继续阅读你就明白的了。
数据库很难做到时时刻刻都能够和我们的JavaBean的属性匹配上。而且不是所有数据库都能很好的实现数据库设计第三范式或BCNF范式。这些问题都使处有时并不能简单地通过自动映射来完成,对于这些复杂的映射关系的处理可以用resultMap来解决表列名与JavaBean字段映射的问题。这也是它存在的原因。举例说明,下面这个复杂的SQL的映射问题:
<select id="selectBlogDetails" resultMap="detailedBlogResultMap">selectB.id as blog_id,B.title as blog_title,B.author_id as blog_author_id,A.id as author_id,A.username as author_username,A.password as author_password,A.email as author_email,A.bio as author_bio,A.favourite_section as author_favourite_section,P.id as post_id,P.blog_id as post_blog_id,P.author_id as post_author_id,P.created_on as post_created_on,P.section as post_section,P.subject as post_subject,P.draft as draft,P.body as post_body,C.id as comment_id,C.post_id as comment_post_id,C.name as comment_name,C.comment as comment_text,T.id as tag_id,T.name as tag_namefrom Blog Bleft outer join Author A on B.author_id = A.idleft outer join Post P on B.id = P.blog_idleft outer join Comment C on P.id = C.post_idleft outer join Post_Tag PT on PT.post_id = P.idleft outer join Tag T on PT.tag_id = T.idwhere B.id = #{id}</select>

我们这里每个表都对应一个类,那么就要将这些连接查询的结果集映射到的对象分别有Blog、Author、Post、Comment、PostTag、Tag。当然我们可以为这个结果集特别创建一个对象。这样就简单多了,但是不灵活,有硬编码的倾向。我们假设我们定义了一个更智能的对象Blog,这个对象Blog有Author(作者对象)还有许多Posts(帖子对象),每个帖子有0个或多个Comments(评论)和 Tags(标签)。那么将结果集映射到这个智能对象Blog的resultMap可以这样写
<!-- Very Complex Result Map --><resultMap id="detailedBlogResultMap" type="Blog"><constructor><idArg column="blog_id" javaType="int"/></constructor><result property="title" column="blog_title"/><association property="author" javaType="Author"><id property="id" column="author_id"/><result property="username" column="author_username"/><result property="password" column="author_password"/><result property="email" column="author_email"/><result property="bio" column="author_bio"/><result property="favouriteSection" column="author_favourite_section"/></association><collection property="posts" ofType="Post"><id property="id" column="post_id"/><result property="subject" column="post_subject"/><association property="author" javaType="Author"/><collection property="comments" ofType="Comment"><id property="id" column="comment_id"/></collection><collection property="tags" ofType="Tag" ><id property="id" column="tag_id"/></collection><discriminator javaType="int" column="draft"><case value="1" resultType="DraftPost"/></discriminator></collection></resultMap>

从上面这个例子,我们可以看到resultMap标签有很多子标签,下面我们一个一个来看。
(1)constructor:在类实例化时将结果注入到类的构造函数中,如将结果果集的内容注入到下面这个类的构造函数中:
public class User {//...public User(Integer id, String username, int age) {//...}//...}
使用constructor时,要保证结果集内容的顺序要和构造函数的参数顺序一致,如下面的映射,MyBatis会搜索定义了三个参数(java.lang.Integer ,java.lang.String ,int)且出现顺序一样的构造函数:
<constructor><idArg column="id" javaType="int"/><arg column="username" javaType="String"/><arg column="age" javaType="_int"/></constructor>
如果不想维护arg元素的顺序,可以为每一个arg元素指定一个名称,使其与构造函数名称对应上(对应的注解方式是使用@Param ):
<constructor><idArg column="id" javaType="int" name="id" /><arg column="age" javaType="_int" name="age" /><arg column="username" javaType="String" name="username" /></constructor>
constructor元素的子元素:
| 子元素 | 描述 |
| idArg | ID参数, 将结果标记为ID将有助于提高总体性能 |
| arg | 注入构造函数的结果列 |

(3)association:复杂类型的组合,它是处理 "has-one" 这种关系的,例如Blog类中有个Author类型时的属性。MyBatis有以下两种方式来实现association加载:
◆ 嵌套Select:通过执行另一个SQL语句来返回这种嵌套的复杂类型,但这种式对大数量的查询的性能不是很好。
<resultMap id="blogResult" type="Blog"><association property="author" column="author_id" javaType="Author" select="selectAuthor"/></resultMap><!--加载Author--><select id="selectAuthor" resultType="Author">SELECT * FROM AUTHOR WHERE ID = #{id}</select><!--加载Blog--><select id="selectBlog" resultMap="blogResult">SELECT * FROM BLOG WHERE ID = #{id}</select>
◆ 嵌套result:
先介绍association的一些属性:
| 属性 | 描述 |
| resultMap | 这是一个外部resultMap的ID |
| columnPrefix | 当连接多张表时,指定列的别名来区分resultSet中重复的列名称 |
| notNullColumn | 一般情况下,至少有一个映射到子对象的属性的列不为空,子对象才会被创建,通过此属性可以改变这一行为,指定一个列不会空,这样子即使所有列都是空的,对象也会被创建。 |
| autoMapping | 当将结果映射到这个属性,MyBatis将启用或禁止自动映射。它对外部resultMap无效,所以与select或resultMap一起使用毫无意义 |
下面是嵌套result的例子:
<resultMap id="blogResult" type="Blog"><id property="id" column="blog_id" /><result property="title" column="blog_title"/><association property="author" resultMap="authorResult" /></resultMap><resultMap id="authorResult" type="Author"><!--指定id可以提高性能,没有指定,MyBatis也能工作--><id property="id" column="author_id"/><result property="username" column="author_username"/><result property="password" column="author_password"/><result property="email" column="author_email"/><result property="bio" column="author_bio"/></resultMap><select id="selectBlog" resultMap="blogResult">selectB.id as blog_id,B.title as blog_title,B.author_id as blog_author_id,A.id as author_id,A.username as author_username,A.password as author_password,A.email as author_email,A.bio as author_biofrom Blog B left outer join Author A on B.author_id = A.idwhere B.id = #{id}</select>

上面我们把author独立写了个resultMap这样author部分就可以重用了,如果我们不想重用,可以将其写在一起,如:

<resultMap id="blogResult" type="Blog"><id property="id" column="blog_id" /><result property="title" column="blog_title"/><association property="author" javaType="Author"><id property="id" column="author_id"/><result property="username" column="author_username"/><result property="password" column="author_password"/><result property="email" column="author_email"/><result property="bio" column="author_bio"/></association></resultMap>

如果Blog中有两个字段author、coauthor,它们都是Author对象,resultMap又该如何写呢?select语句如下:
<select id="selectBlog" resultMap="blogResult">selectB.id as blog_id,B.title as blog_title,A.id as author_id,A.username as author_username,A.password as author_password,A.email as author_email,A.bio as author_bio,CA.id as co_author_id,CA.username as co_author_username,CA.password as co_author_password,CA.email as co_author_email,CA.bio as co_author_biofrom Blog Bleft outer join Author A on B.author_id = A.idleft outer join Author CA on B.co_author_id = CA.idwhere B.id = #{id}</select>
<resultMap id="blogResult" type="Blog"><id property="id" column="blog_id" /><result property="title" column="blog_title"/><association property="author" resultMap="authorResult" /><association property="coAuthor" resultMap="authorResult" columnPrefix="co_" /></resultMap>

association处理多结果集(调用存储过程):从MyBatis 3.2.3开始支持。有一些数据库允许调存储过程来执行一或多条语句,并返回一或多个结果集。这种可以不使用join连接查询。假设我们调用数据库里存储过程来执行以下查询并返回两个结果集(blogs,authors):
SELECT * FROM BLOG WHERE ID = #{id}SELECT * FROM AUTHOR WHERE ID = #{id}
在xml映射文件中,使用resultSets属性指定结果集名称,多个结果集名称之间用逗号分隔:
<select id="selectBlog" resultSets="blogs,authors" resultMap="blogResult">{call getBlogsAndAuthors(#{id,jdbcType=INTEGER,mode=IN})}</select>
现在我们可以把authors结果集数据填充到 "author" association中:
<resultMap id="blogResult" type="Blog"><id property="id" column="id" /><result property="title" column="title"/><association property="author" javaType="Author" resultSet="authors" column="author"><id property="id" column="id"/><result property="username" column="username"/><result property="password" column="password"/><result property="email" column="email"/><result property="bio" column="bio"/></association></resultMap>

(4)collection:复杂类型的集合。collection元素与association元素很像,但后者是解决“has-one",前者是解决”has-many“这种关系。举个例子:
Blog有许多Posts(帖子),在Blog类里,帖子被定义列表:private List<Post> posts;那么resultMap就应该这样写:<collection property="posts" ofType="domain.blog.Post"><id property="id" column="post_id"/><result property="subject" column="post_subject"/><result property="body" column="post_body"/></collection>
与association元素一样,collection元素也有两种方式来处理结果集:
<resultMap id="blogResult" type="Blog"><collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/></resultMap><select id="selectBlog" resultMap="blogResult">SELECT * FROM BLOG WHERE ID = #{id}</select><select id="selectPostsForBlog" resultType="Post">SELECT * FROM POST WHERE BLOG_ID = #{id}</select>
首先,来看看根据blog id拿帖子的SQL:
<select id="selectBlog" resultMap="blogResult">selectB.id as blog_id,B.title as blog_title,B.author_id as blog_author_id,P.id as post_id,P.subject as post_subject,P.body as post_body,from Blog Bleft outer join Post P on B.id = P.blog_idwhere B.id = #{id}</select>
<resultMap id="blogResult" type="Blog"><id property="id" column="blog_id" /><result property="title" column="blog_title"/><collection property="posts" ofType="Post"><!--注意此id是可以提高性能的--><id property="id" column="post_id"/><result property="subject" column="post_subject"/><result property="body" column="post_body"/></collection></resultMap>
<resultMap id="blogResult" type="Blog"><id property="id" column="blog_id" /><result property="title" column="blog_title"/><collection property="posts" ofType="Post" resultMap="blogPostResult" columnPrefix="post_"/></resultMap><resultMap id="blogPostResult" type="Post"><id property="id" column="id"/><result property="subject" column="subject"/><result property="body" column="body"/></resultMap>
collection处理多个resultSet的情况(调用存储过程):
和前面association一样,我们可以调用一个存储过程来执行两个查询,并返回两个结果集,一个是Blogs的,别一个是Posts的:
SELECT * FROM BLOG WHERE ID = #{id}SELECT * FROM POST WHERE BLOG_ID = #{id}
在xml映射文件中,使用resultSets指定每个结果集的名字,多个结果集名称之间用逗号隔开:
<select id="selectBlog" resultSets="blogs,posts" resultMap="blogResult">{call getBlogsAndPosts(#{id,jdbcType=INTEGER,mode=IN})}</select>
将posts集合填充到对象:
<resultMap id="blogResult" type="Blog"><id property="id" column="id" /><result property="title" column="title"/><collection property="posts" ofType="Post" resultSet="posts" column="id" foreignColumn="id"><id property="id" column="id"/><result property="subject" column="subject"/><result property="body" column="body"/></collection></resultMap>
collection的相关属性:
| 属性 | 描述 |
| column | 当使用多个结果集,这个属性用来指定列(多个列之间用逗号隔开),指定的列必须与foreignColumn列相关,以指明父子关系 |
| foreignColumn | 标识包含外键的列的名称,它将与column的指定的值相匹配 |
| resultSet | 标识结果集的名称,这个结果集将会被加载。 |



(5)discriminator
<discriminator javaType="int" column="draft"><case value="1" resultType="DraftPost"/></discriminator>
有时候一个数据库查询可能会返回许多不同的数据类型的结果集。discriminator元素就是设计用来处理这种情况的。discriminator有点java中的switch语句。discriminator的定义指定column和javaType属性,column就是MyBatis将要对比的值所在的地方。javaType是确保对比的类型正确。
<resultMap id="vehicleResult" type="Vehicle"><id property="id" column="id" /><result property="vin" column="vin"/><result property="year" column="year"/><result property="make" column="make"/><result property="model" column="model"/><result property="color" column="color"/><discriminator javaType="int" column="vehicle_type"><case value="1" resultMap="carResult"/><case value="2" resultMap="truckResult"/><case value="3" resultMap="vanResult"/><case value="4" resultMap="suvResult"/></discriminator></resultMap>
与下面是等价的:
<resultMap id="vehicleResult" type="Vehicle"><id property="id" column="id" /><result property="vin" column="vin"/><result property="year" column="year"/><result property="make" column="make"/><result property="model" column="model"/><result property="color" column="color"/><discriminator javaType="int" column="vehicle_type"><case value="1" resultType="carResult"><result property="doorCount" column="door_count" /></case><case value="2" resultType="truckResult"><result property="boxSize" column="box_size" /><result property="extendedCab" column="extended_cab" /></case><case value="3" resultType="vanResult"><result property="powerSlidingDoor" column="power_sliding_door" /></case><case value="4" resultType="suvResult"><result property="allWheelDrive" column="all_wheel_drive" /></case></discriminator></resultMap>
在上面这个例子里,MyBatis会收到结果集中每条记录,然后对比它的vehicle_ type的值,如果它匹配上了discriminator cases,那么就用指定的resultMap。如果没有一个匹配上,那么MyBatis将使用定义在discriminator块外的resultMap。carResult定义如下,如果vehicle_type是1,那么它就被使用:
<resultMap id="carResult" type="Car"><result property="doorCount" column="door_count" /></resultMap>
我们知道car也是vehicle,所以我们想使用carResult的同时也把vehicleResult的字段加载进来,这其实就是car继承vehicle,那么resultMap也是可以继承的,上面的carResult可以继承vehicleResult,这样也会把vehicleResult的字段加载进来:
<resultMap id="carResult" type="Car" extends="vehicleResult"><result property="doorCount" column="door_count" /></resultMap>


2.4.sql
这个元素是用于定义一些可重用的SQL片段,这些片段可以被包含在其他的语句中。如定义以下SQL片段:
<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </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属性里,或者include子句内的属性值,如:
<sql id="sometable">${prefix}Table</sql><sql id="someinclude">from<include refid="${include_target}"/></sql><select id="select" resultType="map">selectfield1, field2, field3<include refid="someinclude"><property name="prefix" value="Some"/><property name="include_target" value="sometable"/></include></select>
2.5.insert、update、delete
<insertid="insertAuthor"parameterType="domain.blog.Author"flushCache="true"statementType="PREPARED"keyProperty=""keyColumn=""useGeneratedKeys=""timeout="20"><updateid="updateAuthor"parameterType="domain.blog.Author"flushCache="true"statementType="PREPARED"timeout="20"><deleteid="deleteAuthor"parameterType="domain.blog.Author"flushCache="true"statementType="PREPARED"timeout="20">
insert、update、delete的属性:
| 属性 | 默认值 | 值 | 备注 |
| id | 命令空间中的唯一标识,这里用的是方法名 | ||
| parameterType | 全限定类名或别名 | 传给语句的参数类型,是可选的,因为MyBatis可以根据实参计算出TypeHandler | |
| flushCache | true | true,false | 当此值为true时,将会刷新二级缓存和本地缓存,无论语句什么时候调用,update、insert、delete的此属性默认都为true。 |
| timeout | 驱动等待数据从数据库读取回来的最长时间 | ||
| statementType | PREPARED | STATEMENT , PREPARED,CALLABLE | 指定MyBatis使用的语句类型 |
| useGeneratedKeys | false | true,false | 只用在insert、update上,这是告诉MyBatis使用JDBC的getGeneratedKeys方法来获取数据库内部产生的key(如MySQL的自增字段值) |
| keyProperty | 无 | 属性名称的逗号分隔列表,如果需要多个生成的列 | 只用在insert、update上,MyBatis将从getGeneratedKeys,或由insert语句的selectKey子项元素返回的值设置到此属性上。 |
| keyColumn | 只用在insert、update上,用生成的key设置表中的列名,这只在特定的数据库有用,如PostgreSQL | ||
| databaseId | 当应用连接多个数据库时有用,指定它,那么相应的update、insert、delete语句就会在相应的数据库上执行。 |
例子
<insert id="insertAuthor">insert into Author (id,username,password,email,bio)values (#{id},#{username},#{password},#{email},#{bio})</insert><update id="updateAuthor">update Author setusername = #{username},password = #{password},email = #{email},bio = #{bio}where id = #{id}</update><delete id="deleteAuthor">delete from Author where id = #{id}</delete>

insert语句自动生成id列:
<insert id="insertAuthor" useGeneratedKeys="true"keyProperty="id">insert into Author (username,password,email,bio)values (#{username},#{password},#{email},#{bio})</insert>

如果数据库支持同时多行插入,那么我们可以给对象传个list或数组:
<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驱动自动生成keys,那么可以使用以下selectKey方式来生成key,下面这个列子是随机生成一个ID:
<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=</insert>

selectKey子元素:

<selectKeykeyProperty="id"resultType="int"order="BEFORE"statementType="PREPARED">
| 属性 | 默认值 | 值 | 备注 |
| keyProperty | 目标属性 | selectKey语句的结果将要被设置到的属性,如果有多个可以用逗号隔开。 | |
| keyColumn | 在与属性匹配的返回的结果集中的列名 | ||
| resultType | 结果类型 | ||
| order | BEFORE、AFTER | 如果是BEFORE,那么它将先选择key,设置keyProperty并执行insert语句。如果是AFTER,它将运行insert语句,然后执行selectKey语句,这在Oracle数据库很常见,因为它在insert语句中内嵌了序列调用。 | |
| statementType | PREPARED | STATEMENT , PREPARED,CALLABLE | 指定MyBatis使用Statement 或PreparedStatement 或CallableStatement。 |



2.6.select
<selectid="selectPerson"parameterType="int"parameterMap="deprecated"resultType="hashmap"resultMap="personResultMap"flushCache="false"useCache="true"timeout="10"fetchSize="256"statementType="PREPARED"resultSetType="FORWARD_ONLY">
select元素的属性:
| 属性 | 默认值 | 值 | 备注 |
| id | 无 | 命名空间中的方法 | 唯一标识(必选) |
| parameterType | 无 | 全限定类名或别名 | 将会传入语句的参数类型(可选)。因为MyBatis可以从实际参数中计算要使用的TypeHandler |
| resultType | 无 | 全限定类名或别名 | 期望返回的类型,是集合中元素的类型,而不是集合类型,不可与resultMap同时使用。 |
| resultMap | 无 | 对一个外部resultMap的命名引用 | 不可与resultType同时使用。 |
| flushCache | false | true,false | 设置为true时,将引起本地和二级缓存刷新,不管语句是否被调用。 |
| useCache | true | true,false | 设置为true时,将引起语句的结果被缓存到二级缓存中。 |
| timeout | 无 | 正整数,单位秒 | 驱动等待数据从数据库返回的超时时间。 |
| fetchSize | 批量返回的数据量 | ||
| statementType | PREPARED | STATEMENT , PREPARED,CALLABLE | 指定MyBatis使用Statement 或PreparedStatement 或CallableStatement。 |
| resultSetType | 无 | FORWARD_ONLY,SCROLL_SENSITIVE,DEFAULT(即无) | |
| databaseId | 如果有已配置的databaseIdProvider,MyBatis将加载所有没有databaseId属性或具有与现在的那个的语句。 | ||
| resultOrdered | false | true,false | 用于嵌套结果集select语句。 |
| resultSets | 只有用多结果集,它列举了语句将要返回的结果集并给每个结果集一个名字,用逗号隔开。 |
select举列:
<select id="selectPerson" parameterType="int" resultType="hashmap">SELECT * FROM PERSON WHERE ID = #{id}</select>


2.7.参数类型
一般来说,我们可以通过parameterType指定参数类型,如果指定的类型与数据库中的类型不一样的话,MyBatis提供一种方式给我们指定参数类型,格式:
#{property,javaType=int,jdbcType=NUMERIC}
javaType通常由参数对象决定,除非对象是一个HashMap。指定javaType可以确保使用正确的TypeHandler。jdbcType是 JDBC需要的,用于所有可以为空的列,如果null作为一个值传递的话,就应该指定jdbcType。
我们还可以指定TypeHandler类或别名:
#{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}
对于数字类型,有一个numericScale可以决定精确到小数点后几位:
#{height,javaType=double,jdbcType=NUMERIC,numericScale=2}
mode模式属性可以是IN 、OUT、INOUT,如果是OUT或INOUT,那么参数对象属性的真实值会被改变。如果模式是OUT或INOUT ,并且jdbcType=CURSOR
,那么你必须指定一个resultMap来映射ResultSet到参数类型,此时javaType属性是可选的:
#{department, mode=OUT, jdbcType=CURSOR, javaType=ResultSet, resultMap=departmentRes}
MyBatis也支持高级数据类型,如结构体structs,但你必须告知语句类型名字:
#{middleInitial, mode=OUT, jdbcType=STRUCT, jdbcTypeName=MY_TYPE, resultMap=departmentRes}
大多数时候,我们都只简单地指定属性名,然后MyBatis就会帮我们搞掂剩下的这些。因此,我们只需要为可空的列指定jdbcType就可以了,如:
#{firstName}#{middleInitial,jdbcType=VARCHAR}#{lastName}

2.8.置换字符串
默认情况下,使用#{}语法,会让MyBatis生成PreparedStatement属性,并将值安全地设置到PreparedStatement的参数里。这种方式既安全又快速。有时,我们想直接在SQL语句注入未修改的字符串,当SQL语句中的元数据(如表名,列名)是动态变化的,字符串置换就显得很有用了。例如,你要从一张表中通过任意一个列来选择:
@Select("select * from user where ${column} = #{value}")User findByColumn(@Param("column") String column, @Param("value") String value);
接受从用户输入或提供SQL语句中未修改的元数据名称是不安全的。这存在潜在的SQL注入攻击。因此如果使用这种方式,我们要做到不允许用户输入这些字段,我们自己应该做好转义和检查,以此来提高安全性。

3.别名
使用别名,你就不需要写全限定路径了。
3.1
第一步:在application.yml中指定MyBatis的配置文件
在springboot的application.yml加入以下配置:
mybatis:# 指定MyBatis的配置文件位置config-location: classpath:config/mybatis-mapper-config.xml# 指定映射器的xml文件的位置mapper-locations: classpath:mybatis/mapper/*.xml
3.2
第二步:根据上一步创建文件
~/Desktop/MyBatisXMLDemo$ touch src/main/resources/config/mybatis-mapper-config.xml~/Desktop/MyBatisXMLDemo$ touch src/main/resources/mybatis/mapper/PersonMapper.xml
3.3
第三步:在mybatis-mapper-config.xml定义别名
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration><typeAliases><typeAlias type="com.wong.mybatis.bean.Person" alias="_Person"/></typeAliases></configuration>
3.4
第四步:在映射文件PersonMapper.xml使用别名
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.wong.mybatis.mapper.PersonMapper"><select id="selectByPrimaryKey" resultType="_Person">select * from person where id = #{id}</select><select id="selectAllPerson" resultType="_Person">select * from person</select></mapper>

还可以把数据库连接的信息从application.yml文件中移到MyBatis的配置文件mybatis-mapper-config.xml中:
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration><!-- 引用db.properties配置文件 --><properties resource="db.properties"/><!--development : 开发模式work : 工作模式--><environments default="development"><environment id="development"><transactionManager type="JDBC" /><!-- 配置数据库连接信息 --><dataSource type="POOLED"><!-- value属性值引用db.properties配置文件中配置的值 --><property name="driver" value="${driver}" /><property name="url" value="${url}" /><property name="username" value="${name}" /><property name="password" value="${password}" /></dataSource></environment></environments></configuration>

4.动态SQL

MyBatis可以实现动态SQL。
● if
● choose (when, otherwise)
● trim (where, set)
● foreach

4.1.if
<select id="findActiveBlogWithTitleLike" resultType="Blog">SELECT * FROM BLOGWHERE state = ‘ACTIVE’<if test="title != null">AND title like #{title}</if></select>
再如:
<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>
4.2.choose (when, otherwise)
<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>
4.3.trim (where, set)
<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子句,当where元素中的if元素有符合的即需要插入where子句的),where元素就会在SQL语句后面添加where子句。如果where子句是以 "AND" 或 "OR"开头,它会把它去掉,再添加到where后面。如果where元素都还满足不了你的需求,Mybatis提供trim元素让你来自定义,如下面的trim等价于where元素:
<select id="findActiveBlogLike" resultType="Blog">SELECT * FROM BLOG<trim prefix="WHERE" prefixOverrides="AND |OR "><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></trim></select>
prefix是要插入的,prefixOverrides是要移除的。prefixOverrides属性接受要重写的以管道分隔的文本列表,注意空格的地方,它是必要的。简单点来说,trim的结果就是prefix属性的值加上trim元素里的值,如果trim里没有值,则不在SQL里插入,如果trim里的值是以prefixOverrides里的值开头的则先移除再和prefix属性值一起插入到SQL语句里。
有一种相似的动态update语句的解决方案叫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>
上面也可以用trim来自定义,如:
<update id="updateAuthorIfNecessary">update Author<!--要插入set子句,如果后面的值是以“,”逗号开头,则要去掉先--><trim prefix="SET" suffixOverrides=","><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></trim>where id=#{id}</update>
4.4.foreach
<select id="selectPostIn" resultType="domain.blog.Post">SELECT *FROM POST PWHERE ID in<foreach item="item" index="index" collection="list" open="(" separator="," close=")">#{item}</foreach></select>
foreach元素允许我们指定一个集合collection,声明item和index变量,这两个变量可以在foreach元素内部使用。foreach也允许我们指定开始和结束的字符串,和添加在迭代之间的分隔符 。foreach元素不会意外地附加额外的分隔符,这一点可以放心。
我们可以传递任何迭代对象,如List、Set等,同样也可传递Map或Array对象到foreach作为集合参数。当使用Iterable 或 Array,index表示当前迭代的下标,item表示当前的值。当使用Map,如 Map.Entry 对象的集合,index就是key对象,item就是值对象。

4.5.script
在映射器类里使用注解的方式使用动态SQL,需要使用script 元素:
@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>"})void updateAuthorValues(Author author);

4.6.bind
bind元素可以让你在OGNL表达式之外,创建一个变量,并将其绑定到上下文中:
<select id="selectBlogsLike" resultType="Blog"><bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />SELECT * FROM BLOGWHERE title LIKE #{pattern}</select>

5. 多数据库供应商支持
上面大概把MyBatis使用xml映射文件说了一遍。









