【Java EE】映射器

映射器

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

  • 面对复杂性,SQL会显得无力
  • 注解的可读性较差
  • 功能上注解丢失了XML上下文相互引用的功能。

概述

映射器的配置元素主要有:select、insert、update、delete、sql、resultMap、cache、cache-ref。

select元素——查询语句

在映射器中select元素代表SQL的select语句,用于查询。select元素的配置有id、parameterType、resultMap、flushCache、useCache、timeout、fetchSize、statementType、resultSetType、databaseId、resultOrdered、resultSets。
实际工作中用的最多的是id、parameterType、resultType、resultMap,如果要设置缓存,还会使用到flushCache、useCache,其他的都是不常用的元素。

简单的select元素的应用

一个简单的select元素应用:

<select id="countUserByFirstName" parameterType="string" resultType="int">
	select count(*) total from t_user where user_name like concat(#{firstName},'%')
</select>

其中元素的含义是:

  • id配合Mapper的全限定名,联合成为一个唯一的标识,用于标识这条SQL
  • parameterType标识这条SQL接受的参数类型
  • resultType标识这条SQL返回的结果类型
  • #{firstName}是被传递进去的参数

还需要给一个接口方法程序才能运行起来:

public Integer countUserByFirstName(String firstName);

自动映射和驼峰映射

MyBatis提供了自动映射的功能,在默认的情况下,自动映射功能是开启的。
在setting元素中有两个可以配置的选项autoMappingBehavior和mapUnderscoreToCamelCase,它们是控制自动映射和驼峰映射的开关。一般自动映射会用的多一些。
配置自动映射的autoMappingBehavior选项的取值范围是:

  • NONE,不进行自动映射
  • PARTIAL,默认值,只对没有嵌套结果集进行自动映射
  • FULL,对所有的结果集进行自动映射,包括嵌套结果集。

为了实现自动映射,首先要给出一个POJO:

public class Role {
	private Long id;
	private String roleName;
	private String note;

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	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;
	}
}

如果编写的SQL列名和属性名称保持一致,那么就会形成自动映射,如下:

<select id="getRole" parameterType="long" resultType="com.learn.ssm.pojo.Role">
	select id, role_name as roleName, note from t_role where id = #{id}
</select>

如果系统都严格按照驼峰命名法,那么只需要在配置项把mapUnderscoreToCamelCase设置为true即可,然后修改SQL语句:

select id, role_name, note from t_role where id = #{id}

MyBatis会严格按照驼峰命名法的方式做自动映射,但是降低了灵活性。
自动映射和驼峰映射都建立在SQL列名与POJO属性名的映射关系上。

传递多个参数

1. 使用map接口传递参数
在MyBatis中允许map接口通过键值对传递多个参数。把接口方法定义为:

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

使用它在SQL中设置对应的参数:

<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的键,即:

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

该方法用的不多,主要在于:map是一个键值对应的集合,使用者要通过阅读它的键,才能明白其作用;其次,使用map不能限定其传递的数据类型,因此业务性质不强,可读性差。
2. 使用注解传递多个参数
MyBatis为开发者提供了一个注解@Param(org.apache.ibatis.annotations.Param),可以通过它去定义映射器的参数名称,使用它可以得到更好的可读性。接口方法如下:

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

然后修改映射文件代码:

<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属性。但是这种方法只适合参数小于5个的情况。
3. 通过JavaBean传递多个参数
先定义一个参数的POJO:

public class RoleParams{
	private String roleName;
	private String note;
	/*setter and getter*/
}

定义接口方法:

public List<Role> findRolesByBean(RoleParams roleParam);

修改映射文件:

<select id="findRolesByBean" parameterType = "com.learn.ssm.param.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>

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

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

4. 混合使用
混合使用几种方法来传递参数,例如支持分页,分页的POJO实现如下:

public class PageParams{
	private int start;
	private int limit;
	/* setter and getter*/
	......
}

接口设计如下:

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

修改映射文件:

<select id="findByMix" 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>

5. 总结

  • 使用map传递参数导致了业务可读性的丧失
  • 使用@Param注解传递了多个参数,受到参数个数(n)的影响
  • 当参数个数多于5个时,推荐使用JavaBean方式
  • 对于使用混合参数,要明确参数的合理性

使用resultMap映射结果集

为了支持复杂的映射,select元素提供了resultMap属性。先定义resultMap属性。如下所示:

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

	<select id="getRoleUserResultMap" parameterType="long" resultMap="roleMap">
		select id, role_name, note from t_role where id = #{id}
	</select>
</mapper>

