04-Mybatis延迟加载和缓存(基于XML配置)

本文详细介绍了Mybatis的延迟加载机制,包括一对一和一对多的延迟加载配置,并通过案例展示了如何在实际操作中应用。同时,文章还探讨了一级缓存和二级缓存的使用,解释了它们的工作原理以及如何开启和关闭。测试结果显示,延迟加载能够有效减少数据库查询,提高性能,而缓存策略则进一步减少了重复查询。
摘要由CSDN通过智能技术生成

《Mybatis使用手册》(第4篇 / 共5篇,持续更新),收藏 + 关注 不迷路,希望对小伙伴们有所帮助哦~

源码链接在文末 ↓ ↓ ↓


在这里插入图片描述

1、Mybatis延迟加载策略

1.1、延迟加载介绍

Mybatis延迟加载就是在用到数据时才进行加载,不需要使用时,则不加载数据。延迟加载也叫做懒加载。

  • 优点

先进行单表查询,需要使用从表数据时再关联从表进行查询,提高了数据库的性能。

  • 缺点

在进行大批量数据查询时,可能造成用户等待时间增长,降低用户体验。

1.1.1、延迟加载案例

在一对多的查询中,一个用户有多个账户。
查询用户时,需要使用账户信息时,再查询账户信息(延迟加载)。
查询账户时,该账户对应的用户信息应该和账户信息一起查询出来(立即加载)。

1.1.2、多表查询使用何种方式查询

一对多、多对多:一般采用延迟加载
多对一、一对一:一般采用立即加载

1.2、案例需求

账户(Account)和用户(User)的一对一(立即加载)和一对多查询(延迟加载)。

1.3、assocation延迟加载

1.3.1、开启Mybatis的延迟加载策略

在这里插入图片描述

在Mybatis的主配置文件种开启延迟加载策略

<settings>
  <setting name="lazyLoadingEnabled" value="true"/>
  <!-- aggressiveLazyLoading 开启时,任一方法的调用都会加载该对象的所有延迟加载属性,
即只要对这个类的任意操作都将完整加载整个类的所有属性即执行级联的SQL语句。 -->
  <setting name="aggressiveLazyLoading" value="false"/>
</settings>

1.3.2、账户持久层接口

public interface AccountDao {
    /**
     * 查询所有账户
     *
     * @return 返回账户列表,包含账户的用户信息 User(立即加载)
     */
    List<Account> findAll();
}

1.3.3、账户持久层映射文件

<mapper namespace="com.junlong.dao.AccountDao">
  <!-- 建立对应关系 -->
  <resultMap id="accountUserMap" type="account">
    <id property="id" column="id"/>
    <result property="uid" column="uid"/>
    <result property="money" column="money"/>
    <!-- select: 需要调用的 select 映射的id -->
    <!-- column: 需要传递给 select 映射的参数 -->
    <!-- fetchType="eager" 表示立即加载 -->
    <!-- 指定从表的引用实体属性 -->
    <association
                 property="user"
                 javaType="user"
                 column="uid"
                 select="com.junlong.dao.UserDao.findById"
                 fetchType="eager"
                 />
  </resultMap>

  <!-- 查询所有账户信息,立即加载该账户对应的用户信息 -->
  <select id="findAll" resultMap="accountUserMap">
    select *
    from account
  </select>
</mapper>

1.3.4、用户持久层接口

public interface UserDao {
    /**
     * 根据用户id查询用户
     *
     * @param id 用户id
     * @return 返回该用户id对用的用户User
     */
    User findById(Integer id);
}

1.3.5、用户持久层映射文件

<mapper namespace="com.junlong.dao.UserDao">
  <!-- 根据用户id查询用户 -->
  <select id="findById" resultType="user" parameterType="int">
    select *
    from user
    where id = #{id}
  </select>
</mapper>

1.3.6、测试查询账户信息及对应的用户信息(立即加载)

public class AccountTest {
    private InputStream inputStream;
    private SqlSession sqlSession;
    private AccountDao accountDao;

    @Before
    public void init() throws IOException {
        inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        sqlSession = sqlSessionFactory.openSession();
        accountDao = sqlSession.getMapper(AccountDao.class);
    }

    @After
    public void destroy() throws IOException {
        sqlSession.commit();
        sqlSession.close();
        inputStream.close();
    }

    /**
     * 测试查询所有账户信息,立即加载账户对应的用户信息
     */
    @Test
    public void testFindAll() {
        List<Account> accounts = accountDao.findAll();
        for (Account account : accounts) {
            System.out.println(account);
        }
    }
}

