简介

MyBatis 提供了两种联合查询的方式,一种是嵌套查询,一种是嵌套结果。先说结论:在项目中不建议使用嵌套查询,会出现性能问题,可以使用嵌套结果。

测试类:com.yjw.demo.JointQueryTest,提供了对嵌套查询嵌套结果的测试。

数据库表模型关系

学生信息级联模型关系:链接

image.png

学生信息级联模型关系是一个多种类型关联关系,包含了如下几种情况:

  • 其中学生表是我们关注的中心,学生证表和它是一对一的关联关系;
  • 而学生表和课程成绩表是一对多的关系,一个学生可能有多门课程;
  • 课程表和课程成绩表也是一对多的关系;
  • 学生有男有女,而健康项目也有所不一,所以女性学生和男性学生的健康表也会有所不同,这些是根据学生的性别来决定的,而鉴别学生性别的就是鉴别器。

关联关系

在联合查询中存在如下几种对应关系:

  • 一对一的关系;
  • 一对多的关系;
  • 多对多的关系,实际使用过程中是把多对多的关系分解为两个一对多的关系,以降低关系的复杂度;
  • 还有一种是鉴别关系,比如我们去体检,男女有别,男性和女性的体检项目并不完全一样;

所以在 MyBatis 中联合分为这么3种:association、collection 和 discriminator。

  • association:代表一对一关系;
  • collection:代表一对多关系;
  • discriminator:代表鉴别器,它可以根据实际选择采用哪种类作为实例,允许你根据特定的条件去关联不同的结果集;

嵌套查询(不建议使用)

一对一关系

以学生表作为关注的中心,学生表和学生证表是一对一的关系。POJO 对象和映射文件的实现如下:

StudentDO

  1. public class StudentDO {
  2. private Long id;
  3. private String name;
  4. private Sex sex;
  5. private Long selfcardNo;
  6. private String note;
  7. private StudentSelfcardDO studentSelfcard;
  8. // get set 方法
  9. }

StudentMapper.xml

  1. <!-- 联合查询:嵌套查询 -->
  2. <resultMap id="studentMap1" type="studentDO">
  3. <id column="id" jdbcType="BIGINT" property="id" />
  4. <result column="name" jdbcType="VARCHAR" property="name" />
  5. <result column="sex" jdbcType="TINYINT" property="sex"
  6. typeHandler="com.yjw.demo.mybatis.common.type.SexEnumTypeHandler"/>
  7. <result column="selfcard_no" jdbcType="BIGINT" property="selfcardNo" />
  8. <result column="note" jdbcType="VARCHAR" property="note" />
  9. <!-- 嵌套查询:一对一级联 -->
  10. <association property="studentSelfcard" column="{studentId=id}"
  11. select="com.yjw.demo.mybatis.biz.dao.StudentSelfcardDao.listByConditions" />
  12. </resultMap>

一对一的关系建立通过 元素实现,该元素中的属性描述如下所示:

  • property:JavaBean 中对应的属性字段;
  • column:数据库的列名或者列标签别名。与传递给 resultSet.getString(columnName) 的参数名称相同。注意: 在处理组合键时,您可以使用 column= “{prop1=col1,prop2=col2}” 这样的语法,设置多个列名传入到嵌套查询语句。这就会把 prop1 和 prop2 设置到目标嵌套选择语句的参数对象中;
  • select:通过这个属性,通过 ID 引用另一个加载复杂类型的映射语句。
  • fetchType: 设置局部延迟加载,它有两个取值范围,即 eager 和 lazy。它的默认值取决于你在配置文件settings 的配置,如果没有配置它,默认是 eager,一旦配置了,全局配置(lazyLoadingEnabled)就会被他们覆盖;

一对多关系

以学生表作为关注的中心,学生表和课程表是一对多的关系。POJO 对象和映射文件的实现如下:

StudentDO

  1. public class StudentDO {
  2. private Long id;
  3. private String name;
  4. private Sex sex;
  5. private Long selfcardNo;
  6. private String note;
  7. private StudentSelfcardDO studentSelfcard;
  8. private List<StudentLectureDO> studentLectures;
  9. // get set 方法
  10. }

