MyBatis之映射器

目录

1 概述

2 select元素——查询语句

2.1 概述

2.2 简单的select元素的应用

2.3 自动映射和驼峰映射

2.4 传递多个参数

2.4.1 使用map接口传递参数

2.4.2 使用注解传递多个参数

2.4.3 通过Java Bean传递多个参数

2.4.4 混合使用

2.4.5 总结

2.5 使用resultMap映射结果集

3 insert元素——插入语句

3.1 概述

3.2 简单的insert语句的应用

3.3 主键回填

3.4 自定义主键

4 update元素和delete元素

5 sql元素

6 resultMap元素

6.1 resultMap元素的构成

7 级联

7.1 MyBatis中的级联

7.2 实例

7.3 N+1问题

7.4 延迟加载

8 缓存

8.1 一级缓存

8.2 二级缓存

9 存储过程

9.1 IN和OUT参数存储过程

9.2 游标的使用


上文介绍了MyBatis的配置相关,详情请看《MyBatis之配置》。本文讲解MyBatis的映射器。

映射器是MyBatis最复杂且最重要的组件。它由一个接口加上XML文件(或者注解,但是注解并不常用,所以本文章只讨论XML)组成。在映射器中可以配置参数、各类的SQL语句、存储过程、缓存、级联等复杂的内容,并且通过简易的映射规则到指定的POJO或者其他对象上,映射器能有效消除JDBC底层的代码。


1 概述

映射器的配置元素
元素名称 描述 备注
select 查询语句,最常用、最复杂的元素之一 可以自定义参数,返回结果集等
insert 插入语句 执行后返回一个整数,代表插入的条数
update 更新语句 执行后返回一个整数,代表更新的条数
delete 删除语句 执行后返回一个整数,代表删除的条数
parameterMap 定义参数映射关系 即将被删除的元素,不建议使用
sql 允许定义一部分SQL,然后在各个地方引用它 例如,一张表列名,一次定义,可以在多个SQL语句中使用
resultMap 用来描述从数据库结果集中来加载对象,它是最复杂、最强大的元素 它将提供映射规则
cache 给定命名空间的缓存配置 ——
cache-ref 其他命名空间缓存配置的引用 ——

2 select元素——查询语句

2.1 概述

select元素的配置
元素 说明 备注
id 它和Mapper的命名空间组合起来是唯一的,供MyBatis调用 如果命名空间和id结合起来不唯一,MyBatis将抛出异常
parameterType 可以给出类的全命名,也可以给出别名,但是别名必须是MyBatis内部定义或者自定义的 可以选择Java Bean、Map等简单的参数类型传递给SQL
resultType

定义类的全路径,在允许自动匹配的情况下,结果集将通过Java Bean的规范映射;

或定义为int、double、float、map等参数;

也可以使用别名,但是要符合别名规范,且不能和resultMap同时使用

常用的参数之一,比如统计总条数时可以把它的值设置为int
resultMap 它是映射集的引用,将执行强大的映射功能。我们可以使用resultType和resultMap其中的一个,resultMap能提供自定义映射规则的机会 MyBatis最复杂的元素,可以配置映射规则、级联、typeHandler等
flushCache 它的作用是在调用SQL后,是否要求MyBatis清空之前查询本地缓存和二级缓存 取值为布尔值,true/false。默认值为false
useCache 启动二级缓存的开关,是否要求MyBatis将此次结果缓存 取值为布尔值,true/false。默认值为true