在这里插入图片描述

正如运行结果所示,立即加载会执行了查询用户的SQL。

1.4、collection延迟加载

1.4.1、开启mybatis的延迟加载策略

在这里插入图片描述

在Mybatis的主配置文件种开启延迟加载策略

<settings>
  <setting name="lazyLoadingEnabled" value="true"/>
  <!-- aggressiveLazyLoading 开启时,任一方法的调用都会加载该对象的所有延迟加载属性,即只要对这个类的任意操作都将完整加载整个类的所有属性即执行级联的SQL语句。 -->
  <setting name="aggressiveLazyLoading" value="false"/>
</settings>

1.4.2、用户持久层接口

public interface UserDao {
    /**
     * 查询所有用户
     *
     * @return 返回用户列表,包含用户的账户信息 Account(延迟加载)
     */
    List<User> findAll();
}

1.4.3、用户持久层映射文件

<mapper namespace="com.junlong.dao.UserDao">
  <!-- 建立对应关系 -->
  <resultMap id="userAccountMap" type="user">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <result property="birthday" column="birthday"/>
    <result property="sex" column="sex"/>
    <result property="address" column="address"/>
    <!-- collection 是用于建立一对多中集合属性的对应关系
            ofType 用于指定集合元素的数据类型
            select 是用于指定查询账户的唯一标识(账户的 dao 全限定类名加上方法名称)
            column 是用于指定使用哪个字段的值作为条件查询
        -->
    <!-- fetchType="lazy" 表示延迟加载 -->
    <collection
                property="accounts"
                ofType="account"
                column="id"
                select="com.junlong.dao.AccountDao.findByUid"
                fetchType="lazy"
                />
  </resultMap>

  <!-- 查询所有用户,延迟加载账户信息 -->
  <select id="findAll" resultMap="userAccountMap">
    select *
    from user
  </select>
</mapper>

1.4.4、账户持久层接口

public interface AccountDao {
    /**
     * 根据用户uid
     *
     * @param uid 用户id
     * @return 返回该用户id的所有账户信息
     */
    List<Account> findByUid(Integer uid);
}

1.4.5、账户持久层映射文件

<mapper namespace="com.junlong.dao.AccountDao">
  <!-- 根据用户id查询所有账户 -->
  <select id="findByUid" resultType="account" parameterType="int">
    select *
    from account
    where UID = #{uid}
  </select>
</mapper>

1.4.6、测试只查询用户,不查询账户信息(延迟加载)

public class UserTest {
    private InputStream inputStream;
    private SqlSession sqlSession;
    private UserDao userDao;

    @Before
    public void init() throws IOException {
        inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        sqlSession = sqlSessionFactory.openSession();
        userDao = sqlSession.getMapper(UserDao.class);
    }

    @After
    public void destroy() throws IOException {
        sqlSession.commit();
        sqlSession.close();
        inputStream.close();
    }

    /**
     * 测试查询所有用户,延迟加载用户的账户信息
     */
    @Test
    public void testFindAll() {
        List<User> users = userDao.findAll();
        for (User user : users) {
            System.out.println(user.getUsername());
        }
    }
}

在这里插入图片描述

如运行结果所示,并没有使用Account相关的信息,所以只查询了用户信息,和Account相关的SQL并未执行(延迟加载)。

2、Mybatis缓存

Mybatis提供了缓存策略,用来减少数据库的查询次数,以提高性能。
Mybatis的缓存分为 一级缓存 和 二级缓存。
在这里插入图片描述

2.1、一级缓存

一级缓存是 SqlSession 范围的缓存,调用SqlSession的修改、添加、删除、commit()、close()、clearCache()方法时,会清空一级缓存。
在这里插入图片描述

2.1.1、代码测试

  • 用户持久层接口
public interface UserDao {
    /**
     * 根据用户id查询用户
     *
     * @param id 用户id
     * @return 返回用户 User 对象
     */
    User findById(int id);

    /**
     * 更新用户信息
     *
     * @param user 需要更新的用户 User 对象
     */
    void updateUser(User user);
}
  • 用户持久层映射文件
<mapper namespace="com.junlong.dao.UserDao">
  <!-- 根据用户id查询用户 -->
  <select id="findById" resultType="user" parameterType="int">
    select *
    from user
    where id = #{id}
  </select>

  <!-- 更新用户信息 -->
  <update id="updateUser" parameterType="user">
    update user
    set username = #{username},
    address  = #{address}
    where id = #{id}
  </update>