StudentMapper.xml

  1. <!-- 联合查询:嵌套查询 -->
  2. <resultMap id="studentMap1" type="studentDO">
  3. <id column="id" jdbcType="BIGINT" property="id" />
  4. <result column="name" jdbcType="VARCHAR" property="name" />
  5. <result column="sex" jdbcType="TINYINT" property="sex"
  6. typeHandler="com.yjw.demo.mybatis.common.type.SexEnumTypeHandler"/>
  7. <result column="selfcard_no" jdbcType="BIGINT" property="selfcardNo" />
  8. <result column="note" jdbcType="VARCHAR" property="note" />
  9. <!-- 嵌套查询:一对一级联 -->
  10. <association property="studentSelfcard" column="{studentId=id}"
  11. select="com.yjw.demo.mybatis.biz.dao.StudentSelfcardDao.listByConditions" />
  12. <!-- 嵌套查询:一对多级联 -->
  13. <collection property="studentLectures" column="{studentId=id}"
  14. select="com.yjw.demo.mybatis.biz.dao.StudentLectureDao.listByConditions" />
  15. </resultMap>

一对一的关系建立通过 元素实现,该元素中的属性描述和 元素一样

鉴别器

以学生表作为关注的中心,不同性别的学生关联不同的健康指标。POJO 对象和映射文件的实现如下:

首先,我们需要新建两个健康情况的 POJO,即 StudentHealthMaleDO和 StudentHealthFemaleDO,分别存储男性和女性的基础信息,再新建两个 StudentDO 的子类:MaleStudentDO 和 FemaleStudentDO,关联健康情况的 POJO。

  1. /**
  2. * 男生
  3. */
  4. public class MaleStudentDO extends StudentDO {
  5. private List<StudentHealthMaleDO> studentHealthMales;
  6. // get set 方法
  7. }
  8. /**
  9. * 女生
  10. */
  11. public class FemaleStudentDO extends StudentDO {
  12. private List<StudentHealthFemaleDO> studentHealthFemales;
  13. // get set 方法
  14. }

StudentMapper.xml

  1. <!-- 联合查询:嵌套查询 -->
  2. <resultMap id="studentMap1" type="studentDO">
  3. <id column="id" jdbcType="BIGINT" property="id" />
  4. <result column="name" jdbcType="VARCHAR" property="name" />
  5. <result column="sex" jdbcType="TINYINT" property="sex"
  6. typeHandler="com.yjw.demo.mybatis.common.type.SexEnumTypeHandler"/>
  7. <result column="selfcard_no" jdbcType="BIGINT" property="selfcardNo" />
  8. <result column="note" jdbcType="VARCHAR" property="note" />
  9. <!-- 嵌套查询:一对一级联 -->
  10. <association property="studentSelfcard" column="{studentId=id}"
  11. select="com.yjw.demo.mybatis.biz.dao.StudentSelfcardDao.listByConditions" />
  12. <!-- 嵌套查询:一对多级联 -->
  13. <collection property="studentLectures" column="{studentId=id}"
  14. select="com.yjw.demo.mybatis.biz.dao.StudentLectureDao.listByConditions" />
  15. <!-- 嵌套查询:鉴别器 -->
  16. <!-- discriminator:使用结果值来决定使用哪个 resultMap -->
  17. <!-- case:基于某些值的结果映射 -->
  18. <discriminator javaType="int" column="sex">
  19. <case value="1" resultMap="maleStudentMap1" />
  20. <case value="2" resultMap="femaleStudentMap1" />
  21. </discriminator>
  22. </resultMap>
  23. <!-- 男 -->
  24. <resultMap id="maleStudentMap1" type="maleStudentDO" extends="studentMap1">
  25. <collection property="studentHealthMales" column="{studentId=id}"
  26. select="com.yjw.demo.mybatis.biz.dao.StudentHealthMaleDao.listByConditions" />
  27. </resultMap>
  28. <!-- 女 -->
  29. <resultMap id="femaleStudentMap1" type="femaleStudentDO" extends="studentMap1">
  30. <collection property="studentHealthFemales" column="{studentId=id}"
  31. select="com.yjw.demo.mybatis.biz.dao.StudentHealthFemaleDao.listByConditions" />
  32. </resultMap>

MyBatis 中的鉴别器通过 元素实现,它对应的列(column)是 sex,对应的 Java 类型(javaType)是 int,case 类似 Java 中的 switch 语句,当 sex=1(男性)时,引入的是 maleStudentMap1,当 sex=2(女性)时,引入的是 femaleStudentMap1,然后我们分别对这两个 resultMap 定义。

N+1 问题

