第四天:mybatis的缓存和注解开发

第四天:mybatis的缓存和注解开发

mybatis框架 学习计划

共四天
第一天:mybatis入门
mybatis的概述
mybatis的环境搭建
mybatis入门案例
自定义mybatis框架(主要的目的是为了让大家了解mybatis中执行细节)
第二天:mybatis基本使用
mybatis的单表crud操作
mybatis的参数和返回值
mybatis的dao编写
mybatis配置的细节
几个标签的使用
第三天:mybatis的深入和多表
mybatis的连接池
mybatis的事务控制及设计的方法
mybatis的多表查询
一对多(多对一)
多对多
第四天:mybatis的缓存和注解开发
mybatis中的加载时机(查询的时机)
mybatis中的一级缓存和二级缓存
mybatis的注解开发
单表CRUD
多表查询

4.1 Mybatis 延迟加载策略(查询的时机)

​ 通过前面的学习,我们已经掌握了 Mybatis 中一对一,一对多,多对多关系的配置及实现,可以实现对象的关联查询。实际开发过程中很多时候我们并不需要总是在加载用户信息时就一定要加载他的账户信息。此时就是我们所说的延迟加载。

4.1.1 为什么要用延迟加载策略

延迟加载:
就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载.
好处:

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

实现需求

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

实际使用场景

在对应的四种表关系中:一对多,一对一,多对多
    一对多,多对多:通常情况下我们都是采用延迟加载。
    一对一:通常情况下我们都是采用立即加载。

4.1.2 使用 assocation 实现延迟加载(一对一)

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

1. 编写账户实体类

/**
 * 账号实体类
 */
public class Account implements Serializable {

    private  Integer id;

    private Integer uid;

    private Double money;

    //从表实体应该包含一个主体实体的对象引用
    private User user;

// getter + setter 省略

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", uid=" + uid +
                ", money=" + money +
                '}';
    }
}

2.账户的持久层 DAO 接口

public interface AccountDao {

    /**
     * 查询所有账号,同时还要获取到当前账号的所属用户信息
     * @return
     */
    List findAll();
}

3. 账户的持久层映射文件

    
    
        
        
        
        
        
        

    

    
    

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

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

/**
 * 根据Id查询用户
 */
User findById(Integer id);

    
    

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

public class AccountTest {

    private  InputStream in;
    private  SqlSession sqlSession;
    private AccountDao accountDao;

    @Before  // test方法执行之前执行
    public void init() throws IOException {
            //1.读取配置文件
             in = Resources.getResourceAsStream("SqlMapConfig.xml");
            //2.创建 SqlSessionFactory 的构建者对象
            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();

            //3.使用构建者创建工厂对象 SqlSessionFactory
            SqlSessionFactory factory = builder.build(in);

            //4.使用 SqlSessionFactory 生产 SqlSession 对象
            sqlSession = factory.openSession();

            //5.使用 SqlSession 创建 dao 接口的代理对象
            accountDao = sqlSession.getMapper(AccountDao.class);

    }

    @After // test方法执行之后执行
    public void destroy() throws IOException {
        //提交事务
        sqlSession.commit();
        //7.释放资源
        sqlSession.close();
        in.close();
    }

    /**
     * 查询所有账号
     */
    @Test
    public void testFindAll()  {
        List accounts = accountDao.findAll();
        for(Account account : accounts) {
           // System.out.println(account);
        }
    }
 
}

6 测试结果

DEBUG example.dao.AccountDao.findAll  - ==>  Preparing: select * from account 
DEBUG example.dao.AccountDao.findAll  - ==> Parameters: 
DEBUG example.dao.AccountDao.findAll  - <==      Total: 3
DEBUG ansaction.jdbc.JdbcTransaction  - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@612679d6]
DEBUG ansaction.jdbc.JdbcTransaction  - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@612679d6]
DEBUG source.pooled.PooledDataSource  - Returned connection 1629911510 to pool.

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

4.1.3 使用 Collection 实现延迟加载(一对多)

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

1. 在 User 实体类中加入List属性

/**
 * 用户的实体类
 */
public class User implements Serializable {

    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;

    // 一对多关系映射; 主表实体应该包含从表实体的集合引用
    private List accounts;
    // 多对多的关系映射: 一个用户可以具有多个角色
    private List roles;

// getter setter 省略

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

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

/**
 * 查询所有用户操作
 * @return
 */
List findAll();

/**
 *查询所有账户 根据用户id 
 * @return
 */
List findAllByUid(Integer uid);

3. 编写用户持久层映射配置



    
    
    
    
    
   
   
    

    






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

4. 编写账户持久层映射配置


5. 测试只加载用户信息

