Mybatis笔记(4)-多表查询和延迟加载

Mybatis多表查询

一对一(<association>)

以学生(Student)和身份证(Card)为例,即一个学生对应一个身份证。

数据表如下:(表中的字段除了3个id字段,其他都是varchar类型,card_id是指向card的id字段的外键)

card:

image-20200916223746339

student:

image-20200916223847902

(省略了setter等)

Card类:

public class Card {
    private Integer id;
    private String idNumber;
}

Student类:

public class Student {
    private Integer id;
    private String studentName;
    private Card card;
}

编写StudentMapper方法:

List<Student> selectAll();


映射文件(查询全部,并Student对应的Card封装进Student):

  • sql语句使用的是等值连接查询。

  • cardMap和studentMap的id标签中的column属性值,分别是cid和sid,对应查询语句结果集的字段名,这里为s.id和c.id设置了别名

    此处column属性值不能使用s.id和c.id,mybatis将无法解析。所以可通过为字段指定别名,将column值指定为对应别名。

  • studentMap中使用了<association>标签来封装Student中的Card对象。

<resultMap id="cardMap" type="card">
    <id property="id" column="cid" />
    <result property="idNumber" column="id_number" />
</resultMap>

<resultMap id="studentMap" type="student">
    <id property="id" column="sid" />
    <result property="studentName" column="student_name" />
    <association property="card" resultMap="cardMap" />
</resultMap>

<select id="selectAll" resultMap="studentMap">
    select s.id sid,student_name,c.id cid,id_number
    from student s
    inner join card c
    on s.card_id=c.id;
</select>


测试方法:

@Test
public void testSelectALl(){
    try(SqlSession session = factory.openSession()){
        StudentMapper mapper = session.getMapper(StudentMapper.class);
        List<Student> students = mapper.selectAll();
        for(Student student : students){
            System.out.println(student);
        }
    }
}

输出:

Student(id=2, studentName=小明, card=Card(id=1, idNumber=111111))
Student(id=1, studentName=张三, card=Card(id=2, idNumber=222222))
Student(id=3, studentName=王大锤, card=Card(id=3, idNumber=333333))

一对多(<collection>)

以班级(grade)和老师(Teacher)为例,一个班级对应多个老师

grade表:

image-20200916233040044

teacher表:(grade_id为指向grade的外键)

image-20200916233100274


对应的JavaBean:

public class Grade {
    private Integer id;
    private String gradeName;
    private List<Teacher> teachers;
}

public class Teacher {
    private Integer id;
    private String teacherName;
}

//省略了setter、getter、toString

定义Mapper方法:

List<Grade> selectAll();

映射文件:

  • 先使用<resultMap>分别对两个表跟JavaBean进行映射。
  • gradeMap中使用了<collection>封装Grade中的Teacher,通过resultMap属性指定集合类型。property属性指定集合对应的JavaBean属性。
  • 与一对一中一样,注意别名问题。
<resultMap id="teacherMap" type="teacher">
    <id property="id" column="tid" />
    <result property="teacherName" column="teacher_name" />
</resultMap>

<resultMap id="gradeMap" type="grade">
    <id property="id" column="gid" />
    <result property="gradeName" column="grade_name" />
    <collection property="teachers" resultMap="teacherMap" />
</resultMap>

<select id="selectAll" resultMap="gradeMap">
    select g.id gid, g.grade_name, t.id tid,t.teacher_name
    from grade g
    inner join teacher t
    on t.grade_id=g.id;
</select>


测试方法:

@Test
public void testSelectAll(){
    try(SqlSession session = factory.openSession()){
        GradeMapper mapper = session.getMapper(GradeMapper.class);
        List<Grade> grades = mapper.selectAll();
        for(Grade grade : grades){
            System.out.println(grade);
        }
    }
}

输出:可看到与实际表数据对应。

Grade(id=2, gradeName=软件工程, teachers=[Teacher(id=1, teacherName=黄老师), Teacher(id=2, teacherName=徐老师), Teacher(id=3, teacherName=张老师)])
Grade(id=1, gradeName=计算机科学与技术, teachers=[Teacher(id=4, teacherName=李老师)])
Grade(id=3, gradeName=信息安全, teachers=[Teacher(id=5, teacherName=王老师), Teacher(id=6, teacherName=马老师)])


