简言:本来这是昨天看的,但是因为想好好写一下【级联】这个东西,所以就看完之后今天来整理一下。

级联

  1. 什么是级联

  级联是一个数据库实体的概念。比如教师就需要存在学生与之对应,这样就有教师学生表,一个教师可能有多个学生,这就是一对多的级联;除此之外还有一对一的级联,比如身份证和公民是一对一的关系;再例如用户与角色的关系,一个用户有多个角色,一个角色也可能有多个用户,这就是多对多的级联。(在MyBatis中多对多的级联可以用两个一对多的级联进行代替)

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

  2. MyBatis中的级联

   MyBatis的级联分三种:

  • 鉴别器(discriminator):它是一个根据某些条件决定采用具体哪类级联的方案(说白了就是根据给出的条件去判断采用哪种级联,有点JavaSE中Switch的感觉,而且鉴别器也可以不去采用级联,而是直接映射关系,下面会有介绍)
  • 一对一(association):比如学生证和学生就是一种一对一的级联
  • 一对多(collection):比如老师和学生就是一种一对多的级联

  3. 级联的配置与使用

  一对一的级联:

  拿身份证和公民的关系举例:先建两个POJO文件。假设要得到公民信息。

/* 公民类 */
public class person {
private int id;
private String name;
private IDCard idCard;
/* getters and setters */
}
/* 身份证类 */
public class IDCard {
private long id;
private String name;
private String addr;
/* getters and setters */
}

  在公民中有一个身份证类型的属性用来存身份证信息,而在数据库中表的创建只需要存一个身份证的ID,这里我就不贴上表的创建了。接下来配置好Mapper配置文件。我这里写XML的配置方法。

 <resultMap type="person" id="PersonMap">
<id column="id" property="id"/>
<result column="name" property="name"/>
<association property="idCard" column="card_id" select="com.learn.ssm.chapter5.mapper.PersonMap.getIDCard"></association>
</resultMap> <select id="getIDCard" resultType="idcard">
select * from t_idcard
</select> <select id="getPersonInfo" resultMap="PersonMap">
select * from t_person
</select>

  首先配置一个resultMap元素,type的值person是公民类的别名。第4行中,property表示association的值保存到公民类的身份证属性中,card_id表示数据库中的身份证号字段,关键的是select属性,将会调用其值所对应的方法(也就是下面的getIDCard方法),并得到返回值存到idCard属性中。

  如果对resultMap的简单使用不熟悉的请参考我之前的随笔~

  一对多的级联:

  举例:老师和学生的关系,老师对应着很多学生,假设要得到老师信息。也先建立两个POJO文件。

public class Teacher {
private int id;
private String name;
private List<Student> studentList;
/* getters and setters */
}
public class Student {
private long id;
private String name;
private String sex;
private String teacherName;
/* getters and setters */
}

  老师的POJO中用一个list集合来存储学生信息。在学生信息中有teacherName属性用来对应相应的老师。

 <resultMap type="teacher" id="TeacherMap">
<id column="id" property="id"/>
<result column="name" property="name"/>
<collection property="studentList" column="name" select="com.learn.ssm.chapter5.mapper.PersonMap.getStudentInfoByTeacherName"></collection>
</resultMap> <select id="getStudentInfoByTeacherName" parameterType="String" resultMap="student">
select * from t_student where teacher_name = #{teacherName}
</select> <select id="getTeacherInfo" resultMap="TeacherMap">
select * from t_teacher
</select>

  与一对一的级联关系相似,只是用教师名字查询的结果会是多条,将会存到studentList属性中。

  鉴别器:

  举例:假设在一对多例子中,数据库中教师表有一个stu_sex的字段,用来过滤学生的性别的。可能我这个例子不是很实际,但是也不难理解。如果stu_sex字段是0的·话,查询的时候就只查出男学生,反之是1的话就自查出女学生。假设要查教师信息了。

 <resultMap type="teacher" id="TeacherMap">
