赞
踩
目录
映射器是MyBatis最为复杂且最重要的组件。它由一个接口和XML文件(或注解)组成。在映射器中可以配置参数、SQL语句、存储过程、缓存、级联等,并能够通过简易的映射规则映射到指定的POJO或其他对象上,映射器能够有效消除JDBC底层的代码。
整个MyBatis应用程序开发中,映射器的开发量占全部工作量的80%。MyBatis的映射器可以使用注解完成,但是应用范围并不广,主要原因:【1】面对复杂性,SQL会显得无力,尤其是面对较长的SQL;【2】注解的可读性较差;【3】在功能上,注解丢失了XML上下文相互引用的功能。基于实际情况,我们主要讨论XML的实现方式。
映射器的配置元素:
元素 | 描述 | 备注 |
select | 查询语句 | 自定义参数,返回结果等 |
insert | 插入语句 | 执行后返回一个整数,代表插入的条数 |
update | 更新语句 | 执行后返回一个整数,代表更新的条数 |
delete | 删除语句 | 执行后返回一个整数,代表删除的条数 |
sql | 允许定义一部分SQL,然后再各个地方引用它 | 一次定义,可以在多个SQL语句中使用 |
resultMap | 描述从数据库结果集中来加载对象,它是最复杂、最强大的元素 | 提供映射规则 |
cache | 给定命名空间的缓存配置 | —— |
cache-ref | 其他命名空间缓存配置的引用 | —— |
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后每个结果集的名称,每个名称之间用逗号分隔 | 较少使用 |
一个栗子,统计用户表中同一个姓氏的数量:
- <select id="" parameterType="String" resultType="sysRole">
- select count(*) from sys_role
- where user_name like concat('%',#{firstName},'%')
- <select/>
仅仅只有这一条SQL语句,还不够,我们还需要一个接口程序才能运行起来:
- public intserface SysRoleMapper{
- public Integer getRoleFirstName(String firstName);
- }
这个栗子仅仅是让我们认识select元素的基础属性和用法,在实际的开发情况中,我们所遇到的问题比这条SQL更加复杂。
MyBatis提供了自动映射的功能,在默认情况下自动映射是开启的,使用它能够有效地减少大量的映射配置,进而减少体力劳动。
关于它们的配置主要在MyBatis的基础文件mybatisConfig.xml中进行配置。在<settings><settings/>元素标签内有两个可以配置的选项autoMappingBehavior和mapUnderscoreToCamelCase,它们是控制自动映射和驼峰映射的开关。
配置自动映射的autoMappingBehavior选项的取值范围是:
默认情况下,使用默认的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。
自己写的demo中多是传递一个参数,在实际开发中我们可能会遇到传递多个参数的情况,如订单可以通过编号查询,也可以通过订单名称、日期或者价格等参数进行查询。假定场景:通过角色名role_name和备注note对角色进行模糊查询,这样就有两个参数了。
- public interface SysRoleMapper{
- public List<SysRole> getRoleByMap(Map<String, Object> paramterMap);
- }
- <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/>
- 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);
【1】map是一个键值对应的集合,使用者需要通过阅读它的键,才能明白其作用;
【2】使用map不能限定其传递的数据类型,因此业务性不强,可读性差。
- 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/>
- 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/>
- SysRoleMapper roleMapper = sqlSession.getMapper(SysRoleMapper.class);
- RoleParams roleParams = new RoleParams();
- roleParams.setRoleName("1");
- roleParams.setNote("1");
- List<SysRole> roles = roleMapper.getRoleByJavaBean(roleParams);
- 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/>
小结:
在复杂的映射配置时,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配置:
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*/
- }

此时,我们需要给接口增加一个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查询后,按照偏移量和限制条数返回查询结果,所以对于大量的数据查询,性能不佳,可通过分页插件进行处理。
执行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语句后,会返回一个整数表示其影响记录数。
插入一条角色记录:
- <insert id="insertRole" paramterType="sysRole">
- insert into sys_role(roleName, note)
- values(#{roleName}, #{note})
- <insert/>
分析:
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/>
有时候主键可能依赖于某些规则,比如取消角色表的id递增规则,而将其规则修改为:
它依赖于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>
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>
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元素就能够使用这个变量名了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。