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
默认情况下:
只启用本地会话缓存,只在会话期间缓存数据。有如下特点:
- 所有查询出来的结果集都会被缓存起来;
- 所有在映射声明文件中的insert、update、delete语句都会被放到缓存里;
- 使用最近最少使用的(LRU)逐出算法 ;
- 缓存不会按任何基于时间的计划刷新
- 缓存可以存储1024个列表或对象的个引用;
- 缓存可读可写,意味着缓存的对象是不共享的,因此能够被调用者安全地修改,因为不存在其他调用者或线程潜在的修改。
缓存配置只会对cache标记所在的映射文件中的语句有起作用。如果不想用默认的配置,可以修改以上缓存的属性,如:
<cache
eviction="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"/>
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>
除了上面这种给列名取个与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_password
from some_table
where id = #{id}
</select>
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>
我们这里每个表都对应一个类,那么就要将这些连接查询的结果集映射到的对象分别有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>
从上面这个例子,我们可以看到resultMap标签有很多子标签,下面我们一个一个来看。
2.3.2.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
| 注入构造函数的结果列
|
(2)id & result:
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
这两个是结果映射中最基本的元素。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>
- 嵌套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>
上面我们把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">
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>
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>
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>
- 嵌套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>
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>
也可以将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>
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语句中包含此片段:
<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">
select
field1, field2, field3
<include refid="someinclude">
<property name="prefix" value="Some"/>
<property name="include_target" value="sometable"/>
</include>
</select>
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">
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>
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子元素:
<selectKey
keyProperty="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
<select
id="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
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 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>
还可以把数据库连接的信息从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 BLOG
WHERE 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 P
WHERE 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 BLOG
WHERE title LIKE #{pattern}
</select>
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>
上面大概把MyBatis使用xml映射文件说了一遍。