其中:

  • resultMap元素定义了一个roleMap,id代表它的标识,type代表使用哪个类作为其映射的类,可以是别名或者全限定名。
  • 子元素id代表resultMap的主键,而result代表其属性,column代表SQL的列名
  • 在select元素中的属性resultMap制定了采用哪个resultMap作为其映射规则。

分页参数RowBounds

MyBatis不仅支持分页,还内置了一个专门处理分页的类——RowBounds。源码如下:

public class RowBounds {

  public static final int NO_ROW_OFFSET = 0;
  public static final int NO_ROW_LIMIT = Integer.MAX_VALUE;
  public static final RowBounds DEFAULT = new RowBounds();

  private final int offset;
  private final 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;
  }

  public int getOffset() {
    return offset;
  }

  public int getLimit() {
    return limit;
  }

}

offset是偏移量,即从第几行开始读取数据。limit是限制条数。
使用这个类只要给接口增加一个RowBounds参数即可:

public List<Role> findByRowBounds(@Param("roleName") String rolename, @Params("note") String note, RowBounds rowBounds);

而映射文件中不需要RowBounds的内容,MyBatis会自动识别它,据此进行分页。具体如下:

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

测试代码如下:

SqlSession sqlSession = null;
	try {
		sqlSession = SqlSessionFactoryUtils.openSqlSession();
		RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
		RowBounds rowBounds = new RowBounds(0,20);
		List<Role> roleList = roleMapper.findByRowBounds("role_name","note",rowBounds);
		System.err.println(roleList.size());
	} catch(Exception ex) {
		ex.printStackTrace();
	} finally {
		if (sqlSession != null) {
			sqlSession.close();
		}
}

注意,RowBounds分页只能运用在一些小数据量的查询。RowBounds分页的原理是执行SQL的查询,按照偏移量和限制条数返回查询结果,所以对于大量的数据查询并不适用。

insert元素——插入语句

概述

MyBatis中insert语句可以配置以下属性:id、parameterType、flushCache、timeout、statementType、useGenerateKeys、keyColumn、databaseId。
MyBatis在执行完一条insert语句后,会返回一个整数标识其影响记录数。

简单的insert语句的应用

写一个SQL插入角色,这是一条最简单的插入语句:

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

说明:

  • id标识出这条SQL
  • parameterType代表传入参数类型
    没有配置的属性将采用默认值。

主键回填

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的属性去匹配这个主键。

自定义主键

MyBatis主要依赖于selectKey元素进行支持自定义键值的生成规则。代码如下:

<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>

keyProperty指定了采用哪个属性作为POJO的主键,resultType告诉MyBatis将返回一个long型的结果集,order设置为BEFORE,说明它将于当前定义的SQL前执行。如果有一些特殊需要,可以把它设置为AFTER,这样就会在插入语句后执行了。

update元素和delete元素

update元素和delete元素与insert的属性差不多,执行完之后会返回一个整数,标识该SQL语句影响了数据库的记录行数。

<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>

sql元素

sql元素的作用在于可以定义一条SQL的一部分,方便后面的SQL引用它。例如:

<mapper namespace="com.ssm.chapter5.mapper.RoleMapper">
	<resultMap id="roleMap" type="role">
		<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" resultType="roleMap">
		select <include refid = "roleCols" /> from t_role where id = #{id}
	</select>

	<insert id = "insertRole" parameterType="role">
	<selectKey keyProperty= "id" resultType = "long" order="BEFORE" statementType="PREPARED">
		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>
</mapper>

通过sql元素进行了定义,就可以通过include元素引入到各个SQL中了。
sql元素支持变量传递。

	<sql id = "roleCols">
		${alias}.id, ${alias}.role_name, ${alias}.note
	<sql>
	<select id="getRole" parameterType="long" resultType="roleMap">
		select <include refid = "roleCols">
			<property name="alias" value="r" />
		</include>
		from t_role where id = #{id}
	</select>

参数

概述

MyBatis跟根据javaType和jdbcType去检测使用哪个typeHandler。如果是一个没有注册的类型,那么就会发生异常。此时可以自定义typeHandler,通过类似的办法指定,就不会抛出异常了。例如:

#{age, javaType=int, jdbcType=NUMERIC, typeHandler=MyTypeHandler}

MyBatis也提供了一些对控制数值的精度支持,类似于:

#{width, javaType=double, jdbcType=NUMERIC, numericScale=2}

存储过程参数文件

MyBatis对存储过程也进行了支持,在存储过程中存在:输入(IN)参数,输出(OUT)参数和输入输出(INOUT)参数3种类型。输入参数是外界需要传递给存储过程的;输出参数是存储过程经过处理后返回的;输入输出参数是一方面需要外界可以传递给它,另一方面是在最后存储过程种也会将它返回给调用者。
对于简单的输出参数(比如INT、VARCHAR、DECIMAL)可以使用POJO通过映射来完成。存储过程的参数类型有3种:

