(十一)MyBatis的高级映射及延迟加载

Mybatis学习目录

上一篇:(十)Mybatis之动态SQL
下一篇:(十二)Mybatis的缓存机制

环境

数据库:

  • 学生表t_students,班级表t_clazz
    班级表t_clazz:
    请添加图片描述
    学生表t_students:
    请添加图片描述
  • 订单表t_order,商品表t_products,关系表t_orderitem
    商品表t_products:
    在这里插入图片描述
    订单表t_order:
    在这里插入图片描述
    关系表t_orderitem:
    在这里插入图片描述

引⼊依赖:mysql驱动依赖、mybatis依赖、logback依赖、junit依赖。
引入配置文件:jdbc.properties、mybatis-config.xml、logback.xml
pojo类:

  • Student、Clazz
  • Order、Products

多对一

多个学生对应一个班级
pojo类Student中添加⼀个属性:Clazz clazz; 表示学生关联的班级对象。
多种方式,常见的包括三种:

  • 第⼀种方式:⼀条SQL语句,级联属性映射。
  • 第⼆种方式:⼀条SQL语句,association。
  • 第三种方式:两条SQL语句,分步查询。(这种方式常用:优点⼀是可复用。优点⼆是支持懒加载。)

Student:

/**
 * 学生信息
 */
public class Student {//Student是多的一方
    private Integer sid;
    private String sname;
    private Clazz clazz;//clazz是一的一方

    public Clazz getClazz() {
        return clazz;
    }

    public void setClazz(Clazz clazz) {
        this.clazz = clazz;
    }

    public Student() {
    }

    public Student(Integer sid, String sname) {
        this.sid = sid;
        this.sname = sname;
    }

    @Override
    public String toString() {
        return "Student{" +
                "sid=" + sid +
                ", sname='" + sname + '\'' +
                ", clazz=" + clazz +
                '}';
    }

    public Integer getSid() {
        return sid;
    }

    public void setSid(Integer sid) {
        this.sid = sid;
    }

    public String getSname() {
        return sname;
    }

    public void setSname(String sname) {
        this.sname = sname;
    }
}

Clazz:

/**
 * 班级信息
 */
public class Clazz {
    private Integer cid;
    private String cname;

    public Clazz() {
    }

    public Clazz(Integer cid, String cname) {
        this.cid = cid;
        this.cname = cname;
    }

    @Override
    public String toString() {
        return "Clazz{" +
                "cid=" + cid +
                ", cname='" + cname + '\'' +
                '}';
    }

    
    public Integer getCid() {
        return cid;
    }

    public void setCid(Integer cid) {
        this.cid = cid;
    }

    public String getCname() {
        return cname;
    }

    public void setCname(String cname) {
        this.cname = cname;
    }
}

第⼀种方式:级联属性映射

⼀条SQL语句,使用resultMap标签进行级联属性映射

创建StudentMapper接口,添加方法

    /**
     * 通过id查询,同时获取学生关联的班级信息,使用resultMap标签进行级联属性映射
     * @param id 学生的id
     * @return 学生对象,含有班级对象
     */
    Student selectById(Integer id);

创建StudentMapper.xml映射文件,配置级联属性映射

	<resultMap id="studentResultMap" type="Student">
        <id property="sid" column="sid"></id>
        <result property="sname" column="sname"></result>
        <!--通过'.',进行引用属性映射-->
        <result property="clazz.cid" column="cid"></result>
        <result property="clazz.cname" column="cname"></result>
    </resultMap>
    <select id="selectById" resultMap="studentResultMap">
        select
               s.sid,s.sname,c.cid,c.cname
        from
             t_students s left join t_clazz c on s.cid = c.cid
        where s.sid = #{sid}
    </select>

测试程序:

    @Test
    public void testSelectById() {
        SqlSession session = SqlSessionUtil.getSession();
        StudentMapper mapper = session.getMapper(StudentMapper.class);
        Student student = mapper.selectById(2);
        System.out.println(student);
        SqlSessionUtil.close(session);
    }

请添加图片描述

第⼆种方式:association

多对一映射的第二种方式,一条SQL语句,在resultMap使用association标签进行关联

  • association标签:翻译为关联,把一个对象关联到另一个对象上。
    • property属性:提供要映射类的属性名
    • javaType属性:要映射的类名
    • id标签和result标签,用法与resultMap一样

StudentMapper接口添加方法:

	/**
     * 通过id查询,同时获取学生关联的班级信息,使用association标签进行关联
     * @return
     */
    Student selectByIdAssociation(Integer id);

