目录
映射器是MyBatis最为复杂且最重要的组件。它由一个接口和XML文件(或注解)组成。在映射器中可以配置参数、SQL语句、存储过程、缓存、级联等,并能够通过简易的映射规则映射到指定的POJO或其他对象上,映射器能够有效消除JDBC底层的代码。
整个MyBatis应用程序开发中,映射器的开发量占全部工作量的80%。MyBatis的映射器可以使用注解完成,但是应用范围并不广,主要原因:【1】面对复杂性,SQL会显得无力,尤其是面对较长的SQL;【2】注解的可读性较差;【3】在功能上,注解丢失了XML上下文相互引用的功能。基于实际情况,我们主要讨论XML的实现方式。
1、概述
映射器的配置元素:
元素 | 描述 | 备注 |
select | 查询语句 | 自定义参数,返回结果等 |
insert | 插入语句 | 执行后返回一个整数,代表插入的条数 |
update | 更新语句 | 执行后返回一个整数,代表更新的条数 |
delete | 删除语句 | 执行后返回一个整数,代表删除的条数 |
sql | 允许定义一部分SQL,然后再各个地方引用它 | 一次定义,可以在多个SQL语句中使用 |
resultMap | 描述从数据库结果集中来加载对象,它是最复杂、最强大的元素 | 提供映射规则 |
cache | 给定命名空间的缓存配置 | —— |
cache-ref | 其他命名空间缓存配置的引用 | —— |
2、select元素
select元素代表SQL的select语句,用于查询。看一下select元素的配置。
元素 | 描述 | 备注 |
id | id和Mapper的命名空间组合起来是唯一的,供MyBatis使用 | 如果命名空间和id结合起来不唯一,MyBatis将抛出异常 |
parameterType | 给出类的全命名,也可以给出别名,但是别名必须是MyBatis内部定义或者自定义的 | 可以选择Java Bean、Map等简单的参数类型传递给SQL |
resultType | 定义类的全路径,在允许自动匹配的情况下,结果集将通过Java Bean的规范映射; 或定义为int、double、float、map等参数 也可以使用别名,但是要符合别名规范,且不能和resultMap同时使用 | 常用的参数之一,比如统计总条数时可以把它的值设置为int |
resultMap | 它是映射集的引用,将执行强大的映射功能。resultType和resultMap不能同时使用 | 最复杂的元素,可以配置映射规则、级联、typeHandler等 |
flushCache | 在调用SQL后,是否要求MyBatis情况之前查询本地缓存和二级缓存 | 取值为布尔类型,默认为false |
useCache | 启动二级缓存的开关,是够要求MyBatis将此次结果缓存 | 布尔类型,默认为true |
timeout | 设置超时参数,超时时将抛出异常,单位为秒 | 默认为数据库厂商提供的JDBC驱动所设置的秒数 |
fetchSize | 获取记录的总条数设定 | 默认为数据库厂商提供的JDBC驱动所设置的条数 |
statementType | 使用哪个JDBC的Statement工作,取值为STATEMENT、PREPARED、CALLABLE | 默认为PREPARED |
resultSetType | —— | 默认为数据库厂商提供的JDBC驱动所设置 |
databaseId | 数据库厂商标识设置 | 多数据库支持 |
resultOrdered | 用于嵌套结果select语句 | 布尔类型,默认为false |
resultSets | 用于多结果集的情况,将列出执行SQL后每个结果集的名称,每个名称之间用逗号分隔 | 较少使用 |
2.1 简单的select元素的应用
一个栗子,统计用户表中同一个姓氏的数量:
<select id="" parameterType="String" resultType="sysRole">
select count(*) from sys_role
where user_name like concat('%',#{firstName},'%')
<select/>
- id:它配合Mapper的全限定名,联合成一个唯一的标识,用于标识这条SQL
- parameterType:表示这条SQL接受的参数类型【即传入的参数的类型】,可以是MyBatis系统定义或者自定义的别名;
- resultType:表示这条SQL语句返回的结果集的类型,与parameterType一样,可以是系统或者自定义的别名,也可以是类的全限定名;
- #{firstName}:如果传入的是pojo实体类型,占位符中的变量名称必须是pojo实体类中对应的属性.属性.属性...。起到占位作用,如果传入的是基本类型(string,long,double,int,boolean,float等),那么#{}中的变量名称可以随意写。
仅仅只有这一条SQL语句,还不够,我们还需要一个接口程序才能运行起来:
public intserface SysRoleMapper{
public Integer getRoleFirstName(String firstName);
}
这个栗子仅仅是让我们认识select元素的基础属性和用法,在实际的开发情况中,我们所遇到的问题比这条SQL更加复杂。
2.2 自动映射和驼峰映射
MyBatis提供了自动映射的功能,在默认情况下自动映射是开启的,使用它能够有效地减少大量的映射配置,进而减少体力劳动。
关于它们的配置主要在MyBatis的基础文件mybatisConfig.xml中进行配置。在<settings><settings/>元素标签内有两个可以配置的选项autoMappingBehavior和mapUnderscoreToCamelCase,它们是控制自动映射和驼峰映射的开关。
配置自动映射的autoMappingBehavior选项的取值范围是:
- NONE,不进行自动映射。
- PARTIAL,默认值,只对没有嵌套结果集进行自动映射。
- FULL,对所有的结果集进行自动映射,包括嵌套结果集。
默认情况下,使用默认的PARTIAL级别就可以了。为了实现自动映射,首先给出一个POJO类:
public class SysRole{
private int id;
private String roleName;
private String note;
/* setter and getter */
}
如上SysRole类,如果编写的SQL列名和属性名保持一致,那么它就会形成自动映射,比如通过角色编号获取角色信息:
<select id="getRole" parameterType="java.lang.Integer" resultType="com.learn.ssmr.chapter5.pojo.SysRole">
select id, role_name as roleName ,note
from sys_role
where id = #{id}
<select/>
原来的列名role_name被别名roleName代替了,这样就和POJO上的属性名称保持一致了。此时MyBatis就会将这个结果集映射到POJO的属性roleName上,自动完成映射,而无须再进行任何配置,明显减少了工作量。
如果系统按照驼峰命名法,那么只需在配置项把mapUnderscoreToCamelCase设置为true即可。这样上述SQL语句可以修改成:
<select id="getRole" parameterType="java.lang.Integer" resultType="com.learn.ssmr.chapter5.pojo.SysRole">
select id, role_name, note
from sys_role
where id = #{id}
<select/>
MyBatis会严格按照驼峰命名的方式做自动映射,只是这样会要求字段和POJO的属性名严格对应,降低了灵活性。
自动映射和驼峰映射都建立在SQL列名和POJO属性名的映射关系上,而现实开发情况更复杂,可能有些字段有主表和从表关联的级联,或者typeHandler的复杂转换规则,此时resultType元素是无法满足这些要求的。如果需要更强大的映射规则,可考虑使用resultMap。
2.3 传递多个参数
自己写的demo中多是传递一个参数,在实际开发中我们可能会遇到传递多个参数的情况,如订单可以通过编号查询,也可以通过订单名称、日期或者价格等参数进行查询。假定场景:通过角色名role_name和备注note对角色进行模糊查询,这样就有两个参数了。
2.3.1 使用map接口传递参数
- 在MyBatis中运行map接口通过键值对传递多个参数,接口方法定义如下:
public interface SysRoleMapper{
public List<SysRole> getRoleByMap(Map<String, Object> paramterMap);
}
- 此时,传递给映射器的是一个map对象,使用它在SQL中设置对应的参数,如下:
<select id="getRoleByMap" paramterType="map" resultType="sysRole">
select id, role_name as roleName, note
from sys_role
where role_name like concat('%',#{roleName},'%')
and note like concat('%',#{note},'%')
<select/>
- 设置map参数:
SysRoleMapper roleMapper = sqlSession.getMapper(SysRoleMapper.class);
Map<String, Object> paramterMap = new HashMap<String, Object>();
paramterMap.put("roleName", "1");
paramterMap.put("note", "1");
List<SysRole> roles = roleMapper.getRoleMap(paramterMap);
- 严格来说,map适用几乎所有场景,但是实际情况使用不多。原因:
【1】map是一个键值对应的集合,使用者需要通过阅读它的键,才能明白其作用;
【2】使用map不能限定其传递的数据类型,因此业务性不强,可读性差。
2.3.2 使用注解传递参数
- 使用map传递多个参数----可读性差。而MyBatis也提供了一个注解@Param,可以通过它去定义映射器的参数名称,使用它可以得到更好的可读性,接口定义方法如下:
public interface SysRoleMapper{
public List<SysRole> getRoleByAnnotation(@Param("roleName") String roleName,
@Param("note") String note);
}
- 可读性提高了,我们能够明确知道参数,一目了然。这个时候需要修改一下映射文件:
<select id="getRoleByAnnotation" resultType="sysRole">
select id, role_name as roleName, note
from sys_role
where role_name like concat('%',#{roleName},'%')
and note like concat('%',#{note},'%')
<select/>
- 此时,不需要再给出传入的参数类型,即不用设置patamterType属性,让MyBatis自动搜索即可。通过注解配置提高了可读性,但是如果SQL语句很复杂,传递的参数很多的时候,那么接口方法要传递的参数也就很多了,使用起来很不方便。此时,我们还可以使用Java Bean的方式传递参数。
2.3.3 使用Java Bean传递参数
- 定义一个POJO类---RoleParams:
public class RoleParams{
private String roleName;
private String note;
/*setter and getter*/
}
- 此时,接口方法定义如下:
public interface SysRoleMapper{
public List<SysRole> getRoleByJavaBean(RoleParams roleParam);
}
- 修改映射文件如下:
<select id="getRoleByJavaBean" paramterType="com.learn.ssm.chapter.param.Roleparams"
resultType="sysRole">
select id, role_name as roleName, note
from sys_role
where role_name like concat('%',#{roleName},'%')
and note like concat('%',#{note},'%')
<select/>
- 修改JavaBean定义的属性作为参数:
SysRoleMapper roleMapper = sqlSession.getMapper(SysRoleMapper.class);
RoleParams roleParams = new RoleParams();
roleParams.setRoleName("1");
roleParams.setNote("1");
List<SysRole> roles = roleMapper.getRoleByJavaBean(roleParams);
2.3.4 使用混合方式传递参数
- 在某些情况下,可能还需要混合使用几种方法来传递参数,比如查询角色名和备注的同时,还需要支持分页,而分页的POJO实现如下:
public class PageParams{
private int start;
private int limit;
/*setter and getter*/
}
- 此时,接口设计如下:
public interface SysRoleMapper{
public List<SysRole> getRoleByMix(@Param("params") RoleParams roleParams,
@Param("page") PageParams pageParams);
}
- 关于映射文件,则如下修改:
<select id="getRoleByMix" resultType="sysRole">
select id, role_name as roleName, note
from sys_role
where role_name like concat('%',#{roleName},'%')
and note like concat('%',#{note},'%')
limit #{page.start},#{page.limit}
<select/>
- 这样就能使用混合参数,MyBatis对params和page这类Java Bean参数提供EL(中间语言)支持。
小结:
- 使用map传递参数导致了业务可读性的丢失,致使后期维护困难,应废弃这种方式;
- 使用@Param注解传递多个参数,收到参数个数(n)的影响。当n<= 5时,最佳传递方式,比使用Java Bean更好;当n>= 5时,多个参数将给调用带来困难,不推荐;
- 当参数多于5个时,建议使用JavaBean方式。
2.4 使用resultMap映射结果集
在复杂的映射配置时,select元素提供了resultMap属性。先定义resultMap属性:
<mapper namespace="com.learn.ssm.chapter.param.Roleparams">
<resultMap id="roleMap" type="sysRole">
<id property="id" column="id"/>
<result property="roleName" column="role_name"/>
<result property="note" column="note"/>
<resultMap/>
<select id="getRoleMap" paramterType="long" resultMap="roleMap">
select id, role_name, note
from sys_role
where id=#{id}
</select>
</mapper>
分析一下上边的SQL配置:
- resultMap:定义了一个roleMap,它的属性id代表它的标识,type代表使用哪个类作为其映射类,可以是别名或全限定类名
- 子元素id:表示resultMap的主键,而result表示其属性,id和result元素的属性property表示POJO类的属性名,而column表示SQL的字段名。把POJO的属性和SQL的列名做对应,就建立起了映射关系
- select元素中:resultMap指定了采用哪个resultMap作为其映射规则
2.5 分页参数RowBounds
MyBatis支持分页,内置了一个专门处理分页的类——RowBounds。
RowBounds源码:
poublic class RowBounds{
private static final int NO_ROW_OFFSET = 0;
private static final int NO_ROW_LIMIT = Integer.MAX_VALUE;
private static final RowBounds DEFAULT = new RowBounds();
private int offset;
private int limit;
public RowBounds(){
this.offset = NO_ROW_OFFSET;
this.limit = NO_ROW_LIMIT;
}
public RowBounds(int offset, int limit){
this.offset = offset;
this.limit = limit;
}
/*setter and getter*/
}
- offset:偏移量,即从第几行开始读取记录
- limit:限制条数,默认值0~2147483647
此时,我们需要给接口增加一个rowBounds参数
public interface SysRoleMapper{
public List<SysRole> getRoleByRowBounds(@Param("roleName") String roleName,@Param("note") String note, RowBounds rowBounds);
}
配置一下映射文件:
<select id="getRoleByRowBounds" resultType="sysRole">
select id, role_name as roleName, note
from sys_role
where role_name like concat('%',#{roleName},'%')
and note like concat('%',#{note},'%')
<select/>
看一下配置文件,并没有关于RowBounds参数的任何信息,它是一个附加参数,MyBatis会自动识别,然后进行分页。测试一下接口:
SqlSession sqlsession = null;
try{
sqlSession = SqlSessionFactoryUtils.openSqlSession();
SysRoleMapper roleMapper = sqlSession.getMapper(SysRoleMapper.class);
RowBounds rowBounds = new RowBounds(0, 20);
List<SysRole> roleList = roleMapper.getRoleByRowBounds("roleName", "note", "rowBounds");
} catch(Exception e){
e.printStackTrace();
} finally{
if(sqlSession != null){
sqlSession.close();
}
}
运行分页就可以限定地查询返回至多20条记录的结果。而这个RowBounds分页只能应用于一些小数据量的查询,原理是执行SQL查询后,按照偏移量和限制条数返回查询结果,所以对于大量的数据查询,性能不佳,可通过分页插件进行处理。
3、insert元素
执行select的基础就是先插入数据,而插入数据依赖于insert语句。相对于select而言,insert语句简单很多。insert语句可以配置以下属性:
属性 | 描述 | 备注 |
id | SQL编号,标识这条SQL | 命名空间+id+databaseId唯一,否则抛异常 |
parameterType | 传入参数类型,同select元素 | 和select一样,可以是单个或多个参数 |
flushCache | 是否刷新缓存,布尔类型;true时,插入数据会刷新一级和二级缓存;false时,不刷新缓存 | 默认true |
statementType | 使用哪个JDBC的Statement工作,取值为STATEMENT、PREPARED、CALLABLE | 默认PREPARED |
useGeneratedKeys | 是否启用JDBC的getGeneratedKeys方法来取出由数据库内部生成的主键(如MySQ和SQLServer这样的数据库表的自增主键) | 默认false |
keyProperty | (仅对insert和update有用)唯一标记一个属性,通过generatedKeys的返回值,或通过insert语句的selectKey子元素设置它的键值。如果是复合主键,要把每一个名称用逗号隔开 | 默认unset,不能和keyColumn一起使用 |
keyColumen | (仅对insert和update有用)通过生成的键值设置表中的列名,这个设置仅在某些数据库中是必须的,当主键列不是表中的第一列时需要设置。如果是复合主键,需要把每一个名称用逗号隔开 | 不能和keyProperty一起使用 |
MyBatis在执行一条insert语句后,会返回一个整数表示其影响记录数。
3.1 简单的insert语句的应用
插入一条角色记录:
<insert id="insertRole" paramterType="sysRole">
insert into sys_role(roleName, note)
values(#{roleName}, #{note})
<insert/>
分析:
- id:表示这条SQL,结合命名空间让MyBatis找到它
- paramterType:表示传入的参数类型
- 没有配置的属性将采用默认值,这样就完成一个角色记录的插入。
3.2 主键回填
JDBC的statement对象在执行插入的SQL后,可以通过getGeneratedKeys方法获得数据库生成的主键,这样便能达到获取主键的功能。在insert语句中有个开关属性useGeneratedKeys,用来控制是否打开这个功能,默认值为false。当打开这个开关,还要配置其属性keyProperty或keyColumn,告诉系统把生成的主键放入哪个属性中,如果存在多个主键,就用逗号隔开。
返回主键:
<insert id="insertRole" paramterType="sysRole" useGeneratedKeys="true" keyProperty=id>
insert into sys_role(roleName, note)
values(#{roleName}, #{note})
<insert/>
- useGeneratedKeys:表示采用JDBC的Statement对象的getGeneratedKeys方法返回主键
- keyProperty:表示使用哪个POJO的属性去匹配这个主键,这里是id,说明它会用数据库生成主键去赋值给这个POJO。
3.3 自定义主键
有时候主键可能依赖于某些规则,比如取消角色表的id递增规则,而将其规则修改为:
- 角色表记录为空时,id设置为1
- 角色表记录不为空时,id设置为当前id加3
它依赖于selectKey元素进行支持,允许自定义键值得生成规则:
<insert id="insertRole" paramterType="sysRole">
<selectKey keyProperty="id" resultType="java.lang.Integer" order="VEFORE">
select if (max(id) = null, 1, max(id) + 3)
from sys_role
</selectKey>
insert into sys_role(id,roleName,note)
values(#{id},#{roleName},#{note})
</insert>
- selectey元素的keyProperty指定了采用哪个属性作为POJO的主键。
- resultType:指定返回结果集类型
- order = “BEFORE”:说明它相当于当前定义的SQL前执行【在执行插入之前执行生成主键的SQL,然后插入数据。】。
4、update元素和delete元素
update和delete的属性差不多,执行完会返回一个整数。所以表示该SQL语句影响了数据库的记录行数
<update id="updateRole" parameterType="sysRole">
update sys_role set roleName = #{roleName},note = #{note}
where id = #{id}
</update>
<delete id="deleteRole" parameterType="java.lang.Integer">
delete from sys_role where id = #{id}
</delete>
5、sql元素
sql元素的作用:定义一条SQL的一部分,便于后面SQL引用。
<mapper namespace="com.learn.ssm.chapter.param.Roleparams">
<resultMap id="roleMap" type="sysRole">
<id property="id" column="id"/>
<result property="roleName" column="role_name"/>
<result property="note" column="note"/>
<resultMap/>
<!-- sql元素 -->
<sql id="roleCols">
id, roleName, note
</sql>
<select id="getRoleMap" paramterType="long" resultMap="roleMap">
select <include refid="roleCols"/>
from sys_role
where id=#{id}
</select>
<insert id="insertRole" paramterType="sysRole">
<selectKey keyProperty="id" resultType="java.lang.Integer" order="VEFORE">
select if (max(id) = null, 1, max(id) + 3)
from sys_role
</selectKey>
insert into sys_role(<include refid="roleCols"/>)
values(#{id},#{roleName},#{note})
</insert>
</mapper>
通过sql元素进行定义,就可以通过include元素引入到各条SQL中,减少对其列名的重复编写。
sql元素还支持变量递增:
<sql id="roleCols">
${alias}.id,${alias}.roleName,${alias}.note,
</sql>
<select id="getRole" parameterType="java.lang.Integer" resultMap="sysRole">
<include refid="roleCols">
<property name="alias" value="r">
</include>
from sys_role where id = #{id}
</select>
在include元素中定义了一个命名为alias的变量,其值是SQL中表sys_role的别名,然后sql元素就能够使用这个变量名了。