Mybatis中的级联关系

  • association 一对一
  • collection 一对多
  • discrimination 多对多

asscociation

我们模拟一个场景

一个学生有姓名,年龄,住址等等

Student类:

1
2
3
4
5
6
7
8
@Data
public Student{
private Long studentNo;
private String name;
private int age;
private Address address;
...
}

而这里的住址又包括省、市、区

1
2
3
4
5
6
7
8
@Data
public Address{
private Long addressNo;
private String province;
private String city;
private String area;
...
}
  1. 多表级联查询不使用association标签

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <resultMap type="Student" id="studentResult">
    <id property="studentNo" column="student_no"/>
    <result property="name" column="name"/>
    <result property="age" column="age"/>
    <!-- 直接赋值 -->
    <result property="address.addressNo" column="a_no"/>
    <result property="address.province" column="province"/>
    <result property="address.city" column="city"/>
    <result property="address.area" column="area"/>
    </resultMap>

    查询语句

    1
    2
    3
    4
    5
    <select id="findStudentWithAddress" 
    resultMap="studentResult"
    parameterType="Integer">
    select * from t_student t1,t_address t2 where t1.a_no=t2.address_no and t1.student_no=#{id}
    </select>

    这种方式采用的是对象方式的级联操作不太好,因为每一次查询都要把所有的属性列在那里,并且修改的时候要全部修改,所以很不方便,尽量模块化

  2. 多表级联查询使用association标签

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <resultMap type="Student" id="StudentResult">
    <id property="studentNo" column="student_no"/>
    <result property="name" column="name"/>
    <result property="age" column="age"/>
    <association property="address" javaType="Address">
    <id property="addressNo" column="address_no"/>
    <result property="province" column="province"/>
    <result property="city" column="city"/>
    <result property="area" column="area"/>
    </association>
    </resultMap>
  3. 单表查询(N+1的问题)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <resultMap type="Student" id="StudentResult">
    <id property="studentNo" column="student_no"/>
    <result property="name" column="name"/>
    <result property="age" column="age"/>
    <association property="address" javaType="Student" column="a_no"
    selete="getAddress"/>
    </resultMap>
    <!-- 不一定在同一XML文件中 -->
    <select id="getAddress" parameterType="int" resultType="Student">
    select * from t_address where address_no=#{_parameter}
    </select>

    这里的selete=getAddress也可以是packageName.xxxMapper.selectID

    不用sql联合查询,通过association的延迟加载来实现:

    ​ 什么是延迟加载?如果先查询订单信息即可满足业务要求就不会去查询用户,只有当用到用户信息时再查询用户信息。 对用户信息按需去查询就是延迟加载。
    比如上面,只有当调用StudentBean中的getAddress方法获取关联的address数据时,才会触发数据库查询t_address表。

    mybatis默认没有开启延迟加载,需要在SqlMapConfig.xmlsetting配置。
    lazyLoadingEnabled:全局性设置懒加载。如果设为false,则所有相关联的都会被初始化加载。

    ​ 允许值有:true | false。默认值:false
    aggressiveLazyLoading:当设置为true的时候,懒加载的对象可能被任何懒属性全部加载。

    ​ 否则,每个属性都按需加载。允许值有:true | false。默认值:true

    事实上,大多数业务场景显示的表格,都会用到多个表字段。
    如果采用延迟加载,会存在N+1问题。
    什么是N+1问题呢?
    每一个获取Order内部的User对象,都会进行一次select查询
    那么当运行过程中执行Order的getList方法时,SQL首先进行1次查询,查询结果如果有N条订单记录,那么实际在每条订单中显示过程中还要运行一次select用户的查询,共n次。
    SQL总共执行了n+1次。相比第二种方法的只进行一次联合查询,这种方式无疑是低效的。
    如果业务场景的表格显示字段,并没有跨表,那么可以采用延迟加载方式

collection

情景:一个班级有多个学生

班级表

