MyBatis使用XML映射文件
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,声明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);
- 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
- 点赞
- 收藏
- 关注作者
评论(0)