StudentMapper.xml映射文件配置:

	<resultMap id="studentResultMapAssociation" type="Student">
        <id property="sid" column="sid"></id>
        <result property="sname" column="sname"></result>
        <association property="clazz" javaType="Clazz">
            <id property="cid" column="cid" />
            <result property="cname" column="cname" />
        </association>
    </resultMap>
    <select id="selectByIdAssociation" resultMap="studentResultMapAssociation">
        select
            s.sid,s.sname,c.cid,c.cname
        from
            t_students s left join t_clazz c on s.cid = c.cid
        where s.sid = #{sid}
    </select>

测试程序:

    @Test
    public void testSelectByIdAssociation() {
        SqlSession session = SqlSessionUtil.getSession();
        StudentMapper mapper = session.getMapper(StudentMapper.class);
        Student student = mapper.selectByIdAssociation(4);
        System.out.println(student);
        SqlSessionUtil.close(session);
    }

请添加图片描述

第三种方式:分步查询

多对一映射的第三种方式,分布查询,两条SQL语句
还是使用association标签:

  • select属性:指定下一步SQL语句的id,全限定的接口方法名
  • column属性:指定要传给第二步的属性

分布查询第一步

第一步,根据id查询所有信息,这些信息含有cid(班级id),我们需要把这个cid传给下一步需要配置结果映射,当然属性名跟数据库表字段名一样,可以不用配(最好配上,因为能提高效率)
StudentMapper接口添加方法:

    /**
     * 通过id查询,同时获取学生关联的班级信息,分布查询第一步
     * @return
     */
    Student selectByIdStep1(Integer id);

StudentMapper.xml配置文件配置:

    <resultMap id="studentResultMapByStep1" type="Student">
        <id property="sid" column="sid" />
        <result property="sname" column="sname" />
        <association property="clazz"
                     select="com.advanced.mapping.mapper.ClazzMapper.selectByIdStep2"
                     column="cid"/>
    </resultMap>
    <select id="selectByIdStep1" resultMap="studentResultMapByStep1">
        select
            *
        from
            t_students
        where sid = #{sid}
    </select>

分布查询第二步

第二步,需要sql语句获取第一步传过来的cid,根据第一步传过来的cid查询班级信息
创建ClazzMapper接口,添加方法:

    /**
     * 分布查询第二步,根据cid获取班级信息
     * @param id
     * @return
     */
    Clazz selectByIdStep2(Integer id);

创建ClazzMapper.xml配置文件:

    <!--分布查询第二步,根据cid获取班级信息-->
    <select id="selectByIdStep2" resultType="Clazz">
        select
            *
        from
            t_clazz
        where cid = #{cid}
    </select>

测试程序

调用方法的时候,只要调用StudentMapper里面的第一步方法就可以了,Mybatis会自动调用第二步方法。

    @Test
    public void testSelectByIdStep() {
        SqlSession session = SqlSessionUtil.getSession();
        StudentMapper mapper = session.getMapper(StudentMapper.class);
        Student student = mapper.selectByIdStep1(3);
        System.out.println(student);
        SqlSessionUtil.close(session);
    }

发现Mybatis自动执行了两条sql语句
请添加图片描述

延迟加载

分步查询的优点:

  • 第一:复用性增强。可以重复利用。(拆成N多个步骤。每一个步骤都可以重复利用。)

  • 第二:采用这种分步查询,可以充分利用他们的延迟加载/懒加载机制。

     延迟加载(懒加载):
     延迟加载的核心原理是:用的时候再执行查询语句。不用的时候不查询。
     作用:提高性能。尽可能的不查,或者说尽可能的少查。来提高效率。
    

在mybatis当中怎么开启延迟加载呢?

  • association标签中添加fetchType=“lazy”,默认情况下是没有开启延迟加载的。
    这种在association标签中配置fetchType=“lazy”,是局部的设置,只对当前的association关联的sql语句起作用。

  • 在实际的开发中,大部分都是需要使用延迟加载的,所以建议开启全部的延迟加载机制:
    在mybatis核心配置文件中添加全局配置:lazyLoadingEnabled=true

     实际开发中的模式:
         把全局的延迟加载打开。
         如果某一步不需要使用延迟加载,在对应association标签设置:fetchType="eager"
    

测试程序:
还是使用分布查询的查询,开启懒加载,我们不把结果集全部输出,我们只输出学生的相关信息

	@Test
    public void testSelectByIdStep() {
        SqlSession session = SqlSessionUtil.getSession();
        StudentMapper mapper = session.getMapper(StudentMapper.class);

        Student student = mapper.selectByIdStep1(3);

        //System.out.println(student);
        System.out.println(student.getSname());
        //System.out.println(student.getClazz().getCname());
        SqlSessionUtil.close(session);
    }