</mapper>
  • 测试一级缓存
public class UserTest {
    private InputStream inputStream;
    private SqlSession sqlSession;
    private UserDao userDao;

    @Before
    public void init() throws IOException {
        inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        sqlSession = sqlSessionFactory.openSession();
        userDao = sqlSession.getMapper(UserDao.class);
    }

    @After
    public void destroy() throws IOException {
        sqlSession.commit();
        sqlSession.close();
        inputStream.close();
    }

    /**
     * 测试根据用户id查询用户
     */
    @Test
    public void test1stCache() {
        User user1 = userDao.findById(48);
        System.out.println("第一次查询的用户:" + user1);

        // mybatis一级缓存是SqlSession级别的,只要 SqlSession 没有 close 或 commit 或 clearCache ,它就存在
        //        sqlSession.clearCache();
        //        sqlSession.commit();

        // 当调用SqlSession的 修改,添加,删除,commit(),close() 等方法时,会清空一级缓存
        //        user1.setUsername("新增用户");
        //        user1.setAddress("石家庄");
        //        userDao.updateUser(user1);

        User user2 = userDao.findById(48);
        System.out.println("第二次查询的用户:" + user2);

        System.out.println(user1 == user2);
    }
}

在这里插入图片描述

如运行结果所示,mybatis第一次查询了数据库,第二次从一级缓存中拿到了查询结果(并没有查询数据库),两次查询的结果是同一个对象,提高了性能。

2.2、二级缓存

二级缓存是 Mapper 映射级别的缓存,二级缓存是跨 SqlSession 的,多个SqlSession共享二级缓存。
在这里插入图片描述
在这里插入图片描述

2.2.1、二级缓存的开启与关闭

  1. Mybatis主配置文件开启二级缓存支持
<!-- 开启二级缓存 -->
<settings>
  <!-- cacheEnabled 默认取值为 true,可以省略不配 -->
  <setting name="cacheEnabled" value="true"/>
</settings>
  1. 配置mapper映射文件
<?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">
<mapper namespace="com.junlong.dao.UserDao">
  <!-- 开启二级缓存的支持,表示当前这个mapper映射将使用二级缓存,以mapper的namespace值作为区分 -->
  <cache/>
</mapper>
  1. 配置statement的useCache属性
<!-- 根据用户id查询用户信息 -->
<!-- userCache="true" 表示当前statement需要使用二级缓存,如果不使用(每次查询都需要最近数据的查询),则设置为false -->
<select id="findById" parameterType="int" resultType="User" useCache="true">
  select *
  from user
  where id = #{id}
</select>

2.2.2、代码测试

public class UserTest {
    private InputStream inputStream;
    private SqlSessionFactory sqlSessionFactory;

    @Before
    public void init() throws IOException {
        inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    }

    @After
    public void destroy() throws IOException {
        inputStream.close();
    }

    /**
     * 测试二级缓存
     *
     * 注意:二级缓存在sqlSession关闭或者提交之后才会生效
     */
    @Test
    public void testFindById() {
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        UserDao userDao1 = sqlSession1.getMapper(UserDao.class);
        User user1 = userDao1.findById(48);
        System.out.println(user1);
        sqlSession1.close();

        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        UserDao userDao2 = sqlSession2.getMapper(UserDao.class);
        User user2 = userDao2.findById(48);
        System.out.println(user2);
        sqlSession2.close();

        System.out.println(user1 == user2);
    }
}

在这里插入图片描述

如运行结果所示,第一次查询后,关闭了一级缓存,第二次查询并没有执行查询数据库的SQL,而是使用了二级缓存。两次查询的结果不是同一个对象,是因为二级缓存是使用序列化的方式来保存对象的,每一次查询都会产生一个新的对象。

2.2.3、二级缓存注意事项

  1. 二级缓存在sqlSession关闭或者提交之后才会生效;
  2. 所缓存的类一定要实现 java.io.Serializable 接口 ,这样才能使用序列化的方式保存对象。

3、源码下载

源码下载链接👉 点击直达下载地址

《Mybatis使用手册》持续更新中…

感谢小伙伴们的关注!收藏 + 关注 不迷路 ~


相关内容
👉 Mybatis手册
👉 JavaSE手册

往期精彩内容
👉 Java小白学习手册 - SE

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

行走的程序喵

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

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

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

打赏作者

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

抵扣说明:

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

余额充值