(三)Mybatis延迟加载(懒加载)、一(二)级缓存、注解开发(简单易懂,图文教学,整套实战)

本文深入探讨MyBatis框架的高级特性,包括延迟加载、一二级缓存机制及注解开发方式,通过实例演示如何优化数据库交互,提升应用性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

实战项目完成代码:MybatisTest_GitHub


上一章:(二)Mybatis增删查改、动态SQL语句、多表操作(简单易懂,图文教学,整套实战)


1.延迟加载

1.1 什么是延迟加载?

需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载.
个人理解:就是把查询分成两次,例如我们在上章中讲一对一查询,查询账户和用户,我们是一次性完成的,延迟加载就是第一次只查询账户,如果用到用户,再查询用户。
使用要点:

  • 一对一通常使用立即加载
  • 一对多通常使用延迟加载

1.2 准备工作

  1. 创建子工程day3
  2. 将day2中的资源和代码都复制过来

1.3 association的延迟加载

  1. 向com.lois.dao.UserDao接口类中添加方法findById
public User findById(int id);
  1. 向com/lois/dao/AccountDao.xml中添加映射
    <select id="findAll" resultMap="accountMap">
        select * from account;
    </select>
  1. 向测试类中添加方法进行测试
@Test
    public void findByIdUser(){
        System.out.println(userDao.findById(1));
    }
  1. 修改com/lois/dao/AccountDao.xml映射文件
<?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.lois.dao.AccountDao">
    <resultMap id="accountMap" type="account">
        <id column="id" property="id"/>
        <result column="uid" property="uid"/>
        <result column="money" property="money"/>
        <!--映射单个对象
            property:在account实体类中的变量名
            javaType:实体类类型,应该写全类名,但是取过别名
            column:查询出来的表的字段名,该列作为传入select方法中的变量
            select:调用的查询方法,封装成javaType类
        -->
        <association property="user" javaType="user" column="uid" select="com.lois.dao.UserDao.findById"/>
    </resultMap>

    <select id="findAll" resultMap="accountMap">
        select * from account;
    </select>
</mapper>
  1. 修改测试类方法findAllAccount,并测试运行
@Test
    public void findAllAccount(){
        List<Account> all = accountDao.findAll();
        for (Account account :
                all) {
            System.out.println("---------------------");
            System.out.println(account);
        }
    }

在这里插入图片描述
我们发现查询确实分成了三次,但是并不是在我们使用的时候才查询的,还是一次性查询出来的。

  1. 这是因为我们没有打开延迟加载,查看mybatis文档后知道要在SqlMapConfig.xml进行设置,放在configuration标签内,但是不能放在第一个
	<settings>
        <!--开启延迟加载-->
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>
  1. 再次运行测试类findAllAccount
    在这里插入图片描述
    我们发现他是在使用的时候才进行第二次查询,且查询过的对象不会再次发起查询(第三次输出没有查询)

1.4 collection的延迟加载

  1. 修改com/lois/dao/UserDao.xml映射文件
<?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.lois.dao.UserDao">
    <resultMap id="userMap" type="user">
        <id column="id" property="id"/>
        <result column="name" property="name"/>
        <result column="age" property="age"/>
        <!--collection 是用于建立一对多中集合属性的对应关系
            ofType 用于指定集合元素的数据类型
            此处的column、select和association中的意义一样-->
        <collection property="accounts" ofType="account" column="id" select="com.lois.dao.AccountDao.findByUid"/>
    </resultMap>

    <select id="findAll" resultMap="userMap">
        select * from user
    </select>

    <select id="findById" resultType="user" parameterType="int">
        select * from user where id = #{id}
    </select>
</mapper>

我们发现在AccountDao中没有findByUid,那么我们写一个

  1. 向com.lois.dao.AccountDao接口类中添加方法
public List<Account> findByUid(int uid);
  1. 向com/lois/dao/AccountDao.xml映射文件中添加映射
    <select id="findByUid" parameterType="int" resultType="account">
        select * from account where uid = #{uid}
    </select>
  1. 向测试类中添加方法进行测试
@Test
    public void findByUidAccount(){
        System.out.println(accountDao.findByUid(1));
    }
  1. 修改测试类中的findAllUser方法,为了更好的看到延迟加载
	@Test
    public void findAllUser(){
        List<User> all = userDao.findAll();
        for (User user :
                all) {
            System.out.println("------------------------");
            System.out.println(user);
        }
    }
  1. 运行测试方法findAllUser
    在这里插入图片描述

2.缓存

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

2.1 一级缓存

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

2.1.1 验证一级缓存
  1. 在测试类中添加测试方法