多对多

多对多其实本质就是一对多,相关的配置与一对多一样。

小结

在上述示例的映射文件中,都是先使用</resultMap>封装好表与JavaBean之间的映射,再在<association><collection>中使用resultMap属性指定即可。这样配置代码更加整洁。

也可以在<association><collection>中配置映射关系,如一对一中的studentMap可改为:

<resultMap id="studentMap" type="student">
    <id property="id" column="sid" />
    <result property="studentName" column="student_name" />
    <!--<association property="card" resultMap="cardMap" />-->
    <association property="card" javaType="card">
        <id property="id" column="cid" />
        <result property="idNumber" column="id_number" />
    </association>
</resultMap>

其中,替代resultMap属性,通过javaType指定封装的类型,对于<collection>,则是使用ofType属性。

Mybatis延迟加载

概述

在多表查询的一对多中,在查询“一”时,有时并不需要将“多”也全部查询出来,什么时候需要用到,什么时候再查询才更符合实际要求,不用造成不必要的系统开销

在四种表关系中:一对、多对,通常情况下都是采用延迟加载;而一对(跟多对),通常情况下“一”都不可省略,所以一般都是采用立即加载(也有使用延迟加载的情况)。

Mybatis中可通过修改配置信息实现延迟加载。

实现延迟加载

实现延迟加载需要先设置mybatis的全局变量lazyLoadingEnabled为true(该值默认为false) ,表示开启延迟加载。

若是3.4.1 及之前的版本,还需设置aggressiveLazyLoading 变量为false,其默认值是true。

该变量的官方解释:

开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载

以上配置在主配置文件的settings标签中进行:

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

通过示例验证:

以学生(Student)和身份证(Card)为例,即一个学生对应一个身份证。

数据表如下:

card:

image-20200916223746339

student:

image-20200916223847902


Card类:

public class Card {
    private Integer id;
    private String idNumber;
    
    public Card() {
        //被创建时输出内容。用于验证延迟加载
        System.out.println("创建了一个Card对象...");
    }
}


Student类:

public class Student {
    private Integer id;
    private String studentName;
    private Card card;
}

两个JavaBean对应的Mapper:

public interface CardMapper { Card selectById(Integer id); }

public interface StudentMapper {  List<Student> selectAll(); }

映射文件:

CardMapper.xml

<mapper namespace="com.mapper.CardMapper">
    <resultMap id="cardMap" type="card">
        <result property="idNumber" column="id_number" />
    </resultMap>

    <select id="selectById" resultMap="cardMap">
        select * from card
        where id = #{id}
    </select>
</mapper>

StudentMapper.xml:

  • selectAll中的sql语句不用使用连接查询语句
  • 在association标签中,select指定了查询Student中的Card时使用的查询方法属性值是Mapper方法的全限定名。column属性指定了用于查询card所需的id,select * from student的查询结果集中的card_id字段
<mapper namespace="com.mapper.StudentMapper">

    <resultMap id="studentMap" type="student">
        <id property="id" column="id" />
        <result property="studentName" column="student_name" />
        <association property="card" column="card_id" select="com.mapper.CardMapper.selectById" />
    </resultMap>

    <select id="selectAll" resultMap="studentMap">
        select * from student
    </select>

</mapper>

测试方法:

@Test
public void testLazyLoad(){
    try(SqlSession session = factory.openSession()){
        StudentMapper mapper = session.getMapper(StudentMapper.class);
        List<Student> students = mapper.selectAll();

        Student student = students.get(0);
        System.out.println("输出student的name:" + student.getStudentName());
        System.out.println("---------------------");
        System.out.println("输出student的card:" + student.getCard());
    }
}

输出是

输出student的name:张三
---------------------
创建了一个Card对象...
输出student的card:Card(id=2, idNumber=222222)

Card对象被创建时,会输出创建了一个Card对象...(见其类定义的构造器),通过输出可看出mapper.selectAll()执行时并没有创建Card对象。当调用了student的getCard()方法时才查询所需的Card对象

通过idea的debug不能看出延迟加载的效果。debug过程中查看students变量可发现所有的card都被创建完成。。。

posted @ 2020-09-18 16:18  bxxiao  阅读(164)  评论(0)    收藏  举报