4.Mybatis-04 Mybatis 延迟加载策略,缓存及注解开发

主要内容

  • Mybatis 延迟加载策略
  • Mybatis 缓存
  • Mybatis 注解开发

Mybatis 延迟加载策略

1.1 何为延迟加载?

延迟加载:就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载.
好处:先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。
坏处:因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降。

1.2 实现需求

需求:
查询账户(Account)信息并且关联查询用户(User)信息。如果先查询账户(Account)信息即可满足要
求,当我们需要查询用户(User)信息时再查询用户(User)信息。把对用户(User)信息的按需去查询就是延迟加载。
mybatis第三天实现多表操作时,我们使用了 resultMap来实现一对一,一对多,多对多关系的操作。主要
是通过 association、 collection 实现一对一及一对多映射。
association、 collection 具备延迟加载功能。

1.3 使用 assocation 实现延迟加载

需求:
查询账户信息同时查询用户信息。

1.3.1 账户的持久层 DAO 接口

package com.ding.dao;

import com.ding.domain.Account;
import java.util.List;

public interface IAccountDao {
    /**
     * 查询所有账户,同时获取账户的所属用户名称以及它的地址信息
     * @return
     */
    List findAll();
}

1.3.2 账户的持久层映射文件




    
    
        
        
        
        
        
        
    
    

select: 填写我们要调用的 select 映射的 id
column : 填写我们要传递给 select 映射的参数

1.3.3 用户的持久层接口和映射文件

public interface IUserDao {
/**
* 根据 id 查询
* @param userId
* @return
*/
User findById(Integer userId);
}

    

1.3.4 开启 Mybatis 的延迟加载策略

进入 Mybaits 的官方文档,找到 settings 的说明信息:


iShot2021-01-24 15.11.33.png

我们需要在 Mybatis 的配置文件 SqlMapConfig.xml 文件中添加延迟加载的配置。



  
  

1.3.5 编写测试只查账户信息不查用户信息。

package com.ding;

import com.ding.dao.IAccountDao;
import com.ding.domain.Account;
import com.ding.domain.AccountUser;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.apache.ibatis.io.Resources;

import java.io.InputStream;
import java.util.List;

public class AccountTest {
    private InputStream in ;
    private SqlSessionFactory factory;
    private SqlSession session;
    private IAccountDao accountDao;

    @Before//在测试方法执行之前执行
    public void init()throws Exception {
        //1.读取配置文件
        in =  Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.创建构建者对象
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        //3.创建 SqlSession 工厂对象
        factory = builder.build(in);
        //4.创建 SqlSession 对象
        session = factory.openSession();
        //5.创建 Dao 的代理对象
        accountDao = session.getMapper(IAccountDao.class);
    }


    @After//在测试方法执行完成之后执行
    public void destroy() throws Exception{
        session.commit();
        //7.释放资源
        session.close();
        in.close();
    }


    @Test
    public void testFindAll1() {
        List accounts = accountDao.findAll();
//        for(Account au : accounts) {
//            System.out.println(au);
//            System.out.println(au.getUser());
//        }
    }
}

测试结果如下:


iShot2021-01-24 15.16.18.png

因为本次只是将 Account对象查询出来放入 List 集合中,并没有涉及到 User对象,所以就没有
发出 SQL 语句查询账户所关联的 User 对象的查询。

1.4 使用 Collection 实现延迟加载

同样我们也可以在一对多关系配置的结点中配置延迟加载策略。
结点中也有 select 属性, column 属性。
需求:
完成加载用户对象时,查询该用户所拥有的账户信息。

1.4.1 在 User 实体类中加入 List属性

package com.ding.domain;

import java.io.Serializable;
import java.util.Date;
import java.util.List;

public class User implements Serializable {
    private Integer id;
    private String username;
    private String address;
    private Date birthday;
    private String sex;

    public List getAccounts() {
        return accounts;
    }

    public void setAccounts(List accounts) {
        this.accounts = accounts;
    }

    private List accounts;


    public String getUsername() {
        return username;
    }

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

    public String getAddress() {
        return address;
    }

    public Integer getId() {
        return id;
    }

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

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

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public String getSex() {
        return sex;
    }

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

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", address='" + address + '\'' +
                ", birthday=" + birthday +
                ", sex='" + sex + '\'' +
                ", accounts=" + accounts +
                '}';
    }
}

1.4.2 编写用户和账户持久层接口的方法