请添加图片描述
我们发现,只访问学生信息,Mybatis不会执行第二条sql语句,只会执行你所访问信息的相关sql语句,松开访问班级信息的语句,发现执行了两条sql语句,这就是懒加载的好处。
请添加图片描述

一对多

一个班级对应多个学生,通常是在一的一方中有List集合属性。
⼀对多的实现通常包括两种实现方式:

  • 第⼀种方式:collection
  • 第⼆种方式:分步查询

Student不变
Clazz:

/**
 * 班级信息
 */
public class Clazz {
    private Integer cid;
    private String cname;
    private List<Student> students;

    public List<Student> getStudents() {
        return students;
    }

    public void setStudents(List<Student> students) {
        this.students = students;
    }

    public Clazz() {
    }

    public Clazz(Integer cid, String cname) {
        this.cid = cid;
        this.cname = cname;
    }
    @Override
    public String toString() {
        return "Clazz{" +
                "cid=" + cid +
                ", cname='" + cname + '\'' +
                ", students=" + students +
                '}';
    }

    public Integer getCid() {
        return cid;
    }

    public void setCid(Integer cid) {
        this.cid = cid;
    }

    public String getCname() {
        return cname;
    }

    public void setCname(String cname) {
        this.cname = cname;
    }
}

第⼀种方式:collection

一对多映射的第一种方式,一条SQL语句,在resultMap使用collection标签
collection标签:翻译为集合,与多对一的association标签标签用法差不多

  • property属性:提供要给集合类的属性名
  • javaType属性:要给集合的类名
  • id标签和result标签,用法与resultMap一样

在ClazzMapper接口添加方法:

	/**
     * 根据id查询,使用collection
     * @param id
     * @return
     */
    Clazz selectByCollection(Integer id);

ClazzMapper.xml配置:

    <resultMap id="clazzResultMap" type="Clazz">
        <id property="cid" column="cid"></id>
        <result property="cname" column="cname"></result>
        <collection property="students" ofType="Student">
            <id property="sid" column="sid"></id>
            <result property="sname" column="sname"></result>
        </collection>
    </resultMap>
    <select id="selectByCollection" resultMap="clazzResultMap">
        select
            c.cid,c.cname,s.sid,s.sname
        from
            t_clazz c left join t_students s on c.cid = s.cid
        where c.cid = #{cid}
    </select>

测试程序:
请添加图片描述

第⼆种方式:分步查询

一对多映射的第二种方式,分布查询,两条SQL语句,与多对一差不多

分步查询第一步

第一步,根据id查询所有信息,这些信息含有班级id(字段名为cid),我们需要把这个cid传给下一步需要配置结果映射,当然属性名跟数据库表字段名一样,可以不用配(最好配上,因为能提高效率)
ClazzMapper接口添加方法:

    /**
     * 根据id查询,分布查询,第一步
     * @param id
     * @return
     */
    Clazz selectByIdStep1(Integer id);

ClazzMapper.xml配置:

    <resultMap id="selectByIdStep" type="Clazz">
        <id property="cid" column="cid"></id>
        <result property="cname" column="cname"></result>
        <collection property="students"
                    select="com.advanced.mapping.mapper.StudentMapper.selectByIdStep2"
                    column="cid">
        </collection>
    </resultMap>
    <select id="selectByIdStep1" resultMap="selectByIdStep">
        select
            *
        from
            t_clazz
        where cid = #{cid}
    </select>

分步查询第二步

第二步,需要sql语句获取第一步传过来的cid,根据第一步传过来的cid查询学生信息
StudentMapper接口添加方法:

    /**
     * 分布查询第二步,根据cid获取学生信息
     * @param id
     * @return
     */
    List<Student> selectByIdStep2(Integer id);

StudentMapper.xml配置:

    <!--分布查询第二步,根据cid获取学生信息-->
    <select id="selectByIdStep2" resultType="Student">
        select
            *
        from
            t_students
        where cid = #{cid}
    </select>

测试程序

    @Test
    public void testSelectByIdStep() {
        SqlSession session = SqlSessionUtil.getSession();
        ClazzMapper mapper = session.getMapper(ClazzMapper.class);

        Clazz clazz = mapper.selectByIdStep1(1000);

        System.out.println(clazz);
        //System.out.println(clazz.getCname());
        //clazz.getStudents().stream().forEach(student -> System.out.println(student));
        SqlSessionUtil.close(session);
    }

请添加图片描述
分布查询同样能进行懒加载,这里就不演示了,参考多对一

