MyBatis(3)学习内容

一、多表联合查询

1.1 多表联合查询概述

在开发过程中单表查询不能满足项目需求分析功能,对于复杂业务来讲,关联的表有几张,甚至几十张并且表与表之间的关系相当复杂。为了能够实现复杂功能业务,就必须进行多表查询,在mybatis中提供了多表查询的结果时映射标签,可以实现表之间的一对一、一对多、多对一、多对多关系映射。

二、MyBatis多表查询之一对一

2.1 表准备

使用之前导入的用户表和订单表:

2.2 准备项目环境

2.2.1 构建maven项目,添加依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.sy.mapper</groupId>
    <artifactId>day05_mybatis</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>


    <dependencies>
        <!--导入mysql依赖-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.49</version>
        </dependency>

        <!--导入junit依赖-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>

        <!--log4j打印日志-->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.6</version>
        </dependency>

    </dependencies>


</project>
2.2.2 项目中加入相关配置文件

(db.properties,log4j.properties,SqlMapConfig.xml和MybatisUtils工具类文件等)

2.2.3 创建orders和user表对应的实体类
package com.sy.pojo;

import java.awt.*;
import java.util.List;

public class User {
    private int id; //编号id
    private String username; //用户名
    private String password; //密码
    private int age; //年龄
    private String sex; //性别
    private String email; //邮箱

    private List<Order> orderList;

    public List<Order> getOrderList() {
        return orderList;
    }

    public void setOrderList(List<Order> orderList) {
        this.orderList = orderList;
    }

    public User() {
    }

    public User(String username, String password, int age, String sex, String email) {
        this.username = username;
        this.password = password;
        this.age = age;
        this.sex = sex;
        this.email = email;
    }

    public User(int id, String username, String password, int age, String sex, String email) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.age = age;
        this.sex = sex;
        this.email = email;
    }

    public int getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                ", email='" + email + '\'' +
                ", orderList=" + orderList +
                '}';
    }

}

package com.sy.pojo;

public class Order {
    // 订单id
    private Integer id;
    // 用户id,该属性名和数据库的字段名不一致
    private Integer userId;
    // 订单号
    private String number;
    // 订单创建时间
    private String createTime;
    // 备注
    private String note;
    private User user;

    public Order() {
    }

    public Order(Integer id, Integer userId, String number, String createTime, String note) {
        this.id = id;
        this.userId = userId;
        this.number = number;
        this.createTime = createTime;
        this.note = note;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public Integer getId() {
        return id;
    }

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

    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }

    public String getCreateTime() {
        return createTime;
    }

    public void setCreateTime(String createTime) {
        this.createTime = createTime;
    }

    public String getNote() {
        return note;
    }

    public void setNote(String note) {
        this.note = note;
    }

    @Override
    public String toString() {
        return "Order{" +
                "id=" + id +
                ", userId=" + userId +
                ", number='" + number + '\'' +
                ", createTime='" + createTime + '\'' +
                ", note='" + note + '\'' +
                ", user=" + user +
                '}';
    }
}

2.3 需求:查询所有订单信息,关联查询下单用户信息

注意:因为一个订单信息只会是一个人下的订单,所以从查询订单信息出发关联查询用户信息为一对一查询。如果从用户信息出发查询用户下的订单信息则为一对多查询,因为一个用户可以下多个订单。

2.4 方法一: 嵌套结果方式

介绍:

嵌套结果 是使用嵌套结果映射来处理重复的联合结果的子集。

特点:

        只有一条复杂的 SQL 语句(多表连接)

方法:

使用resultMap,定义专门的resultMap用于映射一对一查询结果。

2.4.1 改造pojo

在Order类中加入User属性,user属性中用于存储关联查询的用户信息,因为订单关联查询用户是一对一关系,所以这里使用单个User对象存储关联查询的用户信息。

改造Order如下图:

最后再重新生成toString方法即可

2.4.2 Mapper接口

编写OrderMapper接口, 创建查询订单和订单对应用户的方法:queryOrderUserResultMap