@Test
    public void testFirstLevelCache1(){
        User user1 = userDao.findById(1);
        System.out.println(user1);
        User user2 = userDao.findById(1);
        System.out.println(user2);
        System.out.println(user1 == user2);
    }
  1. 运行测试
    在这里插入图片描述
    我们可以看到只发生了一次查询,且user1 == user2为true,我们知道这是比较的地址,所以user1和user2指向同一个对象。其实一级缓存在上面的实验中已经出现过了,在延迟加载查询账户时,小明只查询了一次。
2.2 分析一级缓存

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

2.2.1 重启session
  1. 将测试类中SqlSessionFactory factory提取成为成员变量
private InputStream in;
    private SqlSession session;
    private AccountDao accountDao;
    private UserDao userDao;
    private RoleDao roleDao;
    private SqlSessionFactory factory;
    //在测试方法执行之前执行
    @Before
    public void before() throws IOException {
        //1.获取工厂图纸(配置信息)
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.招募工人(创建构建者)
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        //3.给工人图纸建造工厂(构建者构建工厂对象)
        factory = builder.build(in);
        //4.构建产品流水线(打开自动提交事务)
        session = factory.openSession(true);
        //5.根据产品设计图生产出产品(动态代理)
        accountDao = session.getMapper(AccountDao.class);
        userDao = session.getMapper(UserDao.class);
        roleDao = session.getMapper(RoleDao.class);
    }
  1. 添加测试方法
@Test
    public void testFirstLevelCache2(){
        User user1 = userDao.findById(1);
        System.out.println(user1);
        //重启session
        session.close();
        session = factory.openSession(true);
        userDao = session.getMapper(UserDao.class);

        User user2 = userDao.findById(1);
        System.out.println(user2);
        System.out.println(user1 == user2);
    }
  1. 运行测试
    在这里插入图片描述
    我们发现查询了两次,而且两者比较为false
2.2.2 其他方法
  • 将上面的重启session注释掉,换成session.clearCache(),效果跟上面一样
  • 如果换成在中间调用了修改,添加,删除,commit()的效果也是一样

2.2 二级缓存

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

2.3 开启二级缓存

  1. 在 SqlMapConfig.xml 文件开启二级缓存
<settings>
	<!-- 开启二级缓存的支持 -->
	<setting name="cacheEnabled" value="true"/>
</settings>
  1. 配置相关的 Mapper 映射文件(我们以UserDao.xml为例),在mapper标签内添加
<!--开启User支持二级缓存-->
    <cache/>
  1. 配置 statement 上面的 useCache 属性
	<select id="findById" resultType="user" parameterType="int" useCache="true">
        select * from user where id = #{id}
    </select>
  1. 添加测试方法
@Test
    public void testSecondLevelCache(){
        SqlSession session1 = factory.openSession();
        UserDao userDao1 = session1.getMapper(UserDao.class);
        User user1 = userDao1.findById(1);
        System.out.println(user1);
        session1.close();//一级缓存消失

        SqlSession session2 = factory.openSession();
        UserDao userDao2 = session2.getMapper(UserDao.class);
        User user2 = userDao2.findById(1);
        System.out.println(user2);
        session2.close();//一级缓存消失

        System.out.println(user1 == user2);
    }
  1. 运行测试
    在这里插入图片描述
    我们发现确实只查询了一次,但是user1 == user2 却是false,这表示他们不是同一个对象,这是为什么?
    注意: 这样因为二级缓存存储在factory中,但是他的存储形式不是以对象存储,而是以数据的方式存储,需要用的时候再生成对象,所以在上面查询中,生成了两个对象。

3.注解开发

Mybatis不仅提供了使用xml映射来完成配置,还提供了一种跟简单的配置方法,注解配置。xml映射和注解配置不能在同一个Dao接口类中使用!!

3.1 准备工作

  1. 创建子工程day3_annotation
  2. 复制子工程day1的main和test文件(不要在idea中复制)
  3. 删除UserDao.xml映射文件

3.2 增删查改

  1. 修改UserDao接口类
/**
     * 查询所有用户
     * @return
     */
    @Select("select * from user")
    public List<User> findAll();

    /**
     * 根据id查询用户
     * @param id
     * @return
     */
    @Select("select * from user where id = #{id}")
    public User findById(int id);

    /**
     * 保存用户
     * @param user
     */
    @Insert("insert into user(name,age) values (#{name},#{age})")
    public void save(User user);

    /**
     * 修改用户
     * @param user
     */
    @Update("update user set name = #{name},age = #{age} where id = #{id}")
    public void update(User user);

    /**
     * 根据id删除用户
     * @param id
     */
    @Delete("delete from user where id = #{id}")
    public void deleteById(int id);

