简言:本来这是昨天看的,但是因为想好好写一下【级联】这个东西,所以就看完之后今天来整理一下。
级联
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的配置方法。
1 <resultMap type="person" id="PersonMap"> 2 <id column="id" property="id"/> 3 <result column="name" property="name"/> 4 <association property="idCard" column="card_id" select="com.learn.ssm.chapter5.mapper.PersonMap.getIDCard"></association> 5 </resultMap> 6 7 <select id="getIDCard" resultType="idcard"> 8 select * from t_idcard 9 </select> 10 11 <select id="getPersonInfo" resultMap="PersonMap"> 12 select * from t_person 13 </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属性用来对应相应的老师。
1 <resultMap type="teacher" id="TeacherMap"> 2 <id column="id" property="id"/> 3 <result column="name" property="name"/> 4 <collection property="studentList" column="name" select="com.learn.ssm.chapter5.mapper.PersonMap.getStudentInfoByTeacherName"></collection> 5 </resultMap> 6 7 <select id="getStudentInfoByTeacherName" parameterType="String" resultMap="student"> 8 select * from t_student where teacher_name = #{teacherName} 9 </select> 10 11 <select id="getTeacherInfo" resultMap="TeacherMap"> 12 select * from t_teacher 13 </select>
与一对一的级联关系相似,只是用教师名字查询的结果会是多条,将会存到studentList属性中。
鉴别器:
举例:假设在一对多例子中,数据库中教师表有一个stu_sex的字段,用来过滤学生的性别的。可能我这个例子不是很实际,但是也不难理解。如果stu_sex字段是0的·话,查询的时候就只查出男学生,反之是1的话就自查出女学生。假设要查教师信息了。
1 <resultMap type="teacher" id="TeacherMap"> 2 <id column="id" property="id"/> 3 <result column="name" property="name"/> 4 <discriminator javaType="long" column="stu_sex"> 5 <case value="0" resultMap="maleStuMap"></case> 6 <case value="1" resultMap="femaleStuMap"></case> 7 </discriminator> 8 </resultMap> 9 10 <resultMap type="student" id="maleStuMap" extends="TeacherMap"> 11 <collection property="studentList" column="name" select="com.learn.ssm.chapter5.mapper.PersonMap.getMaleStuInfoByTeacherName"></collection> 12 </resultMap> 13 14 <resultMap type="student" id="femaleStuMap" extends="TeacherMap"> 15 <collection property="studentList" column="name" select="com.learn.ssm.chapter5.mapper.PersonMap.getFemaleStuInfoByTeacherName"></collection> 16 </resultMap> 17 18 <select id="getMaleStuInfoByTeacherName" parameterType="String" resultMap="student"> 19 select * from t_student where teacher_name = #{teacherName} and sex = '男' 20 </select> 21 22 <select id="getFemaleStuInfoByTeacherName" parameterType="String" resultMap="student"> 23 select * from t_student where teacher_name = #{teacherName} and sex = '女' 24 </select> 25 26 <select id="getTeacherInfo" resultMap="TeacherMap"> 27 select * from t_teacher 28 </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
1 public class User2 { 2 private int id; 3 private String name; 4 //角色列表 5 private List<Role2> roleList; 6 /* getters and setters */ 7 } 8 9 public class Role2 { 10 private long id; 11 private String userName; 12 private String note; 13 //用户列表 14 private List<User2> userList; 15 /* getters and setters */ 16 }
若要取用户信息的话:
<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)》书上的例子,就简单来看看,虽然我形容的比较简单,但是还是能懂的)
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就是掌握了基本用法,对于配置和一些功能了解的不是全名。要是觉得我写的不是很清楚的知识点,还请自循搜索。