package com.ding.dao;
import com.ding.domain.Account;
import com.ding.domain.User;
import java.util.List;

public interface IUserDao {

    /**
     * 查询所有用户,同时获取出每个用户下的所有账户信息
     * @return
     */
    List findAll();
    /**
     * 根据用户 id 查询账户信息
     * @param uid
     * @return
     */
    List findByUid(Integer uid);
}

1.4.3 编写用户持久层映射配置





    
        
        
        
        
        
        
        
        
    
    
    


标签:
主要用于加载关联的集合对象
select 属性:
用于指定查询 account 列表的 sql 语句,所以填写的是该 sql 映射的 id
column 属性:
用于指定 select 属性的 sql 语句的参数来源,上面的参数来自于 user 的 id 列,所以就写成 id 这一
个字段名了

1.4.4 编写账户持久层映射配置


    

1.4.5 测试只加载用户信息

package com.ding;

import com.ding.dao.IUserDao;
import com.ding.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.InputStream;
import java.util.Date;
import java.util.List;

public class test {
    private InputStream in ;
    private SqlSessionFactory factory;
    private SqlSession session;
    private IUserDao userDao;

    @Test
    public void testFindAll() {
        //6.执行操作
        List users = userDao.findAll();
//        for(User user : users) {
//            System.out.println("-------每个用户的内容---------");
//            System.out.println(user);
//            System.out.println(user.getAccounts());
//        }
    }
    @Before//在测试方法执行之前执行
    public void init()throws Exception {
        //1.读取配置文件
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.创建构建者对象
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        //3.创建 SqlSession 工厂对象
        factory = builder.build(in);
        //4.创建 SqlSession 对象
        session = factory.openSession();
        //5.创建 Dao 的代理对象
        userDao = session.getMapper(IUserDao.class);
    }
    @After//在测试方法执行完成之后执行
    public void destroy() throws Exception{
        session.commit();
        //7.释放资源
        session.close();
        in.close();
    }
}

测试结果如下:


iShot2021-01-24 15.44.00.png

我们发现并没有加载 Account 账户信息。

Mybatis 缓存

像大多数的持久化框架一样, Mybatis 也提供了缓存策略,通过缓存策略来减少数据库的查询次数, 从而提高性能。
Mybatis 中缓存分为一级缓存,二级缓存。


iShot2021-01-24 15.44.47.png

2.1 Mybatis 一级缓存

2.1.1 证明一级缓存的存在

一级缓存是 SqlSession 级别的缓存,只要 SqlSession 没有 flush 或 close,它就存在。

2.1.2 一级缓存的分析

一级缓存是 SqlSession 范围的缓存,当调用 SqlSession 的修改,添加,删除, commit(), close()等方法时,就会清空一级缓存。


iShot2021-01-24 15.46.42.png

第一次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,如果没有,从数据库查
询用户信息。
得到用户信息,将用户信息存储到一级缓存中。
如果 sqlSession 去执行 commit 操作(执行插入、更新、删除),清空 SqlSession 中的一级缓存,这样
做的目的为了让缓存中存储的是最新的信息,避免脏读。
第二次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,缓存中有,直接从缓存
中获取用户信息。

2.2 Mybatis 二级缓存

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

2.2.1 二级缓存结构图

iShot2021-01-24 15.47.51.png

首先开启 mybatis 的二级缓存。
sqlSession1 去查询用户信息,查询到用户信息会将查询数据存储到二级缓存中。
如果 SqlSession3 去执行相同 mapper 映射下 sql,执行 commit 提交, 将会清空该 mapper 映射下的二
级缓存区域的数据。
sqlSession2 去查询与 sqlSession1 相同的用户信息, 首先会去缓存中找是否存在数据,如果存在直接从
缓存中取出数据。

2.2.2 二级缓存的开启与关闭

2.2.2.1 第一步:在 SqlMapConfig.xml 文件开启二级缓存





因为 cacheEnabled 的取值默认就为 true,所以这一步可以省略不配置。为 true 代表开启二级缓存;为
false 代表不开启二级缓存。

2.2.2.2 第二步:配置相关的 Mapper 映射文件

标签表示当前这个 mapper 映射将使用二级缓存,区分的标准就看 mapper 的 namespace 值。







2.2.2.3 第三步: 配置 statement 上面的 useCache 属性



将 UserDao.xml 映射文件中的