嵌套查询存在 N+1 的问题,每次取一个 Student 对象,那么它所有的信息都会被取出来,这样会造成 SQL 执行过多导致性能下降。

我们通过日志信息来看一下嵌套查询 N+1 的问题:

  1. 2019-09-12 15:38:24.717 DEBUG 2660 --- [ main] c.y.d.m.b.d.S.listStudentByNestingQuery : ==> Preparing: select * from t_student
  2. 2019-09-12 15:38:24.762 DEBUG 2660 --- [ main] c.y.d.m.b.d.S.listStudentByNestingQuery : ==> Parameters:
  3. 2019-09-12 15:38:24.839 DEBUG 2660 --- [ main] c.y.d.m.b.d.S.listByConditions : ====> Preparing: select id, student_id, check_date, heart, liver, spleen, lung, kidney, prostate, note from t_student_health_male WHERE student_id = ?
  4. 2019-09-12 15:38:24.840 DEBUG 2660 --- [ main] c.y.d.m.b.d.S.listByConditions : ====> Parameters: 1(Long)
  5. 2019-09-12 15:38:24.843 DEBUG 2660 --- [ main] c.y.d.m.b.d.S.listByConditions : <==== Total: 1
  6. 2019-09-12 15:38:24.848 DEBUG 2660 --- [ main] c.y.d.m.b.d.S.listByConditions : ====> Preparing: select id, student_id, native_place, issue_date, end_date, note, student_effective from t_student_selfcard WHERE student_id = ?
  7. 2019-09-12 15:38:24.849 DEBUG 2660 --- [ main] c.y.d.m.b.d.S.listByConditions : ====> Parameters: 1(Long)
  8. 2019-09-12 15:38:24.852 DEBUG 2660 --- [ main] c.y.d.m.b.d.S.listByConditions : <==== Total: 1
  9. 2019-09-12 15:38:24.856 DEBUG 2660 --- [ main] c.y.d.m.b.d.S.listByConditions : ====> Preparing: select id, student_id, lecture_id, grade, note from t_student_lecture WHERE student_id = ?
  10. 2019-09-12 15:38:24.857 DEBUG 2660 --- [ main] c.y.d.m.b.d.S.listByConditions : ====> Parameters: 1(Long)
  11. 2019-09-12 15:38:24.859 DEBUG 2660 --- [ main] c.y.d.m.b.d.LectureDao.getByPrimaryKey : ======> Preparing: select id, lecture_name, note from t_lecture where id = ?
  12. 2019-09-12 15:38:24.860 DEBUG 2660 --- [ main] c.y.d.m.b.d.LectureDao.getByPrimaryKey : ======> Parameters: 1(Long)
  13. 2019-09-12 15:38:24.862 DEBUG 2660 --- [ main] c.y.d.m.b.d.LectureDao.getByPrimaryKey : <====== Total: 1
  14. 2019-09-12 15:38:24.864 DEBUG 2660 --- [ main] c.y.d.m.b.d.LectureDao.getByPrimaryKey : ======> Preparing: select id, lecture_name, note from t_lecture where id = ?
  15. 2019-09-12 15:38:24.864 DEBUG 2660 --- [ main] c.y.d.m.b.d.LectureDao.getByPrimaryKey : ======> Parameters: 2(Long)
  16. 2019-09-12 15:38:24.867 DEBUG 2660 --- [ main] c.y.d.m.b.d.LectureDao.getByPrimaryKey : <====== Total: 1
  17. 2019-09-12 15:38:24.868 DEBUG 2660 --- [ main] c.y.d.m.b.d.LectureDao.getByPrimaryKey : ======> Preparing: select id, lecture_name, note from t_lecture where id = ?
  18. 2019-09-12 15:38:24.869 DEBUG 2660 --- [ main] c.y.d.m.b.d.LectureDao.getByPrimaryKey : ======> Parameters: 3(Long)
  19. 2019-09-12 15:38:24.870 DEBUG 2660 --- [ main] c.y.d.m.b.d.LectureDao.getByPrimaryKey : <====== Total: 1
  20. 2019-09-12 15:38:24.871 DEBUG 2660 --- [ main] c.y.d.m.b.d.S.listByConditions : <==== Total: 3
  21. 2019-09-12 15:38:24.874 DEBUG 2660 --- [ main] c.y.d.m.b.d.S.listByConditions : ====> Preparing: select id, student_id, check_date, heart, liver, spleen, lung, kidney, uterus, note from t_student_health_female WHERE student_id = ?
  22. 2019-09-12 15:38:24.875 DEBUG 2660 --- [ main] c.y.d.m.b.d.S.listByConditions : ====> Parameters: 2(Long)
  23. 2019-09-12 15:38:24.878 DEBUG 2660 --- [ main] c.y.d.m.b.d.S.listByConditions : <==== Total: 1
  24. 2019-09-12 15:38:24.879 DEBUG 2660 --- [ main] c.y.d.m.b.d.S.listByConditions : ====> Preparing: select id, student_id, native_place, issue_date, end_date, note, student_effective from t_student_selfcard WHERE student_id = ?
  25. 2019-09-12 15:38:24.879 DEBUG 2660 --- [ main] c.y.d.m.b.d.S.listByConditions : ====> Parameters: 2(Long)
  26. 2019-09-12 15:38:24.881 DEBUG 2660 --- [ main] c.y.d.m.b.d.S.listByConditions : <==== Total: 1
  27. 2019-09-12 15:38:24.882 DEBUG 2660 --- [ main] c.y.d.m.b.d.S.listByConditions : ====> Preparing: select id, student_id, lecture_id, grade, note from t_student_lecture WHERE student_id = ?
  28. 2019-09-12 15:38:24.882 DEBUG 2660 --- [ main] c.y.d.m.b.d.S.listByConditions : ====> Parameters: 2(Long)
  29. 2019-09-12 15:38:24.886 DEBUG 2660 --- [ main] c.y.d.m.b.d.S.listByConditions : <==== Total: 3
  30. 2019-09-12 15:38:24.887 DEBUG 2660 --- [ main] c.y.d.m.b.d.S.listByConditions : ====> Preparing: select id, student_id, check_date, heart, liver, spleen, lung, kidney, prostate, note from t_student_health_male WHERE student_id = ?
  31. 2019-09-12 15:38:24.887 DEBUG 2660 --- [ main] c.y.d.m.b.d.S.listByConditions : ====> Parameters: 3(Long)
  32. 2019-09-12 15:38:24.893 DEBUG 2660 --- [ main] c.y.d.m.b.d.S.listByConditions : <==== Total: 0
  33. 2019-09-12 15:38:24.894 DEBUG 2660 --- [ main] c.y.d.m.b.d.S.listByConditions : ====> Preparing: select id, student_id, native_place, issue_date, end_date, note, student_effective from t_student_selfcard WHERE student_id = ?
  34. 2019-09-12 15:38:24.897 DEBUG 2660 --- [ main] c.y.d.m.b.d.S.listByConditions : ====> Parameters: 3(Long)
  35. 2019-09-12 15:38:24.899 DEBUG 2660 --- [ main] c.y.d.m.b.d.S.listByConditions : <==== Total: 0
  36. 2019-09-12 15:38:24.900 DEBUG 2660 --- [ main] c.y.d.m.b.d.S.listByConditions : ====> Preparing: select id, student_id, lecture_id, grade, note from t_student_lecture WHERE student_id = ?
  37. 2019-09-12 15:38:24.901 DEBUG 2660 --- [ main] c.y.d.m.b.d.S.listByConditions : ====> Parameters: 3(Long)
  38. 2019-09-12 15:38:24.908 DEBUG 2660 --- [ main] c.y.d.m.b.d.S.listByConditions : <==== Total: 0
  39. 2019-09-12 15:38:24.909 DEBUG 2660 --- [ main] c.y.d.m.b.d.S.listStudentByNestingQuery : <== Total: 3