2.4.3 OrderMapper映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace:命名空间 -->
<mapper namespace="com.sy.mapper.OrderMapper">

    <!--查询所有订单信息,关联查询下单用户信息 方式一:嵌套结果方式-->
    <select id="findOrderUserResultMap" resultMap="orderUserResultMap">
        SELECT
            o.id oid,
            o.user_id,
            o.number,
            o.create_time,
            o.note,
            u.id uid,
            u.username,
            u.password,
            u.age,
            u.sex,
            u.email
        FROM
            `orders` o,
            `t_user` u
        WHERE
            o.user_id = u.id
    </select>
    <!--使用resultMap设置结果集和实体类中属性的映射关系-->
    <resultMap id="orderUserResultMap" type="order">
        <!--两部分数据: 订单 + 用户信息-->
        <id column="oid" property="id"/>
        <result column="user_id" property="userId"/>
        <result column="number" property="number"/>
        <result column="create_time" property="createTime"/>
        <result column="note" property="note"/>
        <!--设置数据之间的一对一映射关系使用association-->
        <!--
            property="user": 一对一关联后封装的数据所对应的属性名, 代表的是Order对象中的user属性
            javaType="com.sy.pojo.User": 指定user是属于什么具体类型
            <id column="user_id" property="id"></id> 写id和user_id是一样的效果, 因为两者等价
        -->
        <association property="user" javaType="com.sy.pojo.User">
            <id column="user_id" property="id"/>
            <result column="username" property="username"/>
            <result column="password" property="password"/>
            <result column="age" property="age"/>
            <result column="sex" property="sex"/>
            <result column="email" property="email"/>
        </association>
    </resultMap>

<mapper>
2.4.4 测试程序

2.5 方法二: 嵌套查询方式

介绍:

嵌套查询 是指通过执行另外一条 SQL 映射语句来返回预期的复杂类型;

特点:

        嵌套查询是在查询 SQL 后再进行一个(子)查询、会执行多条 SQL 语句

方法:

先查询全部的订单数据,再查询每个订单数据对应的用户;

2.5.1 创建UserMapper接口

2.5.2 创建UserMapper.xml映射文件

2.5.3 创建OrderMapper接口

2.5.4 创建OrderMapper.xml映射文件

2.5.5 测试程序

三、MyBatis多表查询之一对多

3.1 案例需求

案例:查询所有用户信息及用户关联的订单信息。

用户信息和订单信息为一对多关系。

3.2 方式一: 嵌套结果方式

3.2.1 修改pojo类

3.2.2 创建UserMapper接口

3.2.3 创建UserMapper.xml映射文件
 <!--查询所有用户信息, 关联查询出订单信息  方法一:嵌套结果方式-->
    <select id="findUserOrders" resultMap="userResultMap">
        SELECT
            u.id uid,
            u.username,
            u.password,
            u.age,
            u.sex,
            u.email,
            o.id,
            o.user_id,
            o.number,
            o.create_time,
            o.note
        FROM
            t_user u,
            orders o
        WHERE
            u.id = o.user_id
    </select>
    <resultMap id="userResultMap" type="user">
        <id column="uid" property="id"/>
        <result column="username" property="username"/>
        <result column="password" property="password"/>
        <result column="age" property="age"/>
        <result column="sex" property="sex"/>
        <result column="email" property="email"/>
        <!--
             collection: 设置一对多的映射关系
             property: 封装pojo实体类中订单属性的属性名
             javaType="list": orderList集合的类型
             ofType="User": orderList集合的泛型的类型
         -->
        <collection property="orderList" javaType="list" ofType="order">
            <id column="id" property="id"></id>
            <result column="user_id" property="userId"></result>
            <result column="number" property="number"></result>
            <result column="create_time" property="createTime"></result>
            <result column="note" property="note"></result>
        </collection>
    </resultMap>
3.2.4 测试程序

3.3 方式二: 嵌套查询方式

先查询所有的用户,再根据用户的id查询对应的订单

3.3.1 创建OrderMapper接口

在OrderMapper接口中定义根据用户id查询订单的方法:

3.3.2 创建OrderMapper.xml文件

3.3.3 创建UserMapper接口

接口中加入查询所有用户的方法,并查询用户对应的订单:

3.3.4 创建UserMapper.xml映射文件

3.3.5 测试程序

四、MyBatis多表查询之多对多

4.4.1 创建表

理解:

学生和课程是多对多的关系,一个学生可以对应多个课程,一个课程可以被多个学生选;

学生表 => 课程表(一个学生可以选择多个课程):一对多

课程表 => 学生表(一个课程可以被多个学生选择 ):一对多

创建学生表、课程表、课程学生中间表:

CREATE TABLE `course` (
  `cid` INT(10) NOT NULL AUTO_INCREMENT,
  `cname` VARCHAR(50) DEFAULT NULL,
  PRIMARY KEY (`cid`)
);
INSERT  INTO `course`(`cid`,`cname`) VALUES
 (1,'java'),(2,'css'),(3,'js'),(4,'c++'),(5,'php'),(6,'语文'),(7,'数学');


