现在根据设计模型建立对应的pojo
首先看体检表,由于体检表分为男性和女性,因此可以先设计一个父类,然后通过继承的方式来完成pojo
package com.learn.ssm.chapter5.pojo;
//体检表父类
public abstract class HealthForm {
@Override
public String toString() {
return "HealthForm [id=" + id + ", empId=" + empId + ", heart=" + heart
+ ", liver=" + liver + ", spleen=" + spleen + ", lung=" + lung
+ ", kidney=" + kidney + ", note=" + note + "]";
}
private int id;
private int empId;
private String heart;
private String liver;
private String spleen;
private String lung;
private String kidney;
private String note;
女性体检表
//女性体检表
public class FemaleHealthForm extends HealthForm{
private String uterus;
public String getUterus() {
return uterus;
}
@Override
public String toString() {
return super.toString()+ "FemaleHealthForm [uterus=" + uterus + "]";
}
public void setUterus(String uterus) {
this.uterus = uterus;
}
男性体检表
//男性体检表
public class MaleHealthForm extends HealthForm {
@Override
public String toString() {
return super.toString()+ "MaleHealthForm [prostate=" + prostate + "]";
}
private String prostate;
public String getProstate() {
return prostate;
}
public void setProstate(String prostate) {
this.prostate = prostate;
}
接下来设计员工表,工牌表,和任务表的pojo
//工牌表
public class WorkCard {
private int id;
private int empId;
private String realName;
private String department;
private String mobile;
private String position;
private String note;
任务表
public class Task {
//任务表
private int id;//编号
@Override
public String toString() {
return "Task [id=" + id + ", title=" + title + ", context=" + context
+ ", note=" + note + "]";
}
private String title;//任务标题
private String context;//任务内容
private String note;//备注
还剩雇员表和雇员任务表,它们有一定的关联。先从雇员任务表下手,雇员任务表是通过任务编号(task_id)和任务一一对应。
public class EmployeeTask {
//雇员任务表
/**
* 雇员任务表示通过任务编号task_id来和任务表进行一一关联的,这里只考虑其自身和任务编号的关联
*
*/
private int id;
private int empId;
private Task task=null;
private String taskName;
private String note;
属性task是一个Task类的对象,由它进行关联任务信息。设置雇员表是关键。雇员根据性别分为男雇员和女雇员。它们会有不同体检表记录,但是无论男,女都有一个雇员类,它有两个子类,男雇员类和女雇员类。在mybatis中,有一个鉴别器,通过雇员的字段sex来判断决定使用哪一个具体的子类(MaleEmployee和FemaleEmployee)初始化对象,它与工牌表示一一对应的关联关系,对于雇员任务表示一对多的关系。
雇员类POJO
/***
* 雇员父类
* 雇员根据性别分为男雇员和女雇员,他们会有不同的体检表,但是都有一个父类表。有两个子类(MaleEmployee男雇员)(FemaleEmploee女雇员)
* @author Administrator
*
*/
public class Employee {
private int 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 employeeTaskList=null;
男雇员类
//男性雇员表
public class MaleEmployee extends Employee {
private MaleHealthForm maleHealthForm=null;
public MaleHealthForm getMaleHealthForm() {
return maleHealthForm;
}
public void setMaleHealthForm(MaleHealthForm maleHealthForm) {
this.maleHealthForm = maleHealthForm;
}
女雇员表POJO
public class FemaleEmployee extends Employee{
//女性雇员表包括体检表
private FemaleHealthForm femaleHealthForm=null;
public FemaleHealthForm getFemaleHealthForm() {
return femaleHealthForm;
}
@Override
public String toString() {
return "FemaleEmployee [femaleHealthForm=" + femaleHealthForm + "]";
}
public void setFemaleHealthForm(FemaleHealthForm femaleHealthForm) {
this.femaleHealthForm = femaleHealthForm;
}
MaleEmployee和FemaleEmployee都继承了Employee类,有着不同体检表。Employee类是通过了employeeTaskList属性和多个雇员任务进行一对多级联。而工牌表则是通过workcard来进行一对一级联。
这样就完成了所有的POJO的设计
配置映射文件:
配置映射文件是级联的核心内容,而对于Mapper对的接口就不再书里给出了,因为根据映射文件编写接口十分简单,从最简单的内容入手,最简单的内容无非是那些关联最少的POJO,根据图5-2所示,4个POJO中task和workcard是星湖独立的。所以他们的映射文件相对简单
TaskMapper.xml&TaskMapper
TaskMapper
import com.learn.ssm.chapter5.pojo.Task;
public interface TaskMapper {
public Task getTask(int id);
}
TaskMapper.xml
workcard.xml&workcardMapper
package com.learn.ssm.chapter5.mapper;
import com.learn.ssm.chapter5.pojo.WorkCard;
public interface WorkCardMapper {
public WorkCard getWorkCardByEmpId(int id );
}
这样就完成了两张表的映射文件。雇员任务表通过了任务编号(task_id)和任务表关联。这是一个一对一级联的关系。使用了association元素。雇员任务表一对一级联
雇员任务表一对一级联
这里重点讲解一下association的几种不同用法
第一种:
association的元素代表着一对一级联的开始,property属性代表映射到POJO属性上,select配置是命名空间+SQL id的形式,这样就可以指向对应的mapper的SQL。MyBatis就会通过对应的SQL将数据查询回来。column代表SQL的列,用作参数传递给select属性指定的SQL,如果是多个参数,则需要使用逗号隔开。
第二种:(不使用association标签的方式)
第三种:使用association标签+javaType属性
这种方法个人感觉跟第一种没有本质上的区别,还是一条sql语句对两张表进行关联查询,只不过在结果集映射的时候有一些不同,引入了association标签。可读性比较好,对象的结构关系相较于第一种方式来说更为清晰和明朗。
sql部分,与第一种无异:
在级联元素中,association中是通过javaType的定义去声明实体映射,可以看到在这种写法中,通过association标签明确指定了department对象的类型,然后在这个association的子标签中对department对象进行结果映射
而前面使用association标签+select属性
这种方法就有意思了。与前面两种写法有比较大的不同,使用association的select标签,可以将原本两表联查的一条sql语句拆分为两条简单的sql语句。个人以为搞出这种方式的原因就是要支持级联查询的懒加载吧,这样可以很好的提升数据库的性能,毕竟只有在用到关联对象相关属性的时候,才会执行第二步的查询操作。这部分内容等到后面了解其原理,看过源码后再回来详细说明,在此留一个根。
sql部分,这里就分两部分了。第一是在t_mployee_task表中,根据id查出对应的记录。第二步就是根据前一步中查出的task_id的值,在task表中查询对应的记录。注意这两个sql是分散在两个mapper.xml中的哈。
association标签中有两个重要的属性,select是用来指定这个对象怎么去查,而column属性则是从第一步的查询结果中找出select所需的查询参数。
再研究一下体检表,它能拆分为男性雇员表和女性雇员表,所以就有两个简单的映射器。
这两个映射器都是主要通过雇员编号找到对应的映射关系,为雇员查询是提供了查询的体检表SQL
现在创建雇员的映射关系
注意:
- associaation元素:对工牌进行一对一级联,这个在雇员任务表中已经分析过
- collection元素 一对多级联,其select元素指向SQL,将通过column制定的SQL字段作为参数进行传递,然后就将结果返回给雇员POJO的属性employeeTaskList
- discriminator元素,鉴别器,它的属性column代表着使用哪个字段来进行鉴别,这里的sex,而它的子元素case,则用于区分。类似于switch...case语句,而resultMap属性表示采用哪个ResultMap去映射,比如sex=1,则使用maleHealthFormMapper进行映射。没有合适的case,使用employee进行映射
测试代码:
public static void testGetEmployee() {
Logger logger=Logger.getLogger(chapter5Main.class);
SqlSession sqlSession = null;
try {
sqlSession = SqlSessionFactoryUtils.openSqlSession();
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
Employee employee = employeeMapper.getEmployee2(1);
// EmployeeTaskMapper employeeTaskMapper=sqlSession.getMapper(EmployeeTaskMapper.class);
// List employeeTask=employeeTaskMapper.getEmployeeTaskByEmpId(2);
// TaskMapper taskmapper=sqlSession.getMapper(TaskMapper.class);
// Task task=taskmapper.getTask(1);
// logger.info(employee.getEmployeeTaskList());
// System.out.println(employee.getEmployeeTaskList());
System.out.println(employee);
} catch(Exception ex) {
ex.printStackTrace();
} finally {
if (sqlSession != null) {
sqlSession.close();
}
}
}
延迟加载:
配置项 | 作用 | 配置选项说明 | 默认值 |
---|---|---|---|
lazyLoadingEnabled | 延迟加载的全局开关,当开启时,所有关联对象都会延迟加载,在特定关联关系中,可通过设置fetchType属性来覆盖该项的开关状态 | true\false | false |
aggressiveLazyLoading | 当启用时,对任意延迟属性的调用会使带有延迟加载属性的对象完整加载;反之,则每种属性按需加载 | true\false | 版本之前为true,之后为false |
在mybatisconfig.xml中添加如下代码:
选项lazyLoadingEnabled决定是否开启延迟加载,而选项aggressiveLazyLoading则控制是否采用层级加载,采用层级加载的话,所有关联的信息同个层级的都会被加载出来。比如查询雇员信息,属性中的task关联雇员任务表,属性workcard关联工卡等等,这些是处于同一个层级的情况下。
我们要加载雇员信息只加载雇员任务信息,但是因为层级加载会把工牌信息也加载进来,为了处理这个问题我们可以使用fetchType的属性,它可以全局定义无法处理的问题。fetchType存在级联元素collection,association中。有两个值
- eager,获得当前的POJO后,立即加载对应数据
- lazy 获得当前POJO后延迟加载对应的数据
现在全面学习另一种级联,这个方式完全可以消除N+1的问题,但是也引发其他的问题,首先SQL比较复杂,其次所需要的配置比之前复杂的多。再次一次性将所有的数据提取出来会造成内存的浪费,一般用于比较简单的且关联不多的场景
这里的SQL我们通过left join语句,将一个雇员模型信息所有的关联起来,这样便可以通过一条SQL将所有的信息都查询出来。对于列名做出了别名的处理。在mybatis中允许对这样的SQL进行配置,来完成级联。
- 每一个级联元素(association,discriminator,collection)中属性的id的配置和POJO实体配置的id一一对应,形成级联,比如上述的SQL列et_task_id和task实体的id是对应的,这是级联的关键所在。
-在级联元素上,association是通过javaType的定义声明实体映射,而collection则是使用ofType进行声明 - discriminator元素定义使用何种具体的resultMap进行级联,这里通过sex列进行判定
多对多级联
在现实生活中,有一种多对多的级联,而在程序中多对多的级联往往会被拆分成两个一对多级联处理
比如说:有许多用户,用户归属于一些角色,这样一个用户可以对应多个角色,而一个角色有可以由多个用户担当。
角色POJO
public class Role2 {
private Long id;
private String roleName;
private String note;
// 关联用户信息,一对多关联
private List userList;
用户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 roleList;
两个List类型的属性是专门做一对多级联的时候使用的,使用collection的元素去完成,得到两个mapper
角色mapper.xml
用户mapper.xml
这里使用collection去关联,但是把fetchType设为laz,这样就能够进行延迟加载。
测试代码
public static void testUserRole() {
SqlSession sqlSession = null;
try {
sqlSession = SqlSessionFactoryUtils.openSqlSession();
RoleMapper2 roleMapper2 = sqlSession.getMapper(RoleMapper2.class);
Role2 role2 = roleMapper2.getRole(1L);
System.out.println(role2.getUserList().size());
UserMapper2 userMapper2 = sqlSession.getMapper(UserMapper2.class);
User2 user2 = userMapper2.getUser(1L);
System.out.println(user2.getRoleList().size());
} catch(Exception ex) {
ex.printStackTrace();
} finally {
if (sqlSession != null) {
sqlSession.close();
}
}
}
一共有3条SQL被执行,因为在role.getUserList的方法中调用获取用户信息,所以延迟加载的语句被执行。