学生数据有3条,在查询学生证件、学生成绩等这些信息的时候,分别执行了3次,每次都用 id = ? 执行,如果学生数据比较多时,严重影响性能。

为了处理嵌套查询带来的 N+1 的问题,MyBatis 引入了延迟加载的功能。在 MyBatis 的配置中有两个全局的参数 lazyLoadingEnabled、aggressiveLazyLoading。

设置参数
描述 有效值 默认值
lazyLoadingEnabled 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 true | false false
aggressiveLazyLoading 当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载。 true | false false (true in ≤3.4.1)

我们设置延迟加载的全局开关(lazyLoadingEnabled)为 true 的时候,当访问学生信息的时候,MyBatis 已经把学生的健康情况也查询出来了,当访问学生的课程信息的时候,MyBatis 同时也把其学生证信息查询出来了,为什么是这样一个结果呢?因为在默认情况下 MyBatis 是按层级延迟加载的,让我们看看这个延迟加载的层级:

image.png

这不是我们需要的加载数据方式,我们不希望在访问学生信息的时候去加载学生的健康情况数据。那么这个时候就需要设置 aggressiveLazyLoading 属性了,当它为 true 的时候,MyBatis 的内容按层级加载,否则就按我们调用的要求加载。

这两项配置既可以在 Spring Boot 配置文件中配置,也可以在 MyBatis 配置文件中配置,在 setting 元素中加入下面的代码:

  1. <settings>
  2. <setting name="lazyLoadingEnabled" value="true"/>
  3. <setting name="aggressiveLazyLoading" value="false"/>
  4. </settings>