<id column="id" property="id"/>
<result column="name" property="name"/>
<discriminator javaType="long" column="stu_sex">
<case value="0" resultMap="maleStuMap"></case>
<case value="1" resultMap="femaleStuMap"></case>
</discriminator>
</resultMap> <resultMap type="student" id="maleStuMap" extends="TeacherMap">
<collection property="studentList" column="name" select="com.learn.ssm.chapter5.mapper.PersonMap.getMaleStuInfoByTeacherName"></collection>
</resultMap> <resultMap type="student" id="femaleStuMap" extends="TeacherMap">
<collection property="studentList" column="name" select="com.learn.ssm.chapter5.mapper.PersonMap.getFemaleStuInfoByTeacherName"></collection>
</resultMap> <select id="getMaleStuInfoByTeacherName" parameterType="String" resultMap="student">
select * from t_student where teacher_name = #{teacherName} and sex = '男'
</select> <select id="getFemaleStuInfoByTeacherName" parameterType="String" resultMap="student">
select * from t_student where teacher_name = #{teacherName} and sex = '女'
</select> <select id="getTeacherInfo" resultMap="TeacherMap">
select * from t_teacher
</select>

  就像前面解释的,鉴别器就相当于switch,它不去直接产生级联关系,它调用其他resultMap中的级联关系。从而做到选择功能。

  extends很重要的。我刚开始以为extends在resultMap中起到继承作用(就是如果两个结果集有继承关系的话,子类结果集继承父类的映射),后来看到这里的时候,查了下api文档。如果在使用鉴别器的时候,被调用的resultMap没有extends属性的话,将之返回子resultMap的结果。在本例子中也就是只返回男学生们或者女学生们。

  api中的意思:如果没有extends的话,就会被当成独立组,和父类没有什么关系。贴上API连接~~~~【点击这里

  鉴别器也可以直接去映射关系,举例:

<resultMap id="vehicleResult" type="Vehicle">
<id property="id" column="id" />
<result property="vin" column="vin"/>
<result property="year" column="year"/>
<result property="make" column="make"/>
<result property="model" column="model"/>
<result property="color" column="color"/>
<discriminator javaType="int" column="vehicle_type">
<case value="1" resultMap="carResult"/>
<case value="2" resultMap="truckResult"/>
<case value="3" resultMap="vanResult"/>
<case value="4" resultMap="suvResult"/>
</discriminator>
</resultMap>
<resultMap id="carResult" type="Car" extends="vehicleResult">
<result property="doorCount" column="door_count" />
</resultMap>

  如果嫌这么麻烦的话,还有种写法:

<resultMap id="vehicleResult" type="Vehicle">
<id property="id" column="id" />
<result property="vin" column="vin"/>
<result property="year" column="year"/>
<result property="make" column="make"/>
<result property="model" column="model"/>
<result property="color" column="color"/>
<discriminator javaType="int" column="vehicle_type">
<case value="1" resultType="carResult">
<result property="doorCount" column="door_count" />
</case>
<case value="2" resultType="truckResult">
<result property="boxSize" column="box_size" />
<result property="extendedCab" column="extended_cab" />
</case>
<case value="3" resultType="vanResult">
<result property="powerSlidingDoor" column="power_sliding_door" />
</case>
<case value="4" resultType="suvResult">
<result property="allWheelDrive" column="all_wheel_drive" />
</case>
</discriminator>
</resultMap>

  多对多关系:

  之前介绍了多对多的关系就是两个一对多,举例:角色与用户的关系,角色有多个用户,用户也包含多个角色。这里就需要三张表,用户表,角色表,和用户角色关系表。

  贴上POJO

 public class User2 {
private int id;
private String name;
//角色列表
private List<Role2> roleList;
/* getters and setters */
} public class Role2 {
private long id;
private String userName;
private String note;
//用户列表
private List<User2> userList;
/* getters and setters */
}

  若要取用户信息的话:

