02.XML 映射
XML 映射文件
public interface UserMapper
{
public void insertUser(User user);
public User getUserById(Integer userId);
public List<User> getAllUsers();
public void updateUser(User user);
public void deleteUser(Integer userId);
}
其关联的
<?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='wx.mappers.UserMapper'>
<select id='getUserById' parameterType='int' resultType='wx.domain.User'>
SELECT
user_id as userId,
email_id as emailId ,
password,
first_name as firstName,
last_name as lastName
FROM USER
WHERE USER_ID = #{userId}
</select>
<!-- Instead of referencing Fully Qualified Class Names we can register Aliases in mybatis-config.xml and use Alias names. -->
<resultMap type='User' id='UserResult'>
<id property='userId' column='user_id'/>
<result property='emailId' column='email_id'/>
<result property='password' column='password'/>
<result property='firstName' column='first_name'/>
<result property='lastName' column='last_name'/>
</resultMap>
<select id='getAllUsers' resultMap='UserResult'>
SELECT * FROM USER
</select>
<insert id='insertUser' parameterType='User' useGeneratedKeys='true' keyProperty='userId'>
INSERT INTO USER(email_id, password, first_name, last_name)
VALUES(#{emailId}, #{password}, #{firstName}, #{lastName})
</insert>
<update id='updateUser' parameterType='User'>
UPDATE USER
SET
PASSWORD= #{password},
FIRST_NAME = #{firstName},
LAST_NAME = #{lastName}
WHERE USER_ID = #{userId}
</update>
<delete id='deleteUser' parameterType='int'>
DELETE FROM USER WHERE USER_ID = #{userId}
</delete>
</mapper>
在
public interface BlogMapper
{
@Insert('INSERT INTO BLOG(BLOG_NAME, CREATED_ON) VALUES(#{blogName}, #{createdOn})')
@Options(useGeneratedKeys=true, keyProperty='blogId')
public void insertBlog(Blog blog);
@Select('SELECT BLOG_ID AS blogId, BLOG_NAME as blogName, CREATED_ON as createdOn FROM BLOG WHERE BLOG_ID=#{blogId}')
public Blog getBlogById(Integer blogId);
@Select('SELECT * FROM BLOG ')
@Results({
@Result(id=true, property='blogId', column='BLOG_ID'),
@Result(property='blogName', column='BLOG_NAME'),
@Result(property='createdOn', column='CREATED_ON')
})
public List<Blog> getAllBlogs();
@Update('UPDATE BLOG SET BLOG_NAME=#{blogName}, CREATED_ON=#{createdOn} WHERE BLOG_ID=#{blogId}')
public void updateBlog(Blog blog);
@Delete('DELETE FROM BLOG WHERE BLOG_ID=#{blogId}')
public void deleteBlog(Integer blogId);
}
select
查询是最常见的数据操作之一,如下查询语句接受一个
<select id="selectPerson" parameterType="int" resultType="hashmap">
SELECT * FROM PERSON WHERE ID = #{id}
</select>
其类似于创建一个新的预处理语句,并以
<select
id="selectPerson"
parameterType="int"
parameterMap="deprecated"
resultType="hashmap"
resultMap="personResultMap"
<!-- 将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:false。-->
flushCache="false"
useCache="true"
timeout="10"
fetchSize="256"
statementType="PREPARED"
resultSetType="FORWARD_ONLY"></select>
数据操作
数据变更语句
<insert
id="insertAuthor"
parameterType="domain.blog.Author"
flushCache="true"
statementType="PREPARED"
<!-- 通过生成的键值设置表中的列名,这个设置仅在某些数据库(像 PostgreSQL)是必须的,当主键列不是表中的第一列的时候需要设置。如果希望使用多个生成的列,也可以设置为逗号分隔的属性名称列表。-->
keyProperty=""
keyColumn=""
<!-- 令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系数据库管理系统的自动递增字段)-->
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 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="insertAuthor" useGeneratedKeys="true"
keyProperty="id">
insert into Author (username,password,email,bio)
values (#{username},#{password},#{email},#{bio})
</insert>
对于不支持自动生成类型的数据库或可能不支持自动生成主键的
<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=VARCHAR})
</insert>
在上面的示例中,
<selectKey
keyProperty="id"
resultType="int"
<!-- 这可以被设置为 BEFORE或AFTER。如果设置为 BEFORE,那么它会首先生成主键,设置 keyProperty 然后执行插入语句。如果设置为 AFTER,那么先执行插入语句,然后是 selectKey 中的语句 - 这和 Oracle 数据库的行为相似,在插入语句内部可能有嵌入索引调用。-->
order="BEFORE"
statementType="PREPARED">
复杂参数
<insert id="insertUser" parameterType="User">
insert into users (id, username, password)
values (#{id}, #{username}, #{password})
</insert>
如果我们希望在@Param
注解:
@Mapper
public interface MyMapper {
void update(@Param("a") A a, @Param("b") B b);
...
}
<update id="update" >
UPDATE SOME WHERE x=#{a.x} AND y=#{b.y}
</update>
或者将
void mapCategoryAndPage(@Param("categoryLocalId") Long categoryLocalId, @Param("pageLocalId") Long localId);
<insert id="mapCategoryAndPage" parameterType="map">
INSERT INTO
category_page_mapping (
page_local_id,
category_local_id)
VALUES
(#{pageLocalId},
#{categoryLocalId});
</insert>
我们还可以为参数指定一个特殊的数据类型:
#{property,javaType=int,jdbcType=NUMERIC}
#{height,javaType=double,jdbcType=NUMERIC,numericScale=2}
#{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}
字符串替换
默认情况下#{}
格式的语法会导致
ORDER BY ${columnName}
当
@Select("select * from user where id = #{id}")
User findById(@Param("id") long id);
@Select("select * from user where name = #{name}")
User findByName(@Param("name") String name);
// 替换为单个写法,其中 ${column} 会被直接替换,而 #{value} 会被使用 ? 预处理
@Select("select * from user where ${column} = #{value}")
User findByColumn(@Param("column") String column, @Param("value") String value);
User userOfId1 = userMapper.findByColumn("id", 1L);
User userOfNameKid = userMapper.findByColumn("name", "kid");
批量操作
利用
<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>
resultMap | 结果映射
<select id="selectUsers" resultType="map">
select id, username, hashedPassword
from some_table
where id = #{id}
</select>
上述语句只是简单地将所有的列映射到
public class User {
private int id;
private String username;
private String hashedPassword;
// ... Getter & Setter
}
// <select id="selectUsers" resultType="com.someapp.model.User">
// ...
映射规则
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
指定主键可以提高整体的性能,尤其是进行缓存和嵌套结果映射(也就是连接映射)的时候;在使用复合主键的时候,你可以使用 column="{prop1=col1,prop2=col2}"
这样的语法来指定多个传递给嵌套
这两个元素都可以指定
public class User {
//...
public User(Integer id, String username, int age) {
//...
}
//...
}
标准的
<constructor>
<idArg column="id" javaType="int"/>
<arg column="username" javaType="String"/>
<arg column="age" javaType="_int"/>
</constructor>
这种依赖于顺序的方式,简单易懂,却也有其弊端。当你在处理一个带有多个形参的构造方法时,很容易搞乱
<constructor>
<idArg column="id" javaType="int" name="id" />
<arg column="age" javaType="_int" name="age" />
<arg column="username" javaType="String" name="username" />
</constructor>
Alias | 别名
<resultMap id="userResultMap" type="User">
<id property="id" column="user_id" />
<result property="username" column="user_name"/>
<result property="password" column="hashed_password"/>
</resultMap>
而在引用它的语句中使用
<select id="selectUsers" resultMap="userResultMap">
select user_id, user_name, hashed_password
from some_table
where id = #{id}
</select>
类型别名则允许我们不用输入类的完全限定名称:
<!-- mybatis-config.xml 中 -->
<typeAlias type="com.someapp.model.User" alias="User"/>
<!-- SQL 映射 XML 中 -->
<select id="selectUsers" resultType="User">
自动映射
当自动映射查询结果时,
对于每一个结果映射,在
<select id="selectUsers" resultMap="userResultMap">
select
user_id as "id",
user_name as "userName",
hashed_password
from some_table
where id = #{id}
</select>
<resultMap id="userResultMap" type="User">
<result property="password" column="hashed_password"/>
</resultMap>
NONE: 禁用自动映射。仅对手动映射的属性进行映射。PARTIAL: 对除在内部定义了嵌套结果映射(也就是连接的属性)以外的属性进行映射FULL: 自动映射所有属性。
无论设置的自动映射等级是哪种,你都可以通过在结果映射上设置
<resultMap id="userResultMap" type="User" autoMapping="false">
<result property="password" column="hashed_password"/>
</resultMap>
Association | 关联查询
- 嵌套
Select 查询:通过执行另外一个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>
<!-- 非常复杂的结果映射 -->
<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>
关联的嵌套select 查询
<association property="author" column="blog_author_id" javaType="Author">
<id property="id" column="author_id"/>
<result property="username" column="author_username"/>
</association>
<resultMap id="blogResult" type="Blog">
<association property="author" column="author_id" javaType="Author" select="selectAuthor"/>
</resultMap>
<select id="selectBlog" resultMap="blogResult">
SELECT * FROM BLOG WHERE ID = #{id}
</select>
<select id="selectAuthor" resultType="Author">
SELECT * FROM AUTHOR WHERE ID = #{id}
</select>
两个N + 1
查询问题,即:
- 执行了一个单独的
SQL 语句来获取结果的一个列表(就是“+1”) 。 - 对列表返回的每条记录,你执行一个
select 查询语句来为每条记录加载详细信息(就是“N”) 。
N + 1
查询问题。
关联的嵌套结果映射
对于复杂的嵌套结果映射,同样可以指定子查询的结果映射:
<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>
这里为确保结果能够拥有唯一且清晰的名字,我们设置的别名,而在
<resultMap id="blogResult" type="Blog">
<id property="id" column="blog_id" />
<result property="title" column="blog_title"/>
<association property="author" column="blog_author_id" javaType="Author" resultMap="authorResult"/>
</resultMap>
<resultMap id="authorResult" type="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"/>
</resultMap>
如果
<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 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>
关联的多结果集
集合元素和关联元素几乎是一样的,不过它们能够用来描述多条数据,譬如某个
private List<Post> posts;
然后同样编写多条
<resultMap id="blogResult" type="Blog">
<collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/>
<!-- 或者省略 javaType -->
<!-- <collection property="posts" 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>
<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>
对于复杂嵌套属性的处理则同样类似于单结果集关联:
<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 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>