按需加载的意思是我们不手动调用对应的属性,就不会加载。通过执行如下测试代码来演示一下按需加载的功能:

  1. /**
  2. * 联合查询-嵌套查询(一对一、一对多、鉴别器)
  3. *
  4. * @throws JsonProcessingException
  5. */
  6. @Test
  7. public void listStudentByNestingQuery() throws JsonProcessingException, InterruptedException {
  8. List<StudentDO> students = studentDao.listStudentByNestingQuery();
  9. Thread.sleep(3000L);
  10. System.out.println("睡眠3秒钟");
  11. students.get(0).getStudentSelfcard();
  12. }

在查询完学生信息的时候,我们睡眠了3秒钟,再调学生证件信息,来看下日志的输出:

  1. 2019-09-12 16:24:46.341 INFO 16772 --- [ main] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} inited
  2. 2019-09-12 16:24:46.355 DEBUG 16772 --- [ main] c.y.d.m.b.d.S.listStudentByNestingQuery : ==> Preparing: select * from t_student
  3. 2019-09-12 16:24:46.402 DEBUG 16772 --- [ main] c.y.d.m.b.d.S.listStudentByNestingQuery : ==> Parameters:
  4. 2019-09-12 16:24:46.630 DEBUG 16772 --- [ main] c.y.d.m.b.d.S.listStudentByNestingQuery : <== Total: 3
  5. 睡眠3秒钟
  6. 2019-09-12 16:24:49.655 DEBUG 16772 --- [ main] c.y.d.m.b.d.S.listByConditions : ==> Preparing: select id, student_id, native_place, issue_date, end_date, note, student_effective from t_student_selfcard WHERE student_id = ?
  7. 2019-09-12 16:24:49.659 DEBUG 16772 --- [ main] c.y.d.m.b.d.S.listByConditions : ==> Parameters: 1(Long)
  8. 2019-09-12 16:24:49.666 DEBUG 16772 --- [ main] c.y.d.m.b.d.S.listByConditions : <== Total: 1

看上面的日志输出,延迟加载的配置实现了按需加载的功能。但是嵌套查询还是不建议使用,因为不可控,我们不确定哪些操作会导致 N+1 的问题,比如如果我们使用了 JSON 的工具把查出来的学生信息转成 JSON 字符串的时候,就会导致查询出学生的所有关联信息。

  1. /**
  2. * 联合查询-嵌套查询(一对一、一对多、鉴别器)
  3. *
  4. * @throws JsonProcessingException
  5. */
  6. @Test
  7. public void listStudentByNestingQuery() throws JsonProcessingException, InterruptedException {
  8. List<StudentDO> students = studentDao.listStudentByNestingQuery();
  9. // 1.测试延迟加载的效果
  10. // Thread.sleep(3000L);
  11. // System.out.println("睡眠3秒钟");
  12. // students.get(0).getStudentSelfcard();
  13. // 2.使用JSON功能转JSON字符串会导致N+1的问题
  14. System.out.println(JsonUtils.toJSONString(students));
  15. }

日志输出和没有使用延迟加载配置的效果一样,其实这里的配置是没有问题的,只是 JSON 工具在生成 JSON 字符串的时候,会逐层调用数据,所以就导致了需要把学生的所有关联信息都查出来。

嵌套结果

MyBatis 还提供了另外一种关联查询的方式(嵌套结果),这种方式更为简单和直接,没有 N+1 的问题,因为它的数据是一条 SQL 查出来的,代码如下所示。

