MyBatis 映射文件是所有 SQL 语句放置的地方,写好 SQL 语句映射文件后,需要在配置文件的 mappers 标签中引用。映射文件和与它具有相同功能的 JDBC 代码相比省掉了大部分的代码,而且对 SQL 的构建比普通方法还要好,这就是 MyBatis 的强大之处。
先来看一下入门程序中我们映射文件 UserMapper.xml 中的内容:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org/DTD Mapper 3.0" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.gler.mybatis.mapper.UserMapper">
<!-- 自定义返回结果集 -->
<resultMap id="userMap" type="User">
<id property="id" column="id" javaType="int"></id>
<result property="username" column="username" javaType="String"></result>
<result property="password" column="password" javaType="String"></result>
<result property="sex" column="sex" javaType="String"></result>
<result property="address" column="address" javaType="String"></result>
</resultMap>
<!-- 定义 SQL 语句,其中 id 需要和接口中的方法名一致 -->
<!-- useGeneratedKeys:实现自动生成主键 -->
<!-- keyProperty: 唯一标记一个属性 -->
<!-- parameterType 指明查询时使用的参数类型,resultType 指明查询返回的结果集类型 -->
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
insert into user (username,password,sex,address) values
(#{username},#{password},#{sex},#{address})
</insert>
<update id="updateUser" parameterType="User">
update user set
address=#{address} where
id=#{id}
</update>
<delete id="deleteUser" parameterType="int">
delete from user where
id=#{id}
</delete>
<!-- 如未为 Java Bean 起类别名,resultType="com.gler.mybatis.model.User" -->
<!-- 使用resultType时,一定要保证,你属性名与字段名相同;如果不相同,就使用resultMap -->
<select id="selectUserById" parameterType="int" resultType="User">
select * from user where id=#{id}
</select>
<select id="selectAllUser" resultMap="userMap">
select * from user
</select>
</mapper>
映射文件包含的顶级元素:
- cache:给定命名空间的缓存配置。
- cache-ref:其他命名空间缓存配置的引用。
- resultMap:描述如何从数据库结果集中来加载对象。
- sql:可被其他语句引用的可重用语句块。
- insert:映射插入语句
- update:映射更新语句
- delete:映射删除语句
- select:映射查询语句
1.resultMap
resultMap 是映射文件中最复杂也是最强大的元素。 resultMap 的设计就是简单语句不需要明确的结果映射,而很多复杂语句确实需要描述它们的关系。
首先来看上述示例映射文件中的第一个 select 映射语句:
<select id="selectUserById" parameterType="int" resultType="User">
select * from user where id=#{id}
</select>
在这里, resultType
为 User
, 其中 User
为 com.gler.mybatis.model.User
的别名。在这种情况下,MyBatis 会在幕后自动创建一个 resultMap ,基于属性名来映射列到 JavaBean 的属性上。
再来看自定义的结果集 resultMap:
<resultMap id="userMap" type="User">
<id property="id" column="id" javaType="int"></id>
<result property="username" column="username" javaType="String"></result>
<result property="password" column="password" javaType="String"></result>
<result property="sex" column="sex" javaType="String"></result>
<result property="address" column="address" javaType="String"></result>
</resultMap>
在第二个 select 映射语句中,可以看到映射到了自定义的 userMap
。
<select id="selectAllUser" resultMap="userMap">
select * from user
</select>
当列名不匹配的时候就需要定义一个外部的 resultMap ,上述的例子列名是匹配的,不匹配的情况如下:
<resultMap id="userMap" type="User">
<id property="id" column="user_id" />
<result property="username" column="user_name"/>
<result property="password" column="user_password"/>
<result property="sex" column="user_sex"/>
<result property="address" column="user_sex"/>
</resultMap>
引用语句:
<select id="selectUsers" resultMap="userMap">
select user_id, user_name, user_password, user_sex, user_sex
from user
where id = #{id}
</select>
resultMap 的子元素
包括:
- constructor:用来将结果注入到一个实例化好的类的构造方法中
- idArg: ID 参数,标记结果作为 ID
- arg:注入到构造方法的一个普通结果
- id: 一个 ID 结果,标记结果作为 ID
- result:注入到字段或 JavaBean 属性的普通结果
- association:复杂的类型关联,多个结果合成的类型
- 嵌入结果映射:结果映射自身的关联,也可以引用一个外部结果映射
- collection:复杂类型的集
也可以引用一个外部结果映射 - discriminator:使用结果值来决定使用哪个结果集
- case:基本一些值的结果映射
- 也可以引用一个外部结果映射
- case:基本一些值的结果映射
resultMap 的属性
包括:
- id :当前命名空间中的一个唯一标识,用于标识一个 resultMap
- type:类的全限定名, 或者一个类型别名
- autoMapping:为这个ResultMap开启或者关闭自动映射,该属性会覆盖全局的属性 autoMappingBehavior。默认值为:unset
(1) id & result
id 和 result 都是映射一个单独列的值到简单数据类型(数值型、字符串和日期等)的单独属性或字段 。唯一不同的是 id 为主键映射,而 result 为其他数据库表字段到 JavaBean 属性的映射。
属性 | 描述 |
---|---|
property | 映射到 JavaBean 的属性 |
column | 数据库的列名或列名的重命名标签 |
javaType | 一个 Java 类的完全限定名,或一个类型别名 。如果你映射到一个 JavaBean , MyBatis 通常可以断定类型。如果你映射到的是 HashMap,应该明确地指定 javaType 来保证所需的行为 |
jdbcType | 所支持的 JDBC 类型,其仅作用在对插入,更新和删除操作允许为空的列。这是 JDBC 的需要,而 MyBatis 不需要。如果直接使用 JDBC 编程,需要指定这个类型且有允许为空的列 |
支持的 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
(2) constructor
除了用 id & result
将 JavaBean 属性映射到数据库字段外,还可以用构造方法实现映射。
首先为 JavaBean User 类添加有参的构造方法:
public User(Integer id,String username,String password,String sex,String address){
this.id = id;
this.username = username;
this.password = password;
this.sex = sex;
this.address = address;
}
resultMap 的代码如下:
<resultMap id="userMap" type="User">
<constructor>
<idArg column="id" javaType="int"/>
<arg column="username" javaType="String"/>
<arg column="password" javaType="String"/>
<arg column="sex" javaType="String"/>
<arg column="address" javaType="String"/>
</constructor>
</resultMap>
(3) discriminator
有时一个单独的数据库查询也许返回很多不同 (但是希望有些关联) 数据类型的结果集。 鉴别器元素就是被设计来处理这个情况的, 还有包括类的继承层次结构,它很像 Java 语言中的 switch 语句。
下面的例子,当 sex 为 male ,才映射 sex 的属性。
<resultMap id="userMap" type="User">
<id property="id" column="id" javaType="int"/>
<result property="username" column="username" javaType="String"/>
<result property="password" column="password" javaType="String"/>
<discriminator javaType="String" column="sex">
<case value="male" resultType="sexResult">
<result property="sex" column="sex" javaType="String"/>
</case>
</discriminator>
<result property="address" column="address" javaType="String"/>
</resultMap>
2.select
在 MyBatis 中,对于每个 insert 、update 或 delete 操作通常对应多个 select 操作。
例如一个简单的查询:
<select id="selectUserById" parameterType="int" resultType="User">
select * from user where id=#{id}
</select>
这个语句被叫做 selectUserById
,有一个 int(即 Integer)
型参数,返回一个 User
类型的对象。
注意参数符号:
#{id}
上述语句对于的 JDBC 语句如下:
String selectUserById = "select * from user where id=?";
PreparedStatement ps = conn.prepareStatement(selectUserById);
ps.setInt(1,id);
select 的属性:
属性 | 描述 |
---|---|
id | 命名空间中唯一的标识符,可被其他语句引用 |
parameterType | 传入该语句的参数的完全限定名或别名 |
resultType | 该语句返回类型的完全限定名或别名,注意如果是集合情形,那应该是集合可以包含的类型,而不能是集合本身 |
resultMap | 外部 resultMap 的命名引用 |
flushCache | 任何语句的调用都会导致本地缓存和二级缓存被清空,默认为 false |
useCache | 导致本条语句的结果被二级缓存,默认为 true |
timeout | 设置驱动器在抛出异常前数据库返回请求结果的秒数,默认为 unset(依赖驱动) |
fetchSize | 尝试影响驱动程序每次批量返回的结果行数和这个设置值相等。默认为 unset(依赖驱动) |
statementType | 让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认为 PREPARED |
resultSetType | FORWARD_ONLY,SCROLL_SENSITIVE 或 SCROLL_INSENSITIVE 中的一个,默认为 unset(依赖驱动) |
databaseId | 如果配置了 databaseIdProvider,MyBatis 会加载所有的不带 databaseId 或匹配当前 databaseId 的语句;如果带或者不带的语句都有,则不带的会被忽略 |
resultOrdered | 仅针对嵌套结果 select 语句适用,如果为 true,就是假设包含了嵌套结果集或是分组了,默认为 false |
resultSets | 仅对多结果集适用,它将列出返回的结果集并为每个结果集给一个名称,名称用逗号分隔 |
注:返回时可以使用 resultType 或 resultMap,但不能同时使用。
3.insert、update 和 delete
(1)insert
例如一个插入操作:
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
insert into user (username,password,sex,address) values (#{username},#{password},#{sex},#{address})
</insert>
这里使用了 useGeneratedKeys
来实现自动生成主键策略,然后把 keyProperty
设成对应的列。
(2)update
例如一个更新操作:
<update id="updateUser" parameterType="User">
update user set
address=#{address}
where id=#{id}
</update>
(3)delete
例如一个删除操作:
<delete id="deleteUser" parameterType="int">
delete from user where id=#{id}
</delete>
insert 、update 和 delete 的属性:
属性 | 描述 |
---|---|
id | 命名空间中唯一的标识符,可被其他语句引用 |
parameterType | 传入该语句的参数的完全限定名或别名 |
flushCache | 任何语句的调用都会导致本地缓存和二级缓存被清空,默认为 false |
timeout | 设置驱动器在抛出异常前数据库返回请求结果的秒数,默认为 unset(依赖驱动) |
statementType | 让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认为 PREPARED |
useGeneratedKeys | (仅对 insert 和 update 有用)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系数据库管理系统的自动递增字段),默认为 false |
keyProperty | (仅对 insert 和 update 有用)唯一标记一个属性,MyBatis 会通过 getGeneratedKeys 的返回值或者通过 insert 语句的 selectKey 子元素设置它的键值,默认为 unset |
keyColumn | (仅对 insert 和 update 有用)通过生成的键值设置表中的列名,这个设置仅在某些数据库(像 PostgreSQL)是必须的,当主键列不是表中的第一列的时候需要设置 |
databaseId | 如果配置了 databaseIdProvider,MyBatis 会加载所有的不带 databaseId 或匹配当前 databaseId 的语句;如果带或者不带的语句都有,则不带的会被忽略 |
注:
useGeneratedKeys
、keyProperty
和keyColumn
仅对insert
和update
有用。同时只有数据库支持自增长主键字段(比如 MySQL、SQL Server)才可以设置useGeneratedKeys="true"
,像 Oracle 则不支持自增长 id,如果设置useGeneratedKeys="true"
就会报错。
(4)selectKey
对于不支持自增长 id 的 JDBC 驱动来说, MyBatis 采用 selectKey
来生成主键。
举个例子:
<insert id="insertUser">
<selectKey keyProperty="id" resultType="int" order="BEFORE">
select seq_users.nextval from dual
</selectKey>
insert into user (username,password,sex,address) values (#{username},#{password},#{sex},#{address})
</insert>
上述代码使用语句 select seq_users.nextval from dual
生成一个 key 。
selectKey 的属性:
属性 | 描述 |
---|---|
keyProperty | selectKey 语句结果应该被设置的目标属性 |
keyColumn | 匹配属性的返回结果集中的列名称 |
resultType | 结果的类型, MyBatis 允许任何简单类型(包括字符串)作为主键的类型 |
order | 可设置为 BEFORE 或 AFTER。如果设置为 BEFORE,那么它会首先选择主键,设置 keyProperty 然后执行 insert 语句。如果设置为 AFTER,那么先执行 insert 语句,然后是 selectKey ,这和像 Oracle 的数据库相似,在 insert 语句内部可能有嵌入索引调用 |
statementType | 让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认为 PREPARED |
4.sql
sql 元素可以定义可复用的 SQL 代码段,供其他语句调用。如:
<sql id="selectAllUser">
select * from user
</sql>
如果要调用上述的 SQL 代码段,可以这样写:
<select id="selectUserById" parameterType="int" resultType="User">
<include refid="selectAllUser"/>
where id=#{id}
</select>
5.parameters
在 SQL 映射语句中使用了参数 parameterType
, MyBatis 可以使用基本数据类型和 Java 复杂数据类型。
(1)基本数据类型参数
根据 id 查询用户信息
<select id="selectUserById" parameterType="int" resultType="User">
select * from user where id=#{id}
</select>
(2)Java 实体类型参数
插入一条用户信息
<select id="insertOneUser" parameterType="User" >
insert into user (id,username,password,sex,address) values
(#{id},#{username},#{password},#{sex},#{address})
</select>
(3)Map 参数
根据 username 和 sex 查询用户信息
<select id="selectUserByNameAndSex" parameterType="map" resultType="User">
select * from user where name=#{name} and sex=#{sex}
</select>
(4)字符串替换
默认情况下,使用 #{}
格式的语法会导致 MyBatis 创建预处理语句属性并安全地设置值(比如?),这样做更安全,更迅速。如果想直接在 SQL 语句中插入一个不改变的字符串。比如,像 ORDER BY,可以使用: ORDER BY ${columnName}
,这里 MyBatis 不会修改或转义该字符串。
注: 以上述方法接受用户的输入数据是不安全的,容易造成 SQL 注入攻击。因此,要么不允许用户输入,要么自行转义并检验。
6.cache
MyBatis 包含一个非常强大的,可配置和定制的缓存特性。
默认情况下缓存是未开启的,除了局部的 session 缓存,它可以提高性能,且能解决全局依赖。
要开启二级缓存,只需SQL 映射文件中添加一行:<cache/>
<cache/>
语句的效果如下:
- 映射文件中的所有 select 语句将会被缓存
- 映射文件中的所有 insert,update 和 delete 语句会刷新缓存
- 缓存会使用 Least Recently Used(LRU)算法来收回
- 缓存不会被设定的时间所清空
- 缓存会存储列表集合或对象(无论查询方法返回什么)的 1024 个引用
- 缓存会被视为是 read/write(可读/可写)的缓存,意味着对象检索不是共享的,而且可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改
创建一个 FIFO 缓存让其 60s 清空一次,存储512个对象或列表引用,返回的结果只读。因此在不同线程中的调用者之间修改它们会导致引用冲突。
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
缓存可用的回收策略有:
- LRU – 最近最少使用的:移出最近较长周期内都没有被使用的对象
- FIFO – 先进先出:按对象进入缓存的顺序来移除
- SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象
- WEAK – 弱引用:强制性地移除基于垃圾收集器状态和弱引用规则的对象
在不同的命名空间里共享同一个缓存配置或者实例。在这种情况下,可以使用 cache-ref
来引用另外一个缓存。
<cache-ref namespace="com.gler.mybatis.mapper.UserMapper"/>
参考链接