CREATE TABLE `student` (
  `sid` INT(10) NOT NULL AUTO_INCREMENT,
  `NAME` VARCHAR(50) DEFAULT NULL,
  PRIMARY KEY (`sid`)
);
INSERT  INTO `student`(`sid`,`NAME`) VALUES
 (1,'张三'),(2,'李四'),(3,'柳岩'),(4,'小猪'),(5,'王宝强'),(6,'李晨');


CREATE TABLE `student_course` (
  `id` INT(11) NOT NULL AUTO_INCREMENT,
  `sid` INT(10) DEFAULT NULL,
  `cid` INT(10) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `fk_sid` (`sid`),
  KEY `fk_cid` (`cid`),
  CONSTRAINT `fk_cid` FOREIGN KEY (`cid`) REFERENCES `course` (`cid`),
  CONSTRAINT `fk_sid` FOREIGN KEY (`sid`) REFERENCES `student` (`sid`)
);
INSERT  INTO `student_course`(`id`,`sid`,`cid`) VALUES
 (1,1,2),(2,2,1),(3,3,1),(4,4,1),(5,5,1),(6,1,3),(7,1,4),(8,2,5),(9,2,7);

4.4.2 创建表对应的实体类

学生实体类

package com.sy.pojo;

import java.util.List;

public class Student {
    private Integer sid;
    private String name;

    //放置学生选择的课程数据
    private List<Course> courseList;

    public List<Course> getCourseList() {
        return courseList;
    }

    public void setCourseList(List<Course> courseList) {
        this.courseList = courseList;
    }

    public Student() {
    }

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

    public Integer getSid() {
        return sid;
    }

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

    public String getName() {
        return name;
    }

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

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

课程实体类

package com.sy.pojo;

import java.util.List;

public class Course {
    private Integer cid;
    private String cname;

    private List<Student> studentList;

    public List<Student> getStudentList() {
        return studentList;
    }

    public void setStudentList(List<Student> studentList) {
        this.studentList = studentList;
    }

    public Course() {
    }

    public Course(Integer cid, String cname) {
        this.cid = cid;
        this.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;
    }

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

4.4.3 方法一: 嵌套结果方式

4.4.3.1 创建StudentMapper接口

4.4.3.2 创建StudentMapper.xml映射文件

4.4.3.3 测试程序

4.4.4 方法二: 嵌套查询方式(根据学生姓名查询学生选择的课程)

4.4.3.1 创建CourseMapper接口

创建根据学生的sid查询学生选择的课程的方法:

4.4.3.2 创建CourseMapper.xml映射文件

4.4.3.3 创建StudentMapper接口

4.4.3.4 创建StudentMapper.xml映射文件

4.4.3.5 测试程序

五、延迟加载策略

5.1 延迟加载的简介

什么是延迟加载?

延迟加载(lazy load)是(也称为懒加载)关联关系对象默认的加载方式,延迟加载机制是为了避免一些无谓的性能开销而提出来的,所谓延迟加载就是当在真正需要数据的时候,才真正执行数据加载操作。

延迟加载,可以简单理解为,只有在使用的时候,才会发出sql语句进行查询

5.2 为什么要使用延迟加载

减少访问数据库的频率,我们要访问的数据量过大时,明显用缓存不太合适,因为内存容量有限为了减少并发量,减少系统资源的消耗。

5.3 配置延迟加载策略,并测试结果

在mybatis中使用resultMap来实现一对一,一对多,多对多关系的操作。主要是通过 association、collection 实现一对一及一对多映射。association、collection 具备延迟加载功能。

延迟加载只有在嵌套查询方式中才能有效

5.3.1 局部延迟加载

修改OrderMapper.xml配置文件

把之前写的OrderMapper.xml中,嵌套查询方式,查询所有订单以及订单对应的用户的statement进行修改:

<select id="findOrderUserResultMap02" resultMap="orderUserResultMap02">
  SELECT * FROM `orders`
</select>

<!-- 使用resultMap设置结果集和实体类中属性的映射关系 -->
<resultMap id="orderUserResultMap02" type="order">
  <!--两部分数据: 订单 + 用户信息-->
  <id column="id" property="id"></id>
  <result column="user_id" property="userId"></result>
  <result column="number" property="number"></result>
  <result column="createtime" property="createtime"></result>
  <result column="note" property="note"></result>