嵌套结果中的一对一、一对多、鉴别器和嵌套查询类似,只是不引用外部的 select 语句,属性都配置在了一个 resultMap 中。

  1. <!-- 联合查询:嵌套结果 -->
  2. <resultMap id="studentMap2" type="studentDO">
  3. <id property="id" column="id"/>
  4. <result property="name" column="name"/>
  5. <result property="selfcardNo" column="selfcard_no"/>
  6. <result property="note" column="note"/>
  7. <association property="studentSelfcard" javaType="studentSelfcardDO">
  8. <result property="id" column="ssid"/>
  9. <result property="nativePlace" column="native_place"/>
  10. <result property="issueDate" column="issue_date"/>
  11. <result property="endDate" column="end_date"/>
  12. <result property="note" column="ssnote"/>
  13. </association>
  14. <collection property="studentLectures" ofType="studentLectureDO">
  15. <result property="id" column="slid"/>
  16. <result property="grade" column="grade"/>
  17. <result property="note" column="slnote"/>
  18. <association property="lecture" javaType="lectureDO">
  19. <result property="id" column="lid"/>
  20. <result property="lectureName" column="lecture_name"/>
  21. <result property="note" column="lnote"/>
  22. </association>
  23. </collection>
  24. <discriminator javaType="int" column="sex">
  25. <case value="1" resultMap="maleStudentMap2"/>
  26. <case value="2" resultMap="femaleStudentMap2"/>
  27. </discriminator>
  28. </resultMap>
  29. <!-- 男 -->
  30. <resultMap id="maleStudentMap2" type="maleStudentDO" extends="studentMap2">
  31. <collection property="studentHealthMales" ofType="studentHealthMaleDO">
  32. <id property="id" column="hid"/>
  33. <result property="checkDate" column="check_date"/>
  34. <result property="heart" column="heart"/>
  35. <result property="liver" column="liver"/>
  36. <result property="spleen" column="spleen"/>
  37. <result property="lung" column="lung"/>
  38. <result property="kidney" column="kidney"/>
  39. <result property="prostate" column="prostate"/>
  40. <result property="note" column="shnote"/>
  41. </collection>
  42. </resultMap>
  43. <!-- 女 -->
  44. <resultMap id="femaleStudentMap2" type="femaleStudentDO" extends="studentMap2">
  45. <collection property="studentHealthFemales" ofType="studentHealthFemaleDO">
  46. <id property="id" column="hid"/>
  47. <result property="checkDate" column="check_date"/>
  48. <result property="heart" column="heart"/>
  49. <result property="liver" column="liver"/>
  50. <result property="spleen" column="spleen"/>
  51. <result property="lung" column="lung"/>
  52. <result property="kidney" column="kidney"/>
  53. <result property="uterus" column="uterus"/>
  54. <result property="note" column="shnote"/>
  55. </collection>
  56. </resultMap>
  57. <select id="listStudentByNestingResult" resultMap="studentMap2">
  58. SELECT s.id,s.name,s.sex,s.note,s.selfcard_no,
  59. if(sex=1,shm.id,shf.id) AS hid,
  60. if(sex=1,shm.check_date,shf.check_date) AS check_date,
  61. if(sex=1,shm.heart,shf.heart) AS heart,
  62. if(sex=1,shm.liver,shf.liver) AS liver,
  63. if(sex=1,shm.spleen,shf.spleen) AS spleen,
  64. if(sex=1,shm.lung,shf.lung) AS lung,
  65. if(sex=1,shm.kidney,shf.kidney) AS kidney,
  66. if(sex=1,shm.note,shf.note) AS shnote,
  67. shm.prostate,shf.uterus,
  68. ss.id AS ssid,ss.native_place,
  69. ss.issue_date,ss.end_date,ss.note AS ssnote,
  70. sl.id AS slid,sl.grade,sl.note AS slnote,
  71. l.lecture_name,l.note AS lnote
  72. FROM t_student s
  73. LEFT JOIN t_student_health_male shm ON s.id=shm.student_id
  74. LEFT JOIN t_student_health_female shf ON s.id = shf.student_id
  75. LEFT JOIN t_student_selfcard ss ON s.id = ss.student_id
  76. LEFT JOIN t_student_lecture sl ON s.id=sl.student_id
  77. LEFT JOIN t_lecture l ON sl.lecture_id = l.id
  78. </select>

collection 元素中的 ofType 属性定义的是 collection 里面的 Java 类型。

作者:殷建卫 链接:https://www.yuque.com/yinjianwei/vyrvkf/mckqvf 来源:殷建卫 - 架构笔记 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。