多对多

在实际项目开发中,多对多关系也是非常常见的关系,比如,一个购物系统中,一个订单中可以购买多种商品,一种商品也可以属于多个不同的订单,订单和商品就是多对多的关系。对于数据库中多对多关系建议使用一个中间表来维护关系,中间表中的订单id作为外键参照订单表的id,商品id作为外键参照商品表的id。下面我们就用一个简单示例来看看MyBatis怎么处理多对多关系。
多对多关系,可以拆成两个一对多关系,也可以拆成两个多对一关系,就可以使用上面的方式进行处理,这里只演示订单到商品的多对多关系的分布查询,也就是拆成两个一对多,进行分布查询,其他自己推就可以了
Products 商品实体类:

public class Products {
    Integer id;//商品id
    String name;//商品名称
    Double price;//商品价格
    Integer pnum;//商品库存

    public Products() {
    }

    @Override
    public String toString() {
        return "Products{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", price=" + price +
                ", pnum=" + pnum +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }

    public Integer getPnum() {
        return pnum;
    }

    public void setPnum(Integer pnum) {
        this.pnum = pnum;
    }
}

Order 订单实体类

/**
 * 订单实体类
 */
public class Order {
    Integer id;//订单id
    String address;//订单地址
    String name;//收件人
    String phone;//手机号
    Date time;//订单日期

    List<Products> products;//多对多的关系映射

    public Order() {
    }

    @Override
    public String toString() {
        return "Order{" +
                "id=" + id +
                ", address='" + address + '\'' +
                ", name='" + name + '\'' +
                ", phone='" + phone + '\'' +
                ", time=" + time +
                ", products=" + products +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public Date getTime() {
        return time;
    }

    public void setTime(Date time) {
        this.time = time;
    }

    public List<Products> getProducts() {
        return products;
    }

    public void setProducts(List<Products> products) {
        this.products = products;
    }
}

分布查询第一步

第一步,订单跟关系表是一对多关系,我们根据订单的id查询,这些信息含有订单id,需要把这个id传给第二步进行查询,当然属性名跟数据库表字段名一样,可以不用配(最好配上,因为能提高效率)
创建OrderMapper接口,添加方法:

    /**
     * 分布查询第一步,根据id查询,订单到商品的多对多关系
     * @param id
     * @return
     */
    Order selectByIdStep1(Integer id);

创建OrderMapper.xml进行配置:

<resultMap id="OrderResultMapByStep1" type="Order">
        <id property="id" column="id" />
        <result property="address" column="address" />
        <result property="name" column="name" />
        <result property="phone" column="phone" />
        <result property="time" column="time" />
        <association property="products"
                     select="com.advanced.mapping.mapper.ProductsMapper.selectByIdStep2"
                     column="id" fetchType="lazy"/>
    </resultMap>
    <select id="selectByIdStep1" resultMap="OrderResultMapByStep1">

        select *
        from t_order
        where id = #{id}
            
    </select>

分布查询第二步

第二步,需要sql语句获取第一步传过来的订单id,根据第一步传过来的id,通过商品表和关系表多表查询查询出商品信息。
当然这里也可以拆成两步,通过第一步传过来的订单id查询出商品id,再传给第三步进行商品查询,把一条sql语句拆成两条,但是这种方式需要创建一个关系表的pojo类,以及对应的接口和映射文件,比较麻烦,最重要的是查询出来的关系表的数据在这个业务里面基本没用,不会去用,比较鸡肋。
这里之间就一条语句进行查询了
创建ProductsMapper接口,添加方法:

    /**
     * 分布查询第二步,订单到商品的多对多关系
     * @param id
     * @return
     */
    List<Products> selectByIdStep2(Integer id);

创建ProductsMapper.xml进行配置:

    <select id="selectByIdStep2" resultType="Products">

        select p.*
        from t_products p left join t_orderitem o on p.id = o.product_id
        where o.order_id = #{id}

    </select>

测试程序

    @Test
    public void testSelectByIdStep(){
        SqlSession session = SqlSessionUtil.getSession();
        OrderMapper mapper = session.getMapper(OrderMapper.class);
        Order order = mapper.selectByIdStep1(2);
        System.out.println(order);
    }

请添加图片描述
同样也能进行懒加载,参考多对一方式

一对一

在实际项目开发中,一对一也是非常常见的,例如一个大学生只有一个学号,一个学号只属于一个学生。同样,人与身份证也是一对一的级联关系。
一对一,两种方案:一、主键共享。二、外键唯一
使用分布查询的话,把id传给第二步,参考上面进行推导即可

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

忆亦何为

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值