 package org.example.test;


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.example.dao.UserDao;
import org.example.domain.Account;
import org.example.domain.QueryVo;
import org.example.domain.User;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import sun.nio.ch.Net;

import java.io.IOException;
import java.io.InputStream;
import java.util.*;

/**
 *  mybatis 入门案例
 */
public class UserTest {

    private  InputStream in;
    private  SqlSession sqlSession;
    private  UserDao userDao;

    @Before  // test方法执行之前执行
    public void init() throws IOException {
            //1.读取配置文件
             in = Resources.getResourceAsStream("SqlMapConfig.xml");
            //2.创建 SqlSessionFactory 的构建者对象
            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();

            //3.使用构建者创建工厂对象 SqlSessionFactory
            SqlSessionFactory factory = builder.build(in);

            //4.使用 SqlSessionFactory 生产 SqlSession 对象
            sqlSession = factory.openSession();

            //5.使用 SqlSession 创建 dao 接口的代理对象
            userDao = sqlSession.getMapper(UserDao.class);

    }

    @After // test方法执行之后执行
    public void destroy() throws IOException {
        //提交事务
        sqlSession.commit();
        //7.释放资源
        sqlSession.close();
        in.close();
    }


    @Test
    public void testFindAll()  {
        //6.使用代理对象执行查询所有方法
        List users = userDao.findAll();
    }

}

6 测试结果:

测试结果如下:

DEBUG ansaction.jdbc.JdbcTransaction  - Opening JDBC Connection
DEBUG source.pooled.PooledDataSource  - Created connection 1629911510.
DEBUG ansaction.jdbc.JdbcTransaction  - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@612679d6]
DEBUG rg.example.dao.UserDao.findAll  - ==>  Preparing: select * from user 
DEBUG rg.example.dao.UserDao.findAll  - ==> Parameters: 
DEBUG rg.example.dao.UserDao.findAll  - <==      Total: 10
DEBUG ansaction.jdbc.JdbcTransaction  - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@612679d6]
DEBUG ansaction.jdbc.JdbcTransaction  - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@612679d6]
DEBUG source.pooled.PooledDataSource  - Returned connection 1629911510 to pool.

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

4.2 mybatis中的一级缓存和二级缓存

4.2.1 Mybatis 一级缓存

4.2.1.1证明一级缓存的存在

​ 一级缓存是 SqlSession级别的缓存,只要 SqlSession 没有 flushclose,它就存在。

1. 编写用户持久层 Dao 接口
public interface UserDao {
    /**
     * 根据 id 查询
     * @param userId
     * @return
     */
    User findById(Integer userId);
}
2. 编写用户持久层映射文件



    
    

3. 编写测试方法
@Test
public void testFindById() {
    User user = userDao.findById(41);
    System.out.println("第一次查询的用户:"+user);
    User user2 = userDao.findById(41);
    System.out.println("第二次查询用户:"+user2);
    System.out.println(user == user2);
}
4. 测试结果
DEBUG ansaction.jdbc.JdbcTransaction  - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@e720b71]
DEBUG g.example.dao.UserDao.findById  - ==>  Preparing: select * from user where id = ? 
DEBUG g.example.dao.UserDao.findById  - ==> Parameters: 49(Integer)
DEBUG g.example.dao.UserDao.findById  - <==      Total: 1
第一次查询的用户:org.example.domain.User@17d0685f
第二次查询的用户:org.example.domain.User@17d0685f
true

    我们可以发现,虽然在上面的代码中我们查询了两次,但最后只执行了一次数据库操作,这就是 Mybatis 提供给我们的一级缓存在起作用了。因为一级缓存的存在,导致第二次查询 id 为 41 的记录时,并没有发出 sql 语句从数据库中查询数据,而是从一级缓存中查询。

4.2.1.2 一级缓存的分析

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

4.2.2 Mybatis 二级缓存

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

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

4.2.2.1 二级缓存的开启与关闭

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

    
    

因为 cacheEnabled 的取值默认就为 true,所以这一步可以省略不配置。为 true 代表开启二级缓存;为
false 代表不开启二级缓存。
第二步:配置相关的Mapper 映射文件
标签表示当前这个 mapper 映射将使用二级缓存,区分的标准就看 mapper 的 namespace 值。



    
    

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


将 UserDao.xml 映射文件中的