我们只需要在方法的上方使用@Select、@Insert、@Update、@Delete即可完成配置

  1. 运行测试方法进行测试

3.3 实体类属性与数据库表映射

如果实体类属性的名称和查询出来的表列字段不一致时,我们就要手动设置映射关系

	@Select("select * from user")
    @Results(value = {
            @Result(id = true,column = "ids" ,property = "id"),
            @Result(column = "username" ,property = "name"),
            @Result(column = "age" ,property = "age")
    })
    public List<User> findAll();

如果我们要在别处使用怎么办,Results注解还有一个id属性,设置后可以在别处使用

@Results(id = "userMap",value = {
            @Result(id = true,column = "ids" ,property = "id"),
            @Result(column = "username" ,property = "name"),
            @Result(column = "age" ,property = "age")
    })

在其他方法上添加注解ResultMap

@ResultMap(value = {"userMap"})

3.4 一对一(使用延迟加载)

  1. 将day3子项目中的Account实体类和AccountDao接口类复制到day3_annotation子项目中
  2. 在AccountDao接口中修改方法findAll
@Select("select * from account")
    @Results(id = "AccountMap",value = {
            @Result(id = true,column = "id",property = "id"),
            @Result(column = "uid",property = "uid"),
            @Result(column = "money",property = "money"),
            @Result(column = "uid",property = "user",one = @One(select = "com.lois.dao.UserDao.findById",fetchType = FetchType.LAZY))
    })
    public List<Account> findAll();

使用@one即可分布查询,如果将 fetchType 属性设置 FetchType.LAZY 即开启懒加载

  1. 修改测试类,获取accountDao,并添加测试方法
accountDao = session.getMapper(AccountDao.class);
	@Test
    public void findAllAccount(){
        List<Account> all = accountDao.findAll();
        for (Account account :
                all) {
            System.out.println("---------------------");
            System.out.println(account);
        }
    }

其实跟使用xml映射配置差不多,只是采用了注解的方式,如果调用的也在前面讲过了,这里就不重复了

3.5 一对多(使用延迟加载)

  1. 将day3子项目中的User实体类复制到day3_annotation子项目中
  2. 修改UserDao接口中的findAll方法
    /**
     * 查询所有用户,并且查询出用户下所有账户信息
     * @return
     */
    @Select("select * from user")
    @Results(id = "userMap", value = {
            @Result(column = "id",property = "id"),
            @Result(column = "name",property = "name"),
            @Result(column = "age",property = "age"),
            @Result(column = "id",property = "accounts",many = @Many(select = "com.lois.dao.AccountDao.findByUid",fetchType = FetchType.LAZY))
    })
    public List<User> findAll();
  1. 发现AccountDao.findByUid方法未完成,修改完成
    @Select("select * from account where uid = #{uid}")
    public List<Account> findByUid(int uid);
  1. 向测试类中添加方法,进行测试
	@Test
    public void findAllUser(){
        List<User> all = userDao.findAll();
        for (User user :
                all) {
            System.out.println("------------------------");
            System.out.println(user);
        }
    }

3.6 缓存

一级缓存在这里就不说了,默认是开启的
那么我们来看看二级缓存是如何使用

  1. 配置SqlMapConfig.xml开启二级缓存(其实默认就是开启的,这步可以跳过)
<!-- 配置二级缓存 -->
<settings>
	<!-- 开启二级缓存的支持 -->
	<setting name="cacheEnabled" value="true"/>
</settings>
  1. 然后只需要在需要使用二级缓存的dao接口类上方加入@CacheNamespace(blocking = true)即可
@CacheNamespace(blocking = true)
public interface UserDao {

我在这里就不测试了,需要的话自行测试

3.7 注解开发总结

  • 注解开发跟xml开发大致一样
  • 注解开发使用延迟加载不需要对SqlMapConfig.xml配置文件进行修改
  • 一个Dao接口只能有一个实现方式,要么是注解开发要么是xml映射
  • 如果你想看各个注解还有什么属性,可以按ctrl+鼠标点击该注解查看
  • 个人认为注解开发要比xml简单许多

上一章:(二)Mybatis增删查改、动态SQL语句、多表操作(简单易懂,图文教学,整套实战)


相关内容:

(一)初见MyBatis,快速入门以及配置文件映射文件讲解(构建思路讲解,图文教学,整套实战)

(二)Mybatis增删查改、动态SQL语句、多表操作(简单易懂,图文教学,整套实战)

(三)Mybatis延迟加载(懒加载)、一(二)级缓存、注解开发(简单易懂,图文教学,整套实战)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值