<resultMap type="user2" id="UserMap">
<id column="id" property="id"/>
<result column="user_name" property="userName"/>
<collection property="roleList" column="id" select="com.learn.ssm.chapter5.mapper.UserMapper.getRoleInfoByUserId"></collection>
</resultMap> <select id="getRoleInfoByUserId" parameterType="long" resultMap="role2">
select r.* from t_user_role ur join t_role r on r.id = ur.role_id where ur.role_id = #{id}
</select> <select id="getUser" resultMap="UserMap">
select * from t_user
</select>

  若要获取角色信息:

<resultMap type="role2" id="RoleMap">
<id column="id" property="id"/>
<result column="role_name" property="roleName"/>
<collection property="userList" column="id" select="com.learn.ssm.chapter5.mapper.UserMapper.getUserInfoByRoleId"></collection>
</resultMap> <select id="getUserInfoByRoleId" parameterType="long" resultMap="user2">
select u.* from t_user_role ur join t_user u on u.id = ur.user_id where ur.user_id = #{id}
</select> <select id="getRole" resultMap="RoleMap">
select * from t_role
</select>

  4 N+1问题

  比如在一个雇员信息中,它的任务信息、体检表和工牌信息都是通过级联来取的。此时我只想加载它的基本信息和任务信息,那么体检表和工牌信息就不需要去取,因为这些信息不需要使用,加载他们会多执行几条毫无用处的SQL,会导致数据库资源的损耗和性能的下降。

关系如下:(这是我《JAVAEE 互联网轻量级框架整合开发(SSM)》书上的例子,就简单来看看,虽然我形容的比较简单,但是还是能懂的)

互联网轻量级框架SSM-查缺补漏第六天【级联+延迟加载特辑】-LMLPHP

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

  为了应对N+1问题,MyBatis提供了延迟加载功能

  在MyBatis的settiings配置中有两个元素控制延迟加载功能:

  • lazyLoadingEnabled:顾名思义,延迟加载开关,默认为false,不开启。值为true时开启。
  • aggressiveLazyLoading:3.4.2版本之后默认值为false,之前一致为true。这个元素的意思就是有“有进取心的延迟加载”,值为true,表示它有进取心,所以不管你要不要那些没用的级联信息,都给你加载出来(N+1个信息都给你加载出来)。值为false时,就把你所需要的,想看见的给你加载出来。(就给你加载那1个)aggeressiveLazyLoading是一个层级开关。

  继续用那个雇员的例子来举例:

  如果setting配置如下:

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

  lazyLoadingEnabled开关开启后,如果你不去访问雇员任务和工卡信息的话,是不会加载的。

  aggressiveLazyLoading值为true,将该层级带有延迟加载属性的对象完整加载。

  因为体检表采用了鉴别器来获取数据,它将和实体(也就是雇员本身)同层级,所以将把体检表加载出来。

  如果aggressiveLazyLoading值为false,体检表将不会加载出来。(任务信息和工卡当然也不会被加载出来了)

可是!这样也不是我们的需求呀,我们想得到基本信息和任务信息,显然着还没有满足我们的需求。

  在MyBatis中使用fetchType属性,它可以处理全局定义无法处理的问题。他有两个值:

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

在任务的级联标签中,加入fetchType:

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

  这个时候将按照我们的要求加载数据,先加载雇员信息,然后加载雇员任务信息,fetchType属性会忽略全局配置项LazyLoadingEnabled和aggressiveLazyLoading。

  总结:级联这个东西其实可用也可不用。要灵活使用,最后这个延迟加载的例子我没有附上代码,我觉得我写的还是能理解的。今天是第六天做笔记,作为19应届生,我其实看这本书的目的是为了差缺补漏,我之前接触SSM就是掌握了基本用法,对于配置和一些功能了解的不是全名。要是觉得我写的不是很清楚的知识点,还请自循搜索。

05-11 22:03