1
2
3
4
5
6
7
CREATE TABLE `class` (
`CLASS_ID` int(11) NOT NULL AUTO_INCREMENT,
`CLASS_NAME` varchar(255) DEFAULT NULL,
`CLASS_YEAR` varchar(255) DEFAULT NULL,
`TEACHER_ID` int(255) DEFAULT NULL,
PRIMARY KEY (`CLASS_ID`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

学生表

1
2
3
4
5
6
7
8
9
10
CREATE TABLE `student` (
`student_id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`sex` varchar(255) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
`class_id` int(11) DEFAULT NULL,
PRIMARY KEY (`student_id`),
KEY `class_id` (`class_id`),
CONSTRAINT `class_id` FOREIGN KEY (`class_id`) REFERENCES `class` (`CLASS_ID`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

班级类

1
2
3
4
5
6
7
8
@Data
public class ClassEntity {
private int classid;
private String className;
private String classYear;
private Teacher teacher;
private List<Student> students;
}

学生类

1
2
3
4
5
6
7
@Data
public class Student {
private int studentId;
private String name;
private String sex;
private int age;
}

班级Mapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<mapper namespace="包名.ClassMapper">
<resultMap type="classEntity" id="classResultMap">
<id property="classid" column="CLASS_ID" />
<result property="className" column="CLASS_NAME" />
<result property="classYear" column="CLASS_YEAR" />
<association property="teacher" column="TEACHER_ID"
select="包名.TeacherMapper.getTeacher"/>
<collection property="students" column="class_id"
javaType="ArrayList" ofType="student"
select="包名.StudentMapper.getStudentByClassId"/>
</resultMap>
<select id="getClass" parameterType="integer" resultMap="classResultMap">
SELECT * FROM CLASS CT
WHERE CT.CLASS_ID = #{id};
</select>
</mapper>

学生Mapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
<mapper namespace="包名.StudentMapper">
<resultMap type="student" id="ResultMap">
<id property="studentId" column="STUDENT_ID" />
<result property="name" column="NAME" />
<result property="sex" column="sex" />
<result property="age" column="age" />
</resultMap>
<select id="getStudentByClassId" parameterType="integer" resultMap="ResultMap">
SELECT * FROM Student CT
WHERE CT.CLASS_ID = #{id};
</select>
</mapper>

使用方法和效率的比较:

  1. 全使用单表查询

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    <resultMap type="Student" id="StudentMap">
    <id column="id" property="id" />
    <result column="name" property="name" />
    <result column="job" property="job" />
    <collection property="scores"
    ofType="Score"
    column="id"
    select="queryScoresBySID"/>
    </resultMap>
    <resultMap type="Score" id="ScoreMap">
    <id column="id" property="id" />
    <result column="num" property="num" />
    <association property="subject"
    javaType="Subject"
    column="subject"
    select="querySubjectBySubId"/>
    </resultMap>
    <select id="queryStudents" resultMap="StudentMap" >
    SELECT id,name,job FROM t_student
    </select>
    <select id="queryScoresBySID" resultMap="ScoreMap">
    SELECT id,num,subject FROM t_score WHERE sid = #{sid}
    </select>
    <select id="querySubjectBySubId" resultType="Subject" >
    SELECT id,name FROM t_subject where id = #{id}
    </select>
  2. 使用左连接进行多表查询

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <resultMap type="Student" id="StudentMap2">
    <id column="id" property="id" />
    <result column="name" property="name" />
    <result column="job" property="job" />
    <collection property="scores" javaType="java.util.ArrayList" ofType="Score">
    <id column="id" property="id" />
    <result column="num" property="num"/>
    <association property="subject" javaType="Subject">
    <id column="id" property="id"/>
    <result column="name" property="name"/>
    </association>
    </collection>
    </resultMap>
    <select id="queryStudents2" resultMap="StudentMap2" >
    SELECT stu.id,stu.name name,stu.job,sco.id id,sco.num num,sub.id id,sub.name name
    FROM t_student stu LEFT JOIN t_score sco ON stu.id = sco.sid LEFT JOIN t_subject sub ON sco.subject = sub.id
    </select>

总结:

方案一:需要执行至少三次sql语句,开启三次事务才能完成本次请求。
方案二:只需要执行一次sql语句,开启一次事务就能完成本次请求

方案二比方案一的效率要高,但是在使用的时候,方案一的代码可重用性要高

如果追求代码重用性可以选择方案一
如果追求运行的性能可以选择方案二

descrimination

待续…