2.2 简单的select元素的应用

        <select id="countUserByFirstName" parameterType="string"
		resultType="int">
		select count(1) from t_user where user_name like
		concat(#{firstName},'%')
	</select>
  • id配合Mapper的全限定名,联合成为一个唯一的标识(在不考虑数据库厂商标识的前提下),用于标识这条SQL。
  • parameterType表示这条SQL接受的参数类型,可以是MyBatis系统定义或者自定义的别名,比如int、string、float等,也可以是类的全限定名,比如com.hys.mybatis.example3.pojo.User。
  • resultType表示这条SQL返回的结果类型,与parameterType一样,可以是系统定义或者自定义的别名,也可以是类的全限定名。
  • #{firstName}是被传递进去的参数。

2.3 自动映射和驼峰映射

 MyBatis提供了自动映射功能,在默认的情况下自动映射功能是开启的,使用它的好处在于能有效减少大量的映射配置,从而减少工作量。

在setting元素中有两个可以配置的选项autoMappingBehavior和mapUnderscoreToCamelCase,它们是控制自动映射和驼峰映射的开关。一般而言,自动映射会使用得多一些,因为可以通过SQL别名机制处理一些细节,比较灵活,而驼峰映射则要求比较严苛,所以在实际中应用不算太广。

2.4 传递多个参数

2.4.1 使用map接口传递参数

在MyBatis中允许map接口通过键值对传递多个参数。接口如下:

public List<Role> findRolesByMap(Map<String, Object> parameterMap);

映射XML:

        <select id="findRolesByMap" parameterType="map"
		resultType="role">
		select id,role_name as roleName,note from t_role where
		role_name like
		concat('%',#{roleName},'%') and note like
		concat('%',#{note},'%')
	</select>

注意,参数roleName和note,要求的是map的键,也就是需要按如下代码的方式传递参数:

        RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
        Map<String, Object> parameterMap = new HashMap<>();
        parameterMap.put("roleName", "1");
        parameterMap.put("note", "1");
        List<Role> roles = roleMapper.findRolesByMap(parameterMap);

2.4.2 使用注解传递多个参数

接口:

public List<Role> findRolesByAnnotation(@Param("roleName") String roleName, @Param("note") String note);

此时代码的可读性大大提高了,使用者能明确参数roleName是角色名称,而note是备注,一目了然。映射XML如下:

        <select id="findRolesByAnnotation" resultType="role">
		select
		id,role_name as roleName,note from t_role where
		role_name like
		concat('%',#{roleName},'%') and note like
		concat('%',#{note},'%')
	</select>

注意,此时并不需要给出parameterType属性,让MyBatis自动探索便可以了。

2.4.3 通过Java Bean传递多个参数

先定义一个参数的POJO——RoleParams,代码如下:

public class RoleParams {

    private String roleName;
    private String note;

    public String getRoleName() {
        return roleName;
    }

    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }

    public String getNote() {
        return note;
    }

    public void setNote(String note) {
        this.note = note;
    }
}

接口:

public List<Role> findRolesByBean(RoleParams roleParam);

映射XML:

        <select id="findRolesByBean"
		parameterType="com.hys.mybatis.example3.pojo.RoleParams"
		resultType="role">
		select
		id,role_name as roleName,note from t_role where
		role_name like
		concat('%',#{roleName},'%') and note like
		concat('%',#{note},'%')
	</select>

引入Java Bean定义的属性作为参数,然后查询:

        RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
        RoleParams roleParams = new RoleParams();
        roleParams.setRoleName("1");
        roleParams.setNote("1");
        List<Role> roles = roleMapper.findRolesByBean(roleParams);

2.4.4 混合使用

在某些情况下可能需要混合使用几种方法来传递参数。举个例子,查询一个角色,可以通过角色名称和备注进行查询,于此同时还需要支持分页,而分页的POJO实现代码如下:

public class PageParams {

    private int start;
    private int limit;

    public int getStart() {
        return start;
    }

    public void setStart(int start) {
        this.start = start;
    }

    public int getLimit() {
        return limit;
    }

    public void setLimit(int limit) {
        this.limit = limit;
    }
}

接口:

public List<Role> findRolesByMix(@Param("params") RoleParams roleParams, @Param("page") PageParams pageParams);

映射XML:

        <select id="findRolesByMix" resultType="role">
		select
		id,role_name as
		roleName,note from t_role where
		role_name like
		concat('%',#{params.roleName},'%') and note like
		concat('%',#{params.note},'%')
		limit #{page.start},#{page.limit}
	</select>

这样就能使用混合参数了,其中MyBatis对params和page这类Java Bean参数提供EL支持,为编程带来了很多的便利。

2.4.5 总结

  • 使用map传递参数导致了业务可读性的丧失,导致后续扩展和维护的困难,在实际的应用中要果断废弃这种方式。
  • 使用@Param注解传递多个参数,受到参数个数(n)的影响。当n≤5时,这是最佳的传参方式,它比用Java Bean更好,因为它更加直观;当n>5时,多个参数将给调用带来困难,此时不推荐使用它。
  • 当参数个数多于5个时,建议使用Java Bean方式。
  • 对于使用混合参数的,要明确参数的合理性。

2.5 使用resultMap映射结果集

自动映射和驼峰映射规则比较简单,无法定义多的属性,比如typeHandler、级联等。为了支持复杂的映射,select元素提供了resultMap属性。先定义resultMap属性,代码如下:

<mapper namespace="com.hys.mybatis.example3.mapper.RoleMapper">
	<resultMap type="role" id="roleMap">
		<id property="id" column="id" />
		<result property="roleName" column="role_name" />
		<result property="note" column="note" />
	</resultMap>

	<select id="getRoleUseResultMap" parameterType="long"
		resultMap="roleMap">
		select id,role_name,note from t_role where
		id=#{id}
	</select>
</mapper>
  • resultMap元素定义了一个roleMap,它的属性id代表它的标识,type代表使用哪个类作为其映射的类,可以是别名或者全限定名,role是com.hys.mybatis.example3.pojo.Role的别名。
  • 它的子元素id代表resultMap的主键,而result代表其属性,id和result元素的属性property代表POJO的属性名称,而column代表SQL的别名。把POJO的属性和SQL的列名做对应,例如POJO的属性roleName,就用SQL的列名role_name建立映射关系。
  • 在select元素中的属性resultMap制定了采用哪个resultMap作为其映射规则。

3 insert元素——插入语句

3.1 概述

insert语句的配置
属性 描述 备注
id SQL编号,用于标识这条SQL 命名空间+id+databaseId唯一,否则MyBatis会抛出异常
parameterType 参数类型,同select元素 和select一样,可以是单个参数或者多个参数
flushCache 是否刷新缓存,可以配置true/false,为true时,插入时会刷新一级和二级缓存,否则不刷新 默认值为true
timeout 超时时间,单位为秒  
statementType STATEMENT、PREPARED或CALLABLE中的一个。这会让MyBatis分别使用Statement、PreparedStatement(预编译)或CallableStatement(存储过程)

默认值为PREPARED

useGeneratedKeys 是否开启JDBC的getGeneratedKeys方法来取出由数据库内部生成的主键(比如MYSQL和SQL Server这样的数据库表的自增主键) 默认值为false
keyProperty (仅对insert和update有用)唯一标记一个属性,MyBatis会通过useGeneratedKeys的返回值,或者通过insert语句的selectKey子元素设置它的键值。如果是复合主键,要把每一个名称用逗号(,)隔开 默认值为unset。不能和keyColumn连用
keyColumn (仅对insert和update有用)通过生成的键值设置表中的列名,这个设置仅在某些数据库(像PostgreSQL)中是必须的,当主键列不是表中的第一列时需要设置。如果是复合主键,需要把每一个名称用逗号(,)隔开 不能和keyProperty连用

3.2 简单的insert语句的应用

        <insert id="insertRole" parameterType="role">
		insert into
		t_role(role_name,note) values(#{roleName},#{note})
	</insert>
  • id标识出这条SQL,结合命名空间让MyBatis能够找到它。
  • parameterType代表传入参数类型。

3.3 主键回填

上述代码展示了最简单的插入语句,但是它并没有插入id列,因为Mysql中的表格采用了自增主键,Mysql数据库会为该记录生成对应的主键。有时候还可能需要继续使用这个主键,用以关联其他业务,因此有时候把它取到是十分必要的,比如新增用户时,首先会插入用户表的记录,然后插入用户和角色关系表,插入用户时如果没有办法取到用户的主键,那么就没有办法插入用户和角色关系表了,因此在这个时候要拿到对应的主键,以便后面的操作,MyBatis提供了这样的支持。

JDBC中的Statement对象在执行插入的SQL后,可以通过getGeneratedKeys方法获得数据库生成的主键(需要数据库驱动支持),这样便能达到获取主键的功能。在insert语句中有一个开关属性useGeneratedKeys,用来控制是否打开这个功能,它的默为false。当打开了这个开关,还要配置其属性keyProperty或keyColumn,告诉系统把生成的主键放入哪个属性中,如果存在多个主键,就要用逗号(,)将它们隔开。

        <insert id="insertRole" parameterType="role"
		useGeneratedKeys="true" keyProperty="id">
		insert into
		t_role(role_name,note)
		values(#{roleName},#{note})
	</insert>

useGeneratedKeys代表采用JDBC的Statement对象的getGeneratedKeys方法返回主键,而keyProperty则代表将用哪个POJO的属性去匹配这个主键,这里是id,说明它会用数据库生成的主键去赋值给这个POJO,测试主键回填的结果,如下:

在第19行代码之前并没有给role对象的id属性赋值,而在执行insertRole方法后,通过监控role对象,就可以发现MyBatis给这个对象的id赋了值,拿到这个值,就可以在业务代码中执行下一步的关联和操作了。

3.4 自定义主键

有时候主键可能依赖于某些规则,比如取消角色表(t_role)的id的递增规则,而将其规则修改为:

  • 当角色表记录为空时,id设置为1。
  • 当角色表记录不为空时,id设置为当前id加3。

MyBatis对这样的场景也提供了支持,它主要依赖于selectKey元素进行支持,它允许自定义键值的生成规则。映射XML如下:

        <insert id="insertRole" parameterType="role">
		<selectKey keyProperty="id" resultType="long" order="BEFORE">
			select
			if (max(id)=null,1,max(id)+3) from t_role
		</selectKey>
		insert into
		t_role(id,role_name,note)
		values(#{id},#{roleName},#{note})
	</insert>

上述代码定义了selectKey元素,它的keyProperty指定了采用哪个属性作为POJO的主键。resultType告诉MyBatis将返回一个long型的结果集,而order设置为BEFORE,说明它将于当前定义的SQL前执行。


4 update元素和delete元素

因为update元素和delete元素比较简单,所以把它们放在一起论述。它们和insert的属性差不多,执行完也会返回一个整数,用以标识该SQL语句影响了数据库的记录行数。映射XML如下:

        <update id="updateRole" parameterType="role">
		update t_role set
		role_name=#{roleName},note=#{note} where id=#{id}
	</update>

	<delete id="deleteRole" parameterType="long">
		delete from t_role where
		id=#{id}
	</delete>

最后MyBatis会返回一个整数,标识对应的SQL执行后会影响了多少条数据库表里的记录。至于参数可以参考select元素的参数规则,在MyBatis中它们的规则是通用的。


5 sql元素

sql元素的作用在于可以定义一条SQL的一部分,方便后面的SQL引用它,比如最典型的列名。通常情况下要在select、insert等语句中反复编写它们,特别是那些字段较多的表更是如此,而在MyBatis中,只需要使用sql元素编写一次便能在其他元素中引用它了。

        <resultMap type="role" id="roleMap">
		<id property="id" column="id" />
		<result property="roleName" column="role_name" />
		<result property="note" column="note" />
	</resultMap>

	<sql id="roleCols">
		id,role_name,note
	</sql>

	<select id="getRole" parameterType="long" resultMap="roleMap">
		select
		<include refid="roleCols" />
		from t_role where id=#{id}
	</select>

	<insert id="insertRole" parameterType="role">
		<selectKey keyProperty="id" resultType="long" order="BEFORE">
			select
			if (max(id)=null,1,max(id)+3) from t_role
		</selectKey>
		insert into
		t_role(
		<include refid="roleCols" />
		)
		values(#{id},#{roleName},#{note})
	</insert>

通过sql元素进行了定义,就可以通过include元素引入到各条SQL中了。这样的代码,在字段多的数据库表可以重复使用,从而减少对其列名的重复编写。

sql元素还支持变量传递,代码如下:

        <sql id="roleCols">
		${alias}.id,${alias}.role_name,${alias}.note
	</sql>

	<select id="getRole" parameterType="long" resultMap="roleMap">
		select
		<include refid="roleCols">
			<property name="alias" value="r" />
		</include>
		from t_role r where id=#{id}
	</select>

在include元素中定义了一个命名为alias的变量,其值是SQL中表t_role的别名r,然后sql元素就能够使用这个变量名了。


6 resultMap元素

resultMap的作用是定义映射规则、级联的更新、定制类型转化器等。resultMap定义的主要是一个结果集的映射关系,也就是SQL到Java Bean的映射关系定义,它也支持级联等特性。只是MyBatis现有的版本只支持resultMap查询,不支持更新或者保存,更不必说级联的更新、删除和修改了。

6.1 resultMap元素的构成

resultMap元素的子元素,如下所示:

        <resultMap>
		<constructor>
			<idArg />
			<arg />
		</constructor>
		<id />
		<result />
		<association />
		<collection />
		<discriminator>
			<case />
		</discriminator>
	</resultMap>

其中constructor元素用于配置构造方法。一个POJO可能不存在没有参数的构造方法,可以使用constructor进行配置。假设角色类RoleBean不存在没有参数的构造方法,它的构造方法声明为public RoleBean(Integer id, String roleName),那么需要配置结果集,如下所示:

        <resultMap ...>
		<constructor>
			<idArg column="id" javaType="int" />
			<arg column="role_name" javaType="string" />
		</constructor>
		<!-- do something... -->
	</resultMap>

这样MyBatis就会使用对应的构造方法来构造POJO了。

id元素标识哪个列是主键,允许多个主键,多个主键则称为联合主键。result是配置POJO到SQL列名的映射关系。id和result元素的属性,如下表所示:

id元素和result元素的属性
元素名称 说明 备注
property 映射到列结果的字段或属性。如果POJO的属性匹配的是存在的且与给定SQL列名(column元素)相同的,那么MyBatis就会映射到POJO上 可以使用导航式的字段,比如访问一个学生对象(Student)需要访问学生证(selfcard)的发证日期(issueDate),那么可以写成selfcard.issueDate
column 对应的是SQL的列 ——
javaType 配置Java的类型 可以是特定的类完全限定名或者MyBatis上下文的别名
jdbcType 配置数据库类型 这是一个JDBC的类型,MyBatis已经做了限定,支持大部分常用的数据库类型
typeHandler 类型处理器 允许用特定的处理器来覆盖MyBatis默认的处理器。这就要制定jdbcType和javaType相互转化的规则

7 级联

级联是一个数据库实体的概念。比如角色就需要存在用户与之对应,这样就有角色用户表,一个角色可能有多个用户,这就是一对多的级联;除此之外,还有一对一的级联,比如身份证和公民是一对一的关系。在MyBatis中还有一种被称为鉴别器的级联,它是一种可以选择具体实现类的级联,比如要查找雇员及其体检表的信息,但是雇员有性别之分,而根据性别的不同,其体检表的项目也会不一样,比如男性体检表可能有前列腺的项目,而女性体检表可能有子宫的项目,那么体检表就应该分为男性和女性两种,从而根据雇员性别区分关联。

级联不是必须的,级联的好处是获取关联数据十分便捷,但是级联过多会增加系统的复杂度,同时降低系统的性能,此增彼减,所以当级联的层级超过3层时,就不要考虑使用级联了,因为这样会造成多个对象的关联,导致系统的耦合、复杂和难以维护。在现实的使用过程中,要根据实际情况判断是否需要使用级联。

7.1 MyBatis中的级联

MyBatis的级联分为3种:

  • 鉴别器(discriminator):它是一个根据某些条件决定采用具体实现类级联的方案,比如体检表要根据性别去区分。
  • 一对一(association):比如学生证和学生就是一种一对一的级联,雇员和工牌表也是一种一对一的级联。
  • 一对多(collection):比如班主任和学生就是一种一对多的级联。

值得注意的是,MyBatis没有多对多级联,因为多对多级联比较复杂,使用困难,而且可以通过两个一对多级联进行替换,所以MyBatis不支持多对多级联了。

7.2 实例

<mapper
	namespace="com.hys.mybatis.example3.mapper.EmployeeMapper">
	<resultMap type="com.hys.mybatis.example3.pojo.Employee"
		id="employee">
		<id property="id" column="id" />
		<result property="realName" column="real_name" />
		<result property="sex" column="sex"
			typeHandler="com.hys.mybatis.example3.typehandler.SexTypeHandler" />
		<result property="birthday" column="birthday" />
		<result property="mobile" column="mobile" />
		<result property="email" column="email" />
		<result property="position" column="position" />
		<result property="note" column="note" />
		<association property="workCard" column="id"
			select="com.hys.mybatis.example3.mapper.WorkCardMapper.getWorkCardByEmpId" />
		<collection property="employeeTaskList" column="id"
			select="com.hys.mybatis.example3.mapper.EmployeeTaskMapper.getEmployeeTaskByEmpId" />
		<discriminator javaType="long" column="sex">
			<case value="1" resultMap="maleHealthFormMapper" />
			<case value="2" resultMap="femaleHealthFormMapper" />
		</discriminator>
	</resultMap>

	<resultMap
		type="com.hys.mybatis.example3.pojo.FemaleEmployee"
		id="femaleHealthFormMapper" extends="employee">
		<association property="femaleHealthForm" column="id"
			select="com.hys.mybatis.example3.mapper.FemaleHealthFormMapper.getFemaleHealthForm" />
	</resultMap>


	<resultMap type="com.hys.mybatis.example3.pojo.MaleEmployee"
		id="maleHealthFormMapper" extends="employee">
		<association property="maleHealthForm" column="id"
			select="com.hys.mybatis.example3.mapper.MaleHealthFormMapper.getMaleHealthForm" />
	</resultMap>

	<select id="getEmployee" parameterType="long"
		resultMap="employee">
		select id,real_name as
		realName,sex,birthday,mobile,email,position,note from t_employee where
		id=#{id}
	</select>
</mapper>
  • association元素:代表着一对一级联的开始。property属性代表映射到POJO属性上。select配置是命名空间+SQL id的形式,这样便可以指向对应Mapper的SQL,MyBatis就会通过对应的SQL将数据查询回来。column代表SQL的列,用作参数传递给select属性制定的SQL,如果是多个参数,则需要用逗号隔开。
  • collection元素:一对多级联,其select元素指向SQL,将通过column制定的SQL字段作为参数进行传递,然后将结果返回给雇员POJO的属性employeeTaskList。
  • discriminator元素:鉴别器,它的属性column代表使用哪个字段进行鉴别,这里的是sex,而它的子元素case,则用于进行区分,类似于Java的switch...case...语句。而resultMap属性表示采用哪个ResultMap去映射,比如sex=1,则使用maleHealthFormMapper进行映射。

id为employee的resultMap,被maleHealthFormMapper和femaleHealthFormMapper通过extends元素继承。从类的关系而言,它们也是这样的继承关系,而maleHealthFormMapper和femaleHealthFormMapper都会通过association元素来执行对应关联的字段和SQL。

7.3 N+1问题

级联会引发性能问题,比如作为一个雇员的管理者,他只想看到员工信息和员工任务信息,那么体检表和工牌的信息就是多余的,就会使数据库多执行几条毫无意义的SQL。如果需要在雇员信息系统里加入一个关联信息,那么它在默认情况下会执行SQL取出数据,而真实的需求往往只要完成雇员和雇员人物表的级联就可以了,不需要把所有信息都加载进来,因为有些信息并不常用,加载它们会多执行几条毫无用处的SQL,导致数据库资源的损耗和系统性能的下降。

假设现在有N个关联关系完成了级联,那么只要再加入一个关联关系,就变成了N+1个级联,所有的级联SQL都会被执行,显然会有很多并不是我们关心的数据被取出,这样会造成很大的资源浪费,这就是N+1问题,尤其是在哪些需要高性能的互联网系统中,这往往是不被允许的。

为了应对N+1问题,MyBatis提供了延迟加载功能,即再一开始取雇员信息时,并不需要将工牌表、体检表、任务表的记录取出,而是只将雇员信息和雇员任务表的信息取出。当我们通过雇员POJO访问工牌表、体检表和任务表的记录时才通过对应的SQL取出。

7.4 延迟加载

MyBatis支持延迟加载,我们希望一次性把常用的级联数据通过SQL直接查询出来,而对于那些不常用的级联数据不要取出,而是等待要用时才取出,这些不常用的级联数据可以采用了延迟加载的功能。

在MyBatis的setting配置中存在两个元素可以配置级联,如下表所示:

延迟加载的配置项
配置项 作用 配置选项说明 默认值
lazyLoadingEnabled 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。在特定关联关系中,可通过设置fetchType属性来覆盖该项的开关状态 true|false false
aggressiveLazyLoading 当启用时,对任意延迟属性的调用会使带有延迟加载属性的对象完整加载;反之,则每种属性按需加载 true|false 版本3.4.1(包含)之前为true,之后为false

选项lazyLoadingEnabled决定是否开启延迟加载,而选项aggressiveLazyLoading则控制是否采用层级加载,但是它们都是全局性的配置,并不能解决我们的需求。加载雇员信息时,只加载雇员任务信息,因为层级加载会把工牌信息也加载进来。为了处理这个问题,在MyBatis种使用fetchType属性,它可以处理全局定义无法处理的问题,进行自定义。fetchType出现在级联元素(association、collection,注意,discriminator没有这个属性可配置)中,它存在着两个值:

  • eager:获得当前POJO后立即加载对应的数据。
  • lazy:获得当前POJO后延迟加载对应的数据。
<collection property="employeeTaskList" column="id"
			fetchType="eager"
			select="com.hys.mybatis.example3.mapper.EmployeeTaskMapper.getEmployeeTaskByEmpId" />

fetchType属性会忽略全局配置项lazyLoadingEnabled和aggressiveLazyLoading。


8 缓存

在MyBatis中允许使用缓存,缓存一般都放置在可高速读/写的存储器上,比如服务器的内存,它能够有效提高系统的性能。因为数据库在大部分场景下是把存储在磁盘上的数据索引出来。从硬件的角度分析,索引磁盘是一个较为缓慢的过程,读取内存或者高速缓存处理器的速度要比读取磁盘快得多,其速度是读取磁盘的几十倍到上百倍,但是内存和高速缓存处理器的空间有限,所以一般只会把那些常用且命中率高的数据缓存起来,以便将来使用,而不缓存那些不常用且命中率低的数据缓存。因为命中率低,最后还是要在磁盘内查找,并不能有效提高性能。

MyBatis分为一级缓存和二级缓存,同时也可以配置关于缓存的设置。

8.1 一级缓存

一级缓存实在SqlSession上的缓存,二级缓存实在SqlSessionFactory上的缓存。默认情况下,也就是没有任何配置的情况下,MyBatis系统会开启一级缓存,也就是对于SqlSession层面的缓存,这个缓存不需要POJO对象可序列化(实现java.io.Serializable接口)。

        SqlSession sqlSession = null;
        Logger logger = Logger.getLogger(Test.class);
        try {
            sqlSession = SqlSessionFactoryUtils.openSqlSession();
            RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
            Role role1 = roleMapper.getRole(1L);
            logger.info("再获取一次POJO...");
            Role role2 = roleMapper.getRole(1L);
            sqlSession.commit();
        } catch (Exception e) {
            logger.info(e);
        } finally {
            if (sqlSession != null) {
                sqlSession.close();
            }
        }

当在同一sqlSession下调用两次getRole方法时,通过查看相关日志可知,实际只有一条SQL被执行,其原因是代码使用了同一个SqlSession对象获取数据。当一个SqlSession第一次通过SQL和参数获取对象后,它就会将其缓存起来,如果下次的SQL和参数都没有发生变化,并且缓存没有超时或者需要刷新时,那么它就会从缓存中获取数据,而不是通过SQL获取了。

        SqlSession sqlSession1 = null;
        SqlSession sqlSession2 = null;
        Logger logger = Logger.getLogger(Test.class);
        try {
            sqlSession1 = SqlSessionFactoryUtils.openSqlSession();
            sqlSession2 = SqlSessionFactoryUtils.openSqlSession();
            RoleMapper roleMapper1 = sqlSession1.getMapper(RoleMapper.class);
            Role role1 = roleMapper1.getRole(1L);
            //需要提交,如果是一级缓存,MyBatis才会缓存对象到SqlSessionFactory层面
            sqlSession1.commit();
            logger.info("不同sqlSession再获取一次POJO...");
            RoleMapper roleMapper2 = sqlSession2.getMapper(RoleMapper.class);
            Role role2 = roleMapper2.getRole(1L);
            //需要提交,MyBatis才缓存对象到SqlSessionFactory
            sqlSession2.commit();
        } catch (Exception e) {
            logger.info(e);
        } finally {
            if (sqlSession1 != null) {
                sqlSession1.close();
            }
            if (sqlSession2 != null) {
                sqlSession2.close();
            }
        }

注意commit()方法的使用,如果不进行commit,是不会有一级缓存存在的。运行上述代码后得出的日志可以看出,SQL被执行了两次,这说明了一级缓存是在SqlSession层面的,对于不同的SqlSession对象是不能共享的。

8.2 二级缓存

为了使SqlSession对象之间共享相同的缓存,有时候需要开启二级缓存,开启二级缓存很简单,只要在映射文件(RoleMapper.xml)上加入代码:

<cache />

这个时候MyBatis会序列化和反序列化对应的POJO,也就要求POJO是一个可序列化的对象,那么它就必须实现java.io.Serializable接口。如果没有实现,MyBatis将会抛出异常,导致程序运行错误。


9 存储过程

存储过程是数据库的一个概念,它时数据库预先编译好,放在数据库内存中的一个程序片段,所以具备性能高,可重复使用的特性。它定义了3种类型的参数:输入参数、输出参数、输入输出参数。

  • 输入参数:是外界给的存储过程参数,在Java互联网中,也就是互联网系统给它的参数。
  • 输出参数:是存储过程经过计算返回给程序的结果参数。
  • 输入输出参数:是一开始作为参数传递给存储过程,而存储过程修改后将其返回的参数,比如那些商品的库存就是这样的。

对于返回结果而言,一些常用的简易类型,比如整形、字符型OUT或者INOUT参数是Java程序比较好处理的,而存储过程还可能返回游标类型的参数,这需要我们处理,不过在MyBatis中,这些都可以轻松完成。

9.1 IN和OUT参数存储过程

在Mysql中创建如下存储过程:

CREATE PROCEDURE count_role ( IN p_role_name VARCHAR ( 20 ), OUT count_total INT, OUT exec_date date ) BEGIN
SELECT
	count( 1 ) INTO count_total 
FROM
	t_role 
WHERE
	role_name LIKE concat( '%', p_role_name, '%' );
SELECT CURRENT_DATE INTO
	exec_date;
END;

这样就创建了这个存储过程。为了使用它,要设计一个POJO——PdCountRoleParams,如下:

package com.hys.mybatis.example3.param;

import java.util.Date;

public class PdCountRoleParams {

    private String roleName;
    private int    total;
    private Date   execDate;

    public String getRoleName() {
        return roleName;
    }

    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }

    public int getTotal() {
        return total;
    }

    public void setTotal(int total) {
        this.total = total;
    }

    public Date getExecDate() {
        return execDate;
    }

    public void setExecDate(Date execDate) {
        this.execDate = execDate;
    }
}

映射XML:

        <select id="countRole"
		parameterType="com.hys.mybatis.example3.param.PdCountRoleParams"
		statementType="CALLABLE">
		{call
		count_role(#{roleName,mode=IN,jdbcType=VARCHAR},#{total,mode=OUT,jdbcType=INTEGER},#{execDate,mode=OUT,jdbcType=DATE})}
	</select>

测试类:

        PdCountRoleParams params = new PdCountRoleParams();
        SqlSession sqlSession = null;
        try {
            sqlSession = SqlSessionFactoryUtils.openSqlSession();
            RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
            params.setRoleName("Robert Hou");
            roleMapper.countRole(params);
            System.out.println(params.getTotal());
            System.out.println(params.getExecDate());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (sqlSession != null) {
                sqlSession.close();
            }
        }

运行结果:

log4j:WARN No appenders could be found for logger (org.apache.ibatis.logging.LogFactory).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
1
Sun Jul 22 08:00:00 CST 2018

9.2 游标的使用

在实际应用中,除了使用简易的输入输出参数,有时候也可能使用游标,MyBatis也对存储过程的游标提供了支持。如果把jdbcType声明为CURSOR,那么它就会使用ResultSet对象处理对应的结果,只要设置映射关系,MyBatis就会把结果集映射出来(具体代码实现可以查看其他文章,本文不再赘述)。

参考资料:[1]杨开振 周吉文 梁华辉 谭茂华.Java EE 互联网轻量级框架整合开发:SSM框架(Spring MVC+Spring+MyBatis)和Redis实现[M].北京:电子工业出版社,2017.7

猜你喜欢

转载自blog.csdn.net/weixin_30342639/article/details/81150845