#{id, mode = IN}
#{id, mode = OUT}
#{id, mode = INOUT}

特殊字符串的替换和处理(#和$)

在MyBatis种,构建动态列名常常要传递类似于字符串的columns="col1, col2, col3, …"给SQL,让其组装成为SQL语句。如果不想被MyBatis像处理普通参数一样把它设为“ col1, col2, col3, …”那么可以写成select ${columns} from t_tablename, 这样MyBatis就不会转移columns。但是这样对SQL而言是不安全的。

resultMap元素

resultMap的作用是定义映射规则、级联的更新、定制类型转化器等。resultMap定义的主要是一个结果集的映射关系,也就是SQL到java Bean的映射关系定义,它也支持级联等特性。

resultMap元素的构成

resultMap元素的子元素:

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

其中constructor元素用于配置构造方法。一个POJO可能不存在没有参数的构造防范,可以使用constructor进行配置。示例如下:

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

id元素表示哪个列是主键,允许多个主键,多个主键则成为联合主键。result是配置POJO到SQL列名的映射关系。result元素和idArg元素的属性有:property、column、javaType、jdbcType、typeHandler。此外还有association、collection和discriminator这些元素。一条查询SQL执行后,就会返回结果,而结果可以使用map存储,也可以使用POJO存储。

使用map存储过程集

一般而言,任何select语句都可以使用map存储。即:

<select id="findColorByNote" parameterType="string" resultType="map">
	select id, color,note from t_color where note like concat('%', #{note}, '%')
</select>

但是更多的时候推荐使用POJO方式。

使用POJO存储结果集

POJO是最常用的方式,一方面可以使用自动映射,有时候需要更为复杂的映射或者级联,这个时候还可以使用select语句的resultMap属性配置映射集合,只是使用前要配置类似的resultMap。如下:

<resultMap id="roleResultMap" type="com.learn.ssm.pojo.Role">
	<id property = "id" column="id"/>
	<result property="roleName" column = "rolw_name"/>
	<result property="note" column="note">
</resultMap>

type代表需要映射的POJO。配置映射文件:

<select parameterType="long" id="getRole" resultMap="roleResultMap">
	select id, role_name, note from t_role where id=#{id}
</select>

SQL语句的列名和roleResultMap的column是一一对应的,使用XML配置的结果集,还可以配置typeHandler、javaType、jdbcType等,但是配置了

级联

级联是resultMap中的配置,有一对多和一对一的级联。在MyBatis中还有一种被称为鉴别器的级联,它是一种可以选择具体实现类的级联。
级联不是必须的,级联的好处是获取关联数据十分便捷,缺点是级联过多影响系统性能。

MyBatis中的级联

MyBatis的级联分为3种:

  • 鉴别器(discriminator):根据某些条件决定采用具体实现类级联的方案
  • 一对一(association)
  • 一对多(collection)

注意: MyBatis没有多对多级联。将多对多关系转换为两个一对多关系进行替换。
级联模型建表SQL:

DROP TABLE IF EXISTS t_female_health_form;
DROP TABLE IF EXISTS t_male_health_form;
DROP TABLE IF EXISTS t_task;
DROP TABLE IF EXISTS t_work_card;
DROP TABLE IF EXISTS t_employee_task;
DROP TABLE IF EXISTS t_employee;

/*==============================================================*/
/* Table: t_employee                                            */
/*==============================================================*/
CREATE TABLE t_employee
(
   id                   INT(12) NOT NULL AUTO_INCREMENT,
   real_name            VARCHAR(60) NOT NULL,
   sex                  INT(2) NOT NULL COMMENT '1 - ÄÐ 
            0 -Å®',
   birthday             DATE NOT NULL,
   mobile               VARCHAR(20) NOT NULL,
   email                VARCHAR(60) NOT NULL,
   POSITION             VARCHAR(20) NOT NULL,
   note                 VARCHAR(256),
   PRIMARY KEY (id)
);

/*==============================================================*/
/* Table: t_employee_task                                       */
/*==============================================================*/
CREATE TABLE t_employee_task
(
   id                   INT(12) NOT NULL auto_increment,
   emp_id               INT(12) NOT NULL,
   task_id              INT(12) NOT NULL,
   task_name            VARCHAR(60) NOT NULL,
   note                 VARCHAR(256),
   PRIMARY KEY (id)
);

/*==============================================================*/
/* Table: t_female_health_form                                  */
/*==============================================================*/
CREATE TABLE t_female_health_form
(
   id                   INT(12) NOT NULL AUTO_INCREMENT,
   emp_id               INT(12) NOT NULL,
   heart                VARCHAR(64) NOT NULL,
   liver                VARCHAR(64) NOT NULL,
   spleen               VARCHAR(64) NOT NULL,
   lung                 VARCHAR(64) NOT NULL,
   kidney               VARCHAR(64) NOT NULL,
   uterus               VARCHAR(64) NOT NULL,
   note                 VARCHAR(256),
   PRIMARY KEY (id)
);

/*==============================================================*/
/* Table: t_male_health_form                                    */
/*==============================================================*/
CREATE TABLE t_male_health_form
(
   id                   INT(12) NOT NULL AUTO_INCREMENT,
   emp_id               INT(12) NOT NULL,
   heart                VARCHAR(64) NOT NULL,
   liver                VARCHAR(64) NOT NULL,
   spleen               VARCHAR(64) NOT NULL,
   lung                 VARCHAR(64) NOT NULL,
   kidney               VARCHAR(64) NOT NULL,
   prostate             VARCHAR(64) NOT NULL,
   note                 VARCHAR(256),
   PRIMARY KEY (id)
);

/*==============================================================*/
/* Table: t_task                                                */
/*==============================================================*/
CREATE TABLE t_task
(
   id                   INT(12) NOT NULL auto_increment,
   title                VARCHAR(60) NOT NULL,
   context              VARCHAR(256) NOT NULL,
   note                 VARCHAR(256),
   PRIMARY KEY (id)
);

/*==============================================================*/
/* Table: t_work_card                                           */
/*==============================================================*/
CREATE TABLE t_work_card
(
   id                   INT(12) NOT NULL AUTO_INCREMENT,
   emp_id               INT(12) NOT NULL,
   real_name            VARCHAR(60) NOT NULL,
   department           VARCHAR(20) NOT NULL,
   mobile               VARCHAR(20) NOT NULL,
   POSITION             VARCHAR(30) NOT NULL,
   note                 VARCHAR(256),
   PRIMARY KEY (id)
);

ALTER TABLE t_employee_task ADD CONSTRAINT FK_Reference_4 FOREIGN KEY (emp_id)
      REFERENCES t_employee (id) ON DELETE RESTRICT ON UPDATE RESTRICT;
ALTER TABLE t_employee_task ADD CONSTRAINT FK_Reference_8 FOREIGN KEY (task_id)
      REFERENCES t_task (id) ON DELETE RESTRICT ON UPDATE RESTRICT;
ALTER TABLE t_female_health_form ADD CONSTRAINT FK_Reference_5 FOREIGN KEY (emp_id)
      REFERENCES t_employee (id) ON DELETE RESTRICT ON UPDATE RESTRICT;
ALTER TABLE t_male_health_form ADD CONSTRAINT FK_Reference_6 FOREIGN KEY (emp_id)
      REFERENCES t_employee (id) ON DELETE RESTRICT ON UPDATE RESTRICT;
ALTER TABLE t_work_card ADD CONSTRAINT FK_Reference_7 FOREIGN KEY (emp_id)
      REFERENCES t_employee (id) ON DELETE RESTRICT ON UPDATE RESTRICT;

建立POJO

// 体检表父表
public class HealthForm {
	
	private Long id;
	private Long empId;
	private String heart;
	private String liver;
	private String spleen;
	private String lung;
	private String kidney;
	private String note;
	/* setter and getter */	
}
// 女性体检表
public class FemaleHealthForm extends HealthForm {

	private String uterus;
	/* setter and getter */	
}
// 男性提交表
public class MaleHealthForm extends HealthForm {
	
	private String prostate;
	/*setter and getter */
}
// 工牌表
public class WorkCard {
	private Long id;
	private Long empId;
	private String realName;
	private String department;
	private String mobile;
	private String position;
	private String note;
	/*setter and getter */
}
// 任务表
public class Task {
	private Long id;
	private String title;
	private String context;
	private String note;
	/*setter and getter */
}
// 雇员任务POJO
public class EmployeeTask {
	private Long id;
	private Long empId;
	private Task task = null;
	private String taskName;
	private String note;
	/*setter and getter */
}
// 雇员父类
public class Employee {

	private Long id;
	private String realName;
	private SexEnum sex = null;
	private Date birthday;
	private String mobile;
	private String email;
	private String position;
	private String note;
    //工牌按一对一级联
	private WorkCard workCard;
	//雇员任务,一对多级联
	private List<EmployeeTask> employeeTaskList = null;
	/*setter and getter */
}
// 男雇员类
public class MaleEmployee extends Employee {

	private MaleHealthForm maleHealthForm = null;
	/*setter and getter */
}
// 女雇员类
public class FemaleEmployee extends Employee {

	private FemaleHealthForm femaleHealthForm = null;
	/*setter and getter */
}

配置映射文件

配置映射文件是级联的核心内容。

<?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="com.ssm.chapter5.mapper.TaskMapper">
    <select id="getTask" parameterType="long" resultType="com.ssm.chapter5.pojo.Task">
        select id, title, context, note from t_task where id = #{id}
    </select>
</mapper>
<?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="com.ssm.chapter5.mapper.WorkCardMapper">
    <select id="getWorkCardByEmpId" parameterType="long" resultType="com.ssm.chapter5.pojo.WorkCard">
        SELECT  id, emp_id as empId, real_name as realName, department, mobile, position, note FROM t_work_card
        where emp_id = #{empId} 
    </select>
</mapper>

雇员任务是一对一的级联关系,通过task_id级联,使用association元素。

<?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="com.ssm.chapter5.mapper.EmployeeTaskMapper">

    <resultMap type="com.ssm.chapter5.pojo.EmployeeTask" id="EmployeeTaskMap">
        <id column="id" property="id"/>
        <result column="emp_id" property="empId"/>
        <result column="task_name" property="taskName"/>
        <result column="note" property="note"/>
        <association property="task" column="task_id"
            select="com.ssm.chapter5.mapper.TaskMapper.getTask"/>
    </resultMap>
    
    <select id="getEmployeeTaskByEmpId" resultMap="EmployeeTaskMap">
        select id, emp_id, task_name, task_id, note from t_employee_task 
        where emp_id = #{empId}
    </select>
</mapper>

select配置是命名空间+SQL id的形式,这样便可以指向对应Mapper的SQL,MyBatis就会通过对应的SQL将数据查询回来。column代表SQL的列,用作参数传递给select属性制定的SQL,如果是多个参数,则需要用逗号隔开。

<?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="com.ssm.chapter5.mapper.MaleHealthFormMapper">
	<select id="getMaleHealthForm" parameterType="long"
		resultType="com.ssm.chapter5.pojo.MaleHealthForm">
		select id, heart, liver, spleen, lung, kidney, prostate, note from
		t_male_health_form where emp_id = #{id}
	</select>
</mapper>
<?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="com.ssm.chapter5.mapper.FemaleHealthFormMapper">
	<select id="getFemaleHealthForm" parameterType="long"
		resultType="com.ssm.chapter5.pojo.FemaleHealthForm">
		select id, heart, liver, spleen, lung, kidney, uterus, note from
		t_female_health_form where emp_id = #{id}
	</select>
</mapper>

这两个映射器都主要通过雇员编号找到对应体检表的记录,为雇员查询时提供查询体检表的SQL。

<?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="com.ssm.chapter5.mapper.EmployeeMapper">
	<resultMap type="com.ssm.chapter5.pojo.Employee" id="employee">
		<id column="id" property="id" />
		<result column="real_name" property="realName" />
		<result column="sex" property="sex"
			typeHandler="com.ssm.chapter5.typeHandler.SexTypeHandler" />
		<result column="birthday" property="birthday" />
		<result column="mobile" property="mobile" />
		<result column="email" property="email" />
		<result column="position" property="position" />
		<result column="note" property="note" />
		<association property="workCard" column="id"
			select="com.ssm.chapter5.mapper.WorkCardMapper.getWorkCardByEmpId" />
		<collection property="employeeTaskList" column="id"
			fetchType="eager"
			select="com.ssm.chapter5.mapper.EmployeeTaskMapper.getEmployeeTaskByEmpId" />
		<discriminator javaType="long" column="sex">
			<case value="1" resultMap="maleHealthFormMapper" />
			<case value="2" resultMap="femaleHealthFormMapper" />
		</discriminator>
	</resultMap>

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

	<resultMap type="com.ssm.chapter5.pojo.MaleEmployee" id="maleHealthFormMapper"
		extends="employee">
		<association property="maleHealthForm" column="id"
			select="com.ssm.chapter5.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>

	<resultMap id="employee2" type="com.ssm.chapter5.pojo.Employee">
		<id column="id" property="id" />
		<result column="real_name" property="realName" />
		<result column="sex" property="sex"
			typeHandler="com.ssm.chapter5.typeHandler.SexTypeHandler" />
		<result column="birthday" property="birthday" />
		<result column="mobile" property="mobile" />
		<result column="email" property="email" />
		<result column="position" property="position" />
		<association property="workCard" javaType="com.ssm.chapter5.pojo.WorkCard"
			column="id">
			<id column="wc_id" property="id" />
			<result column="id" property="empId" />
			<result column="wc_real_name" property="realName" />
			<result column="wc_department" property="department" />
			<result column="wc_mobile" property="mobile" />
			<result column="wc_position" property="position" />
			<result column="wc_note" property="note" />
		</association>
		<collection property="employeeTaskList" ofType="com.ssm.chapter5.pojo.EmployeeTask"
			column="id">
			<id column="et_id" property="id" />
			<result column="id" property="empId" />
			<result column="task_name" property="taskName" />
			<result column="note" property="note" />
			<association property="task" javaType="com.ssm.chapter5.pojo.Task"
				column="et_task_id">
				<id column="t_id" property="id" />
				<result column="t_title" property="title" />
				<result column="t_context" property="context" />
				<result column="t_note" property="note" />
			</association>
		</collection>
		<discriminator javaType="int" column="sex">
			<case value="1" resultMap="maleHealthFormMapper2" />
			<case value="2" resultMap="femaleHealthFormMapper2" />
		</discriminator>
	</resultMap>


	<resultMap type="com.ssm.chapter5.pojo.MaleEmployee" id="maleHealthFormMapper2"
		extends="employee2">
		<association property="maleHealthForm" column="id"
			javaType="com.ssm.chapter5.pojo.MaleHealthForm">
			<id column="h_id" property="id" />
			<result column="h_heart" property="heart" />
			<result column="h_liver" property="liver" />
			<result column="h_spleen" property="spleen" />
			<result column="h_lung" property="lung" />
			<result column="h_kidney" property="kidney" />
			<result column="h_prostate" property="prostate" />
			<result column="h_note" property="note" />
		</association>
	</resultMap>

	<resultMap type="com.ssm.chapter5.pojo.FemaleEmployee" id="femaleHealthFormMapper2"
		extends="employee">
		<association property="femaleHealthForm" column="id"
			javaType="com.ssm.chapter5.pojo.FemaleHealthForm">
			<id column="h_id" property="id" />
			<result column="h_heart" property="heart" />
			<result column="h_liver" property="liver" />
			<result column="h_spleen" property="spleen" />
			<result column="h_lung" property="lung" />
			<result column="h_kidney" property="kidney" />
			<result column="h_uterus" property="uterus" />
			<result column="h_note" property="note" />
		</association>
	</resultMap>

	<select id="getEmployee2" parameterType="long" resultMap="employee2">
		select
		emp.id, emp.real_name, emp.sex, emp.birthday,
		emp.mobile, emp.email,
		emp.position, emp.note,
		et.id as et_id, et.task_id as et_task_id,
		et.task_name as et_task_name,
		et.note as et_note,
		if (emp.sex = 1,
		mhf.id, fhf.id) as h_id,
		if (emp.sex = 1, mhf.heart, fhf.heart) as
		h_heart,
		if (emp.sex = 1, mhf.liver, fhf.liver) as h_liver,
		if (emp.sex
		= 1, mhf.spleen, fhf.spleen) as h_spleen,
		if (emp.sex = 1, mhf.lung,
		fhf.lung) as h_lung,
		if (emp.sex = 1, mhf.kidney, fhf.kidney) as
		h_kidney,
		if (emp.sex = 1, mhf.note, fhf.note) as h_note,
		mhf.prostate
		as h_prostate, fhf.uterus as h_uterus,
		wc.id wc_id, wc.real_name
		wc_real_name, wc.department wc_department,
		wc.mobile wc_mobile,
		wc.position wc_position, wc.note as wc_note,
		t.id as t_id, t.title as
		t_title, t.context as t_context, t.note as t_note
		from t_employee emp
		left join t_employee_task et on emp.id = et.emp_id
		left join
		t_female_health_form fhf on emp.id = fhf.emp_id
		left join
		t_male_health_form mhf on emp.id = mhf.emp_id
		left join t_work_card wc
		on emp.id = wc.emp_id
		left join t_task t on et.task_id = t.id
		where
		emp.id = #{id}
	</select>
</mapper>

N+1问题

假设现有N个关联关系完成了级联,那么只要再加入一个关联关系,就变成了N+1个级联,所有的级联SQL都会被执行,造成资源浪费,这就是N+1问题。
为了应对N+1问题,MyBatis提供了延迟加载功能。

延迟加载

在MyBatis的settings配置种存在两个元素可以配置级联,即lazyLoadingEnabled和aggressiveLazyLoading。前者是一个开关,决定开不开启延迟加载,默认值为false,后者是一个层级开关,当设置为true时,它是一个开启了层级开关的延迟加载。默认为false。配置示例如下:

<settings>
	<setting name="lazyLoadingEnabled" value="true"/>
	<setting name="aggressiveLazyLoading" value="true"/>
</settings>

选项lazyLoadingEnabled决定是否开启延迟加载,选项aggressiveLazyLoading控制是否采用层级加载,但是它们都是全局性的配置。在MyBatis中使用fetchType属性,可以处理全局定义无法处理的问题,进行自定义。
fetchType出现在级联元素(association、 collection, 注意 discriminator没有这个属性可配置)中,它存在两个值:

  • eager,获得当前POJO后立即加载对应的数据。
  • lazy,获得当前POJO后延迟加载对应的数据。

修改配置如下:

<collection property="employeeTaskList" column="id" fetchType="eager"
select = "com.learn.ssm.mapper.EmployeeTaskMapper.getEmployeeTaskByEmpId"
/>

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

另一种级联

MyBatis还提供了另一张级联方式,它是基于SQL表连接的基础,进行再次设计的。
先定义SQL语句,然后对复杂的SQL进行级联配置,在这个过程中:

  • 每一个级联元素(association、 discriminator、collection)中属性id的配置和POJO实体配置的id一一对应,形成级联。
  • 在级联元素中,association是通过javaType的定义去声明实体映射的,而collection则是使用ofType进行声明的。
  • discrimination元素定义使用何种具体的resultMap进行级联。
    但是这样仍然存在SQL比较复杂,配置项较多,而且一次性取数据造成内存浪费的问题。同时维护也比较困难。

多对多级联

多对多的问题往往会被拆分为两个一对多来处理。例如,用户和角色:

//角色POJO
public class Role2 {
	private Long id;
	private String roleName;
	private String note;
	// 关联用户信息,一对多关联
	private List<User2> userList;
	/* setter and getter */	
}
//用户POJO
public class User2 {
	private Long id;
	private String userName;
	private String realName;
	private SexEnum sex;
	private String moble;
	private String email;
	private String note;
	// 对角色一对多关联
	private List<Role2> roleList;
	/* setter and getter */	
}

注意,这里的两个List类型的属性是专门做一对多级联用的,使用collection元素去完成,得到两个Mapper。

<?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="com.ssm.chapter5.mapper2.RoleMapper2">
	<resultMap type="com.ssm.chapter5.pojo2.Role2" id="roleMapper">
		<id column="id" property="id" />
		<result column="role_name" property="roleName" />
		<result column="note" property="note" />
		<collection property="userList" column="id" fetchType="lazy"
			select="com.ssm.chapter5.mapper2.UserMapper2.findUserByRoleId" />
	</resultMap>

	<select id="getRole" parameterType="long" resultMap="roleMapper">
		select id, role_name, note from t_role where id = #{id}
	</select>

	<select id="findRoleByUserId" parameterType="long" resultMap="roleMapper">
		select r.id, r.role_name, r.note from t_role r, t_user_role ur
		where r.id = ur.role_id and ur.user_id = #{userId}
	</select>
</mapper>
<?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="com.ssm.chapter5.mapper2.UserMapper2">
	<resultMap type="com.ssm.chapter5.pojo2.User2" id="userMapper">
		<id column="id" property="id" />
		<result column="user_name" property="userName" />
		<result column="real_name" property="realName" />
		<result column="sex" property="sex"
			typeHandler="com.ssm.chapter5.typeHandler.SexTypeHandler" />
		<result column="mobile" property="moble" />
		<result column="email" property="email" />
		<result column="position" property="position" />
		<result column="note" property="note" />
		<collection property="roleList" column="id" fetchType="lazy"
			select="com.ssm.chapter5.mapper2.RoleMapper2.findRoleByUserId" />
	</resultMap>
	<select id="getUser" parameterType="long" resultMap="userMapper">
		select id, user_name, real_name, sex, moble, email, note from t_user where
		id =#{id}
	</select>
	<select id="findUserByRoleId" parameterType="long" resultMap="userMapper">
		select u.id, u.user_name, u.real_name, u.sex, u.moble, u.email, u.note
		from
		t_user u , t_user_role ur where u.id = ur.user_id and ur.role_id =#{roleId}
	</select>
</mapper>

使用fetchType设置为lazy,就能够进行延迟加载。

缓存

在Mybatis中允许使用缓存,缓存一般都放置在可高速读/写的存储器上。分为一级缓存和二级缓存,同时也可以配置关于缓存的设置。

一级缓存和二级缓存

一级缓存是在SqlSession上的缓存,二级缓存是在SqlSessionFactory上的缓存,默认情况下会开启一级缓存,这个不需要POJO对象可序列化。
注意在通过SqlSession获取对象的时候commit()方法的使用,如果不进行commit(),是不会有一级缓存存在的。
一级缓存是在SqlSession层面,对于不同的SqlSession对象是不能共享的。因此,要使其共享,则开启二级缓存,即只要在映射文件中加入代码:

<cache/>

这个时候MyBatis会序列化和反序列化对应的POJO,即要求POJO是一个可序列化的对象,那么就必须实现java.io.Serializable接口。

缓存配置项、自定义和引用

缓存要明确cache元素的配置项,有:blocking、readOnly、eviction、flushInterval、type、size。
对于自定义的缓存,只要实现类需要实现MyBatis的接口org.apache.ibatis.cache.Cache,即:

public interface Cache {

  /**
   * @return The identifier of this cache
   */
  String getId();

  /**
   * @param key Can be any object but usually it is a {@link CacheKey}
   * @param value The result of a select.
   */
  void putObject(Object key, Object value);

  /**
   * @param key The key
   * @return The object stored in the cache.
   */
  Object getObject(Object key);

  /**
   * As of 3.3.0 this method is only called during a rollback
   * for any previous value that was missing in the cache.
   * This lets any blocking cache to release the lock that
   * may have previously put on the key.
   * A blocking cache puts a lock when a value is null
   * and releases it when the value is back again.
   * This way other threads will wait for the value to be
   * available instead of hitting the database.
   *
   *
   * @param key The key
   * @return Not used
   */
  Object removeObject(Object key);

  /**
   * Clears this cache instance.
   */
  void clear();

  /**
   * Optional. This method is not called by the core.
   *
   * @return The number of elements stored in the cache (not its capacity).
   */
  int getSize();

  /**
   * Optional. As of 3.2.6 this method is no longer called by the core.
   * <p>
   * Any locking needed by the cache must be provided internally by the cache provider.
   *
   * @return A ReadWriteLock
   */
  default ReadWriteLock getReadWriteLock() {
    return null;
  }

}

现实中,可以使用Redis,MongoDB或者其他常用的缓存,假设存在一个Redis的缓存实现类com.learn.ssm.cache.RedisCache,那么可以这样配置:

<cache type="com.learn.ssm.cache.RedisCache">
	<property name="host" value="localhost"/>
</cache>

配置后,MyBatis会启用缓存,同时调用setHost(String host)方法,去设置配置的内容。另外,还可以自定义配置:

<select ... flushCache="false" useCache="true"/>
<insert ... flushCache="true"/>
<update ... flushCache="true"/>
<delete ... flushCache="true"/>

flushCache代表是否刷新缓存,useCache属性则是select特有的,表示是否需要使用缓存。

存储过程

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

IN和OUT参数存储过程

定义一个简单的存储过程:

create or replace
PROCEDURE count_role(
	p_role_name in varchar,
	count_total out int,
	exec_date out date
)
IS
BEGIN
select count(*) into count_total from t_role where role_name like '%'||p_role_name||'%';
select sysdate into exec_date from dual;
End;

设计一个POJO:

public class PdCountRoleParam{
	private String roleName;
	private int total,
	private Date execDate;
	/* setter and getter */
}

修改配置文件:

<select id="countRole" parameterType="com.learn.ssm.pojo.PdCountRoleParam" statementType="CALLABLE">
	{call count_role(
	#{roleName, mode=IN,jdbcType=VARCHAR},
	#{total, mode=OUT, jdbcType=INTEGER},
	#{execData, mode=OUT, jdbcType=DATE}
	)}
</select>

其中:

  • 指定statementType为CALLABLE,说明它是在使用存储过程
  • 定义parameterType为PdCountRoleParams参数
  • 在调度存储过程种放入参数对应的属性,并在在属性上通过mode设置了其输入或者输出参数,指定对应的jdbcType。

游标的使用

如果把jdbcType声明为CURSOR,那么它就会使用ResultSet对象处理对应的结果,只要设置映射关系,MyBatis就会把结果映射出来。为了使得ResultSet对应能够映射为POJO,设置resultMap为roleMap,如下所示,这样MyBatis就会采用配置的映射规则将其映射为POJO了。

<resultMap type="role" id="roleMap">
	<id property="id" column="id"/>
	<result property="roleName" column="role_name"/>
	<result property="note" column="note"/>
</resultMap>
<select id="countRole" parameterType="com.learn.ssm.pojo.PdCountRoleParam" statementType="CALLABLE">
	{call count_role(
	#{roleName, mode=IN,jdbcType=VARCHAR},
	#{total, mode=OUT, jdbcType=INTEGER},
	#{execData, mode=OUT, jdbcType=DATE},
	#{roleList,mode=OUT,jdbcType=CURSOR,javaType=ResultSet,resultMap=roleMap}
	)}
</select>
发布了279 篇原创文章 · 获赞 169 · 访问量 32万+

猜你喜欢

转载自blog.csdn.net/ARPOSPF/article/details/104401387