  <!--
    fetchType="lazy": 给用户的查询加入延迟加载, fetchType的默认值是eager,
    加入延迟加载后, 只要用到订单对应的用户数据的时候, 才会去查询用户数据,
    不加入延迟加载, 只要是查询订单就会查询对应的用户数据
  -->
  <association property="user" javaType="user" fetchType="lazy"
    select="com.sy.mapper.UserMapper.findUserById"
    column="user_id">
  </association>
</resultMap>

配置文件对应的接口:

/**
     * 查询所有订单信息, 以及订单对应的用户信息(嵌套查询实现)
     * @return
     */
List<Order> findOrderUserResultMap02();

总结:

        相关联的查询标签上加 fetchType="lazy"

        fetchType默认值为eager ;立即加载,Lazy为延时加载。

        延迟加载只有在嵌套查询方式中才能有效

5.3.2 全局延迟加载

如果希望所有关联都需要延时加载,可以在mybatis的核心配置文件中进行配置,不用在collection或association中指定。默认全局开启。

<settings>

    <!-- 全局开启延迟加载 -->

    <setting name="lazyLoadingEnabled" value="true"/>

    <!-- 当启用时,有会话时,会执行延迟加载 -->

    <setting name="aggressiveLazyLoading" value="false"/>

</settings>

六、MyBatis 缓存

6.1 缓存简介

缓存是存在内存中的临时数据。将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。使用缓存能够减少和数据库的交互次数,减少系统开销,提高系统效率。经常查询并且不经常改变的数据能够使用缓存。

MyBatis系统中默认定义了两级缓存:

        一级缓存:默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)

        二级缓存:二级缓存需要手动开启和配置,它是基于Mapper.xml级别的缓存。

6.2 一级缓存

6.2.1 一级缓存介绍

mybatis一级缓存一种是SESSION级别的,针对同一个会话SqlSession中,执行多次条件完全相同的同一个sql,那么会共享这一缓存。

一级缓存mybatis已经为我们自动开启,不用我们手动操作,而且我们是关闭不了的!!但是我们可以手动清除缓存。

特点:

        (1)自带的,不能卸载,但是我们可以手动清除缓存。

        (2)SqlSession级别的缓存,使用无需配置。

6.2.2 测试一级缓存的清空

当我们在两次查询之间做增、删、改操作都会把一级缓存清空,因为不清空就不能保证缓存中的数据与数据库中数据的一致性,可能会读取不正确的数据。

总结:

一级缓存清空三种方式:

(1)clearCache()

(2)手动提交事务: commit();

(3)执行数据库的写操作: delete insert update

注意:

SqlSession对象, 它是应用程序与持久层之间执行交互操作的对象, 主要作用是执行持久化操作; 类似于JDBC中的Connection连接对象, SqlSession对象包含了执行SQL语句的操作方法, 由于底层封装了JDBC连接, 所以可以直接使用SqlSession对象执行以映射的SQL语句; SqlSession对象一旦关闭, 就代表Connection连接对象关闭, 查询结束, 再次查询就会报错;

如果当前的SqlSession对象一旦关闭, 想要再次执行查询方法, 需要重新创建SqlSession对象, 并执行查询方法即可, 如果两个不同的SqlSession对象, 我能不能也让它走缓存呢?

可以使用二级缓存;

6.2.3 测试程序

使用UserMapper接口中,根据用户id查询用户的方法缓存测试:

UserMapper.xml映射文件:

测试程序:  两次查询之间执行增删或者clearCache、commit都会清空一级缓存

结果展示:

6.3 二级缓存

6.3.1 二级缓存介绍

二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存,它是基于Mapper.xml文件级别的缓存,一个namespace命名空间对应一个二级缓存。

二级缓存工作机制为:

一个会话查询的一条数据就会被放在当前会话的一级缓存中;

如果当前会话关闭了,这个会话对应的一级缓存就没了;但是二级缓存开启后,一级缓存中的数据被保存到二级缓存中,新的会话查询信息,就可以从二级缓存中获取内容;

不同的mapper查出的数据会放在自己对应的缓存(map)中

总结:

二级缓存:默认是不开启的,使用需要配置。

二级缓存是 mapper 映射级别的缓存,多个 SqlSession 去操作同一个 Mapper 映射的 sql 语句,多个 SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。

6.3.2 二级缓存的开启与关闭

在SqlMapConfig.xml 文件开启二级缓存

配置相关的 Mapper 映射文件

注意:对应的实体类记得实现Serializable接口,因为往二级缓存中存放数据需要序列化

配置 statement 中的 useCache 属性

测试程序:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值