MyBatis使用XML映射文件

举报
yd_221104950 发表于 2020/12/03 01:39:22 2020/12/03
【摘要】 1.概述 这一次想把MyBatis的XML声明SQL的方式大概说一下。使用的demo可以参考: 《spring boot整合Mybatis3.5.4使用XML定义SQL》 MyBatis可以通过注解使用声明,也可以xml文件来声明SQL。前者简单,不灵活,后者不仅方便灵活,还方便优。通过XML来编写映射的SQL也是MyBatis所推荐的。MyBatis的一个映射器类...

1.概述

这一次想把MyBatis的XML声明SQL的方式大概说一下。使用的demo可以参考:
《spring boot整合Mybatis3.5.4使用XML定义SQL》

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/>

  
 
  • 1

只启用本地会话缓存,只在会话期间缓存数据。有如下特点:

  • 所有查询出来的结果集都会被缓存起来;
  • 所有在映射声明文件中的insert、update、delete语句都会被放到缓存里;
  • 使用最近最少使用的(LRU)逐出算法 ;
  • 缓存不会按任何基于时间的计划刷新
  • 缓存可以存储1024个列表或对象的个引用;
  • 缓存可读可写,意味着缓存的对象是不共享的,因此能够被调用者安全地修改,因为不存在其他调用者或线程潜在的修改。

缓存配置只会对cache标记所在的映射文件中的语句有起作用。如果不想用默认的配置,可以修改以上缓存的属性,如:

<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5

创建一个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"/>

  
 
  • 1

自定义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();
  }

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

实现Cache接口:

public class MyCustomCache implements Cache{
	// ...
}

  
 
  • 1
  • 2
  • 3

点到为止,更多内容请到网上查阅!

2.2.cache-ref

引用其他命名空间的缓存配置。

<cache-ref namespace="com.wong.mybatis.SomeMapper"/>

  
 
  • 1

2.3.resultMap

resultMap是MyBatis中最重要的元素,因为将结果集映射到对象中,基本都用resultMap来完成。使用resultMap可以让你省去90%的JDBC代码,resultMap其甚至可以实现一些JDBC做不到的事。MyBatis自动创建ResultMap,基于名称自动映射列到JavaBean的属性上, 如果列名和JavaBean的属性匹配不上,我们可以在列名上使用select子句别名(标准SQL特性)来创建标签匹配:

<select id="selectUsers" resultType="User">
	select
	user_id as "id",
	user_name as "userName",
	hashed_password as "hashedPassword"
	from some_table
	where id = #{id}
</select>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

除了上面这种给列名取个与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>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5

(2)第二步:引用resultMap

<select id="selectUsers" resultMap="userResultMap">
	select user_id, user_name, hashed_password
	from some_table
	where id = #{id}
</select>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5

resultMap不能和resultType同时使用,继续阅读你就明白的了。

2.3.1.resultMap的高级使用

数据库很难做到时时刻刻都能够和我们的JavaBean的属性匹配上。而且不是所有数据库都能很好的实现数据库设计第三范式或BCNF范式。这些问题都使处有时并不能简单地通过自动映射来完成,对于这些复杂的映射关系的处理可以用resultMap来解决表列名与JavaBean字段映射的问题。这也是它存在的原因。举例说明,下面这个复杂的SQL的映射问题:

<select id="selectBlogDetails" resultMap="detailedBlogResultMap">
	select
	B.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_name
	from Blog B
	left outer join Author A on B.author_id = A.id
	left outer join Post P on B.id = P.blog_id
	left outer join Comment C on P.id = C.post_id
	left outer join Post_Tag PT on PT.post_id = P.id
	left outer join Tag T on PT.tag_id = T.id
	where B.id = #{id}
</select>

  
 
  • 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
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

我们这里每个表都对应一个类,那么就要将这些连接查询的结果集映射到的对象分别有Blog、Author、Post、Comment、PostTag、Tag。当然我们可以为这个结果集特别创建一个对象。这样就简单多了,但是不灵活,有硬编码的倾向。我们假设我们定义了一个更智能的对象Blog,这个对象Blog有Author(作者对象)还有许多Posts(帖子对象),每个帖子有0个或多个Comments(评论)和 Tags(标签)。那么将结果集映射到这个智能对象Blog的resultMap可以这样写
(假设Blog,Author, Post, Comments,Tags都是类型别名):

<!-- 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>

  
 
  • 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
  • 26
  • 27
  • 28
  • 29

从上面这个例子,我们可以看到resultMap标签有很多子标签,下面我们一个一个来看。

2.3.2.resultMap的子标签

(1)constructor :在类实例化时将结果注入到类的构造函数中,如将结果果集的内容注入到下面这个类的构造函数中:

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

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

使用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>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5

如果不想维护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>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5

constructor元素的子元素:

子元素 描述
idArg ID参数, 将结果标记为ID将有助于提高总体性能
arg 注入构造函数的结果列

(2)id & result

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

  
 
  • 1
  • 2

