mybatis学习之级联

image.png

现在根据设计模型建立对应的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的方法中调用获取用户信息,所以延迟加载的语句被执行。

你可能感兴趣的:(mybatis学习之级联)