这两个是结果映射中最基本的元素。id和result都是映射一个列值到一个简单类型(String, int, double, Date等)的属性或字段。 它们唯一的区别是id结果标识为一个标识符属性,在比较对象实例时使用,有助于提高总体性能。特别是提高缓存和嵌套result映射的性能,如SQL中带join语句的映射。

它们有以下属性:

属性 描述
property 列名将要映射到的字段或属性。
column 列名或列名的别名
javaType 全限定java类名,或类型别名,MyBatie通常都能够自己识别出类型,如果你遇到一个JavaBean的话。但是如果你映射到一个HashMap,那么你就应该显式指定javaType,确保得到期望的行为。
jdbcType JDBC类型只有在insert、update、delete上对可空列进行操作时需要指定。
typeHandler 全限定类名或别名 ,TypeHandler的实现类,类型处理器

JDBC支持的类型:

BIT FLOAT CHAR TIMESTAMP OTHER UNDEFINED
TINYINT REAL VARCHAR BINARY BLOB NVARCHAR
SMALLINT DOUBLE LONGVARCHAR VARBINARY CLOB NCHAR
INTEGER NUMERIC DATE LONGVARBINARY BOOLEAN NCLOB
BIGINT DECIMAL TIME NULL CURSOR ARRAY

(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>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 嵌套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">
	select
	B.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
	from Blog B left outer join Author A on B.author_id = A.id
	where B.id = #{id}
</select>

  
 
  • 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
  • 26
  • 27
  • 28

上面我们把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>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

如果Blog中有两个字段author、coauthor,它们都是Author对象,resultMap又该如何写呢?select语句如下:

<select id="selectBlog" resultMap="blogResult">
	select
	B.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_bio
	from Blog B
	left outer join Author A on B.author_id = A.id
	left outer join Author CA on B.co_author_id = CA.id
	where B.id = #{id}
</select>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

resultMap可以这样写:

<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>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

association处理多结果集(调用存储过程):从MyBatis 3.2.3开始支持。有一些数据库允许调存储过程来执行一或多条语句,并返回一或多个结果集。这种可以不使用join连接查询。假设我们调用数据库里存储过程来执行以下查询并返回两个结果集(blogs,authors):

SELECT * FROM BLOG WHERE ID = #{id}
SELECT * FROM AUTHOR WHERE ID = #{id}

  
 
  • 1
  • 2

在xml映射文件中,使用resultSets属性指定结果集名称,多个结果集名称之间用逗号分隔:

<select id="selectBlog" resultSets="blogs,authors" resultMap="blogResult">
	{call getBlogsAndAuthors(#{id,jdbcType=INTEGER,mode=IN})}
</select>

  
 
  • 1
  • 2
  • 3

现在我们可以把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>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

(4)collection:复杂类型的集合。collection元素与association元素很像,但后者是解决“has-one",前者是解决”has-many“这种关系。举个例子:
Blog有许多Posts(帖子),在Blog类里,帖子被定义列表:

private List<Post> posts;

  
 
  • 1

那么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>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5

与association元素一样,collection元素也有两种方式来处理结果集:

  • 嵌套select:
<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>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 嵌套result:
    首先,来看看根据blog id拿帖子的SQL:
<select id="selectBlog" resultMap="blogResult">
	select
	B.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 B
	left outer join Post P on B.id = P.blog_id
	where B.id = #{id}
</select>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

resultMap我们可以这样写:

<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>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

也可以将Post的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>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

collection处理多个resultSet的情况(调用存储过程):
和前面association一样,我们可以调用一个存储过程来执行两个查询,并返回两个结果集,一个是Blogs的,别一个是Posts的:

SELECT * FROM BLOG WHERE ID = #{id}
SELECT * FROM POST WHERE BLOG_ID = #{id}

  
 
  • 1
  • 2

在xml映射文件中,使用resultSets指定每个结果集的名字,多个结果集名称之间用逗号隔开:

<select id="selectBlog" resultSets="blogs,posts" resultMap="blogResult">
	{call getBlogsAndPosts(#{id,jdbcType=INTEGER,mode=IN})}
</select>

  
 
  • 1
  • 2
  • 3

将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>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

collection的相关属性:

属性 描述
column 当使用多个结果集,这个属性用来指定列(多个列之间用逗号隔开),指定的列必须与foreignColumn列相关,以指明父子关系
foreignColumn 标识包含外键的列的名称,它将与column的指定的值相匹配
resultSet 标识结果集的名称,这个结果集将会被加载。

(5)discriminator

<discriminator javaType="int" column="draft">
	<case value="1" resultType="DraftPost"/>
</discriminator>

  
 
  • 1
  • 2
  • 3

有时候一个数据库查询可能会返回许多不同的数据类型的结果集。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>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

与下面是等价的:

<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>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

在上面这个例子里,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>

  
 
  • 1
  • 2
  • 3

我们知道car也是vehicle,所以我们想使用carResult的同时也把vehicleResult的字段加载进来,这其实就是car继承vehicle,那么resultMap也是可以继承的,上面的carResult可以继承vehicleResult,这样也会把vehicleResult的字段加载进来:

<resultMap id="carResult" type="Car" extends="vehicleResult">
	<result property="doorCount" column="door_count" />
</resultMap>

  
 
  • 1
  • 2
  • 3

2.4.sql

这个元素是用于定义一些可重用的SQL片段,这些片段可以被包含在其他的语句中。如定义以下SQL片段:

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

  
 
  • 1

在select语句中包含此片段:

<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>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

属性值也可以用于include refid属性里,或者include子句内的属性值,如:

<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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

2.5.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">

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

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 set
	username = #{username},
	password = #{password},
	email = #{email},
	bio = #{bio}
	where id = #{id}
</update>

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

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

insert语句自动生成id列:

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

  
 
  • 1
  • 2
  • 3
  • 4
  • 5

如果数据库支持同时多行插入,那么我们可以给对象传个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>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

如果数据库不支持自动生成列类型或者还不支持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>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

selectKey子元素:

<selectKey
keyProperty="id"
resultType="int"
order="BEFORE"
statementType="PREPARED">

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
属性 默认值 备注
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

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

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

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>

  
 
  • 1
  • 2
  • 3

2.7.参数类型

一般来说,我们可以通过parameterType指定参数类型,如果指定的类型与数据库中的类型不一样的话,MyBatis提供一种方式给我们指定参数类型,格式:

#{property,javaType=int,jdbcType=NUMERIC}

  
 
  • 1

javaType通常由参数对象决定,除非对象是一个HashMap。指定javaType可以确保使用正确的TypeHandler。jdbcType是 JDBC需要的,用于所有可以为空的列,如果null作为一个值传递的话,就应该指定jdbcType。

我们还可以指定TypeHandler类或别名:

#{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}

  
 
  • 1

对于数字类型,有一个numericScale可以决定精确到小数点后几位:

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

  
 
  • 1

mode模式属性可以是IN 、OUT、INOUT,如果是OUT或INOUT,那么参数对象属性的真实值会被改变。如果模式是OUT或INOUT ,并且jdbcType=CURSOR
,那么你必须指定一个resultMap来映射ResultSet到参数类型,此时javaType属性是可选的:

#{department, mode=OUT, jdbcType=CURSOR, javaType=ResultSet, resultMap=departmentRes}

  
 
  • 1

MyBatis也支持高级数据类型,如结构体structs,但你必须告知语句类型名字:

#{middleInitial, mode=OUT, jdbcType=STRUCT, jdbcTypeName=MY_TYPE, resultMap=departmentRes}

  
 
  • 1

大多数时候,我们都只简单地指定属性名,然后MyBatis就会帮我们搞掂剩下的这些。因此,我们只需要为可空的列指定jdbcType就可以了,如:

#{firstName}
#{middleInitial,jdbcType=VARCHAR}
#{lastName}

  
 
  • 1
  • 2
  • 3

2.8.置换字符串

默认情况下,使用#{}语法,会让MyBatis生成PreparedStatement属性,并将值安全地设置到PreparedStatement的参数里。这种方式既安全又快速。有时,我们想直接在SQL语句注入未修改的字符串,当SQL语句中的元数据(如表名,列名)是动态变化的,字符串置换就显得很有用了。例如,你要从一张表中通过任意一个列来选择:

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

  
 
  • 1
  • 2

接受从用户输入或提供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

  
 
  • 1
  • 2
  • 3
  • 4
  • 5

3.2.第二步:根据上一步创建文件

~/Desktop/MyBatisXMLDemo$ touch src/main/resources/config/mybatis-mapper-config.xml
~/Desktop/MyBatisXMLDemo$ touch src/main/resources/mybatis/mapper/PersonMapper.xml

  
 
  • 1
  • 2

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>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

3.4.第四步:在映射文件PersonMapper.xml使用别名

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//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>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

还可以把数据库连接的信息从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>

  
 
  • 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

4.动态SQL

MyBatis可以实现动态SQL。

  • if
  • choose (when, otherwise)
  • trim (where, set)
  • foreach

4.1.if

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

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

再如:

<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>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

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>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

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>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

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>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

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>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

上面也可以用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>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

4.4.foreach

<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>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

foreach元素允许我们指定一个集合collection,声明itemindex变量,这两个变量可以在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);

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

4.6.bind

bind元素可以让你在OGNL表达式之外,创建一个变量,并将其绑定到上下文中:

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

  
 
  • 1
  • 2
  • 3
  • 4
  • 5

5. 多数据库供应商支持

说白了就是连接多个数据库。如果有一个databaseIdProvider配置了“_databaseId"变量并且对于动态代码来说是可用的。那么,我们可以根据不同的数据库提供商来建不同的语句,如:

<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>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

上面大概把MyBatis使用xml映射文件说了一遍。

文章来源: blog.csdn.net,作者:WongKyunban,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/weixin_40763897/article/details/106079314

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。