深入解析:MyBatis框架 - 延迟加载+一/二级缓存

一、延迟加载

立即加载

概念: 当查询主对象时,立即执行所关联的sql语句,一次性将对象数据全部查询出来。
行为: 执行SELECT * FROM order WHERE id = #{id}后,立马执行SELECT * FROM user WHERE id = #{userId}
使用场景: 多对一查询
优点: 数据一次性加载完毕,响应速度快。
缺点: 可能查询出不需要的数据,造成数据库资源和网络传输的浪费,降低性能。
使用: 默认使用立即加载。

延迟加载

概念: 当查询主对象时,只查询主对象的数据。关联对象的数据不会立即查询,只用程序第一次真正访问关联对象时,才会加载关联对象的数据。
行为: 先执行SELECT * FROM order WHERE id = #{id},当调用order.getUser()方法时,才执行SELECT * FROM user WHERE id = #{userId}

使用场景: 一对多查询
优点: 按需加载,避免了不必要的数据库查询。
缺点: 当第一次访问关联对象时,会有个短暂的查询延迟。后续访问多个关联对象,会产生多次数据库查询。
使用: 需要手动开启延迟加载。

  • 在主配置文件中开启延迟加载:lazyLoadingEnabledtrue开启延迟加载,aggressiveLazyLoading控制延迟加载的行为,默认值为false,按需加载
  • 在子配置文件中:在<association>标签或<collection>标签中设置fetchType指定哪个关联查询使用延迟加载。
    • fetchType属性值:lazy - 延迟加载;eager - 立即加载

实操案例

将一对多查询和多对一查询都进行延迟加载演示:
多对一延迟加载实操案例演示:

  • 在AccountMapper接口类中编写方法
    package com.tx.mapper;
    import com.tx.entity.Account;
    import java.util.List;
    public interface AccountMapper {
    // 延迟加载:多对一查询,查询某一个用户的所有账户信息
    public List<Account> findAccountAll();
      }
  • 在AccountMapper.xml中进行配置和SQL语句
    <mapper namespace="com.tx.mapper.AccountMapper">
      <!--延迟加载:多对一查询-->
        <!--内连接查询-->
            <select id="findAccountAll" resultMap="accountMap">
            select * from account
          </select>
          <!--通过用户的id查询账户信息-->
              <select id="findByUid" parameterType="int" resultType="account">
              select * from account where uid = #{uid}
            </select>
            <!--配置映射-->
                <resultMap id="accountMap" type="account">
                <result property="id" column="id" />
                <result property="uid" column="uid" />
                <result property="money" column="money" />
                <!--在多的一方指定关联查询的延迟加载-->
                    <association property="user" javaType="user"
                    select="com.tx.mapper.UserMapper.findUserById" column="uid" fetchType="lazy">
                  <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" />
                </association>
              </resultMap>
            </mapper>
  • 在UserMapper接口类中编写方法
    package com.tx.mapper;
    import com.tx.entity.User;
    import java.util.List;
    public interface UserMapper {
    // 延迟加载:多对一查询
    public List<User> findUserById(Integer uid);
      }
  • 在UserMapper.xml中进行配置文件
    <mapper namespace="com.tx.mapper.UserMapper">
      <!--配置延迟加载:多对一查询-->
          <select id="findUserById" parameterType="int" resultType="user">
          select * from user where id = #{id}
        </select>
      </mapper>
  • 在主配置文件中开启延迟加载
    <configuration>
      <!--配置延迟加载-->
        <settings>
          <!-- 开启延迟加载 -->
            <setting name="lazyLoadingEnabled" value="true"/>
            <!-- 将积极加载改为消极加载及按需加载 -->
              <setting name="aggressiveLazyLoading" value="false"/>
            </settings>
            <!--定义类型别名-->
              <typeAliases>
                <package name="com.tx.entity" />
              </typeAliases>
              <!--主配置文件-->
                <!--配置环境们-->
                    <environments default="mysql">
                    <!--配置环境-->
                        <environment id="mysql">
                        <!--配置事务的类型,使用本地事务策略-->
                        <transactionManager type="JDBC"></transactionManager>
                          <!--配置是否使用连接池 POOLED表示使用链接池,UNPOOLED表示不使用连接池-->
                              <dataSource type="POOLED">
                              <property name="driver" value="com.mysql.jdbc.Driver"/>
                              <property name="url" value="jdbc:mysql:///mybatis_db"/>
                              <property name="username" value="root"/>
                              <property name="password" value="root"/>
                            </dataSource>
                          </environment>
                        </environments>
                        <!--加载映射文件-->
                          <mappers>
                          <mapper resource="mapper/UserMapper.xml"></mapper>
                          <mapper resource="mapper/AccountMapper.xml"></mapper>
                          </mappers>
                        </configuration>
  • 测试代码
    package com.tx.test;
    import com.tx.entity.Account;
    import com.tx.entity.User;
    import com.tx.mapper.AccountMapper;
    import com.tx.mapper.UserMapper1;
    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.IOException;
    import java.io.InputStream;
    import java.util.List;
    // 入门程序
    public class Test01 {
    private InputStream in;
    private SqlSessionFactory factory;
    private SqlSession session;
    @Before
    public void init() throws IOException {
    // 1. 加载主配置文件,目的是构建SqlSessionFactory对象
    in = Resources.getResourceAsStream("SqlMapConfig.xml");
    // 2. 创建SqlSessionFactory(Sql会话工厂)对象
    factory = new SqlSessionFactoryBuilder().build(in);
    // 3. 获取session对象,使用SqlSessionFactory工厂对象创建SqlSession对象
    session = factory.openSession();
    }
    @After
    public void destory() throws IOException {
    // 5. 释放资源
    session.close();
    in.close();
    }
    // 4.1 延迟加载:多对一查询
    @Test
    public void testFindAccountAll(){
    // 4.1.1 通过session创建Mapper接口的代理对象
    AccountMapper mapper = session.getMapper(AccountMapper.class);
    // 4.1.2 执行方法
    List<Account> list = mapper.findAccountAll();
      for (Account account:list){
      System.out.println("开始……");
      System.out.println(account.getMoney());
      System.out.println(account.getUser().getUsername());
      System.out.println("结束……");
      System.out.println();
      }
      }
      }

结果
一对多延迟加载实操案例演示:

  • 在UserMapper接口类中编写方法
    package com.tx.mapper;
    import com.tx.entity.User;
    import java.util.List;
    public interface UserMapper1 {
    // 延迟加载:一对多查询
    public List<User> findUserAll();
      }
  • 在UserMapper.xml中进行配置和SQL语句
    <mapper namespace="com.tx.mapper.UserMapper1">
      <!--配置延迟加载:一对多查询-->
          <select id="findUserAll" resultMap="userMap">
          select * from user
        </select>
        <!--数据封装-->
            <resultMap id="userMap" type="user">
            <id property="id" column=""/>
            <result property="username" column="username"/>
            <result property="birthday" column="birthday"/>
            <result property="sex" column="sex"/>
            <result property="address" column="address"/>
            <!--select="":使用账号的方法查询
            column="":使用id值去查询账号
            -->
              <collection property="accounts" ofType="account"
              select="com.tx.mapper.AccountMapper1.findAccountByUid" column="id" fetchType="lazy">
            <id property="id" column="id"/>
            <result property="uid" column="uid"/>
            <result property="money" column="money"/>
          </collection>
        </resultMap>
      </mapper>
  • 在AccountMapper接口类中编写方法
    package com.tx.mapper;
    import com.tx.entity.Account;
    import java.util.List;
    public interface AccountMapper1 {
    // 延迟加载:一对多查询
    public List<Account> findAccountByUid(Integer uid);
      }
  • 在AccountMapper.xml中进行配置和SQL语句
    <mapper namespace="com.tx.mapper.AccountMapper1">
      <!--延迟加载:一对多查询-->
        <!--通过用户的id查询账户信息-->
            <select id="findAccountByUid" parameterType="int" resultType="account">
            select * from account where uid = #{uid}
          </select>
        </mapper>
  • 在主配置文件中开启延迟加载
    <configuration>
      <!--配置延迟加载-->
        <settings>
          <!-- 开启延迟加载 -->
            <setting name="lazyLoadingEnabled" value="true"/>
            <!-- 将积极加载改为消极加载及按需加载 -->
              <setting name="aggressiveLazyLoading" value="false"/>
            </settings>
            <!--定义类型别名-->
              <typeAliases>
                <package name="com.tx.entity" />
              </typeAliases>
              <!--主配置文件-->
                <!--配置环境们-->
                    <environments default="mysql">
                    <!--配置环境-->
                        <environment id="mysql">
                        <!--配置事务的类型,使用本地事务策略-->
                        <transactionManager type="JDBC"></transactionManager>
                          <!--配置是否使用连接池 POOLED表示使用链接池,UNPOOLED表示不使用连接池-->
                              <dataSource type="POOLED">
                              <property name="driver" value="com.mysql.jdbc.Driver"/>
                              <property name="url" value="jdbc:mysql:///mybatis_db"/>
                              <property name="username" value="root"/>
                              <property name="password" value="root"/>
                            </dataSource>
                          </environment>
                        </environments>
                        <!--加载映射文件-->
                          <mappers>
                          <mapper resource="mapper/UserMapper1.xml"></mapper>
                          <mapper resource="mapper/AccountMapper1.xml"></mapper>
                          <mapper resource="mapper/UserMapper2.xml"></mapper>
                          </mappers>
                        </configuration>
  • 测试代码
    // 4.2 延迟加载:一对多查询
    @Test
    public void testFindUserAll(){
    // 4.2.1 通过session创建Mapper接口的代理对象
    UserMapper1 mapper = session.getMapper(UserMapper1.class);
    // 4.2.2 执行方法
    List<User> list = mapper.findUserAll();
      for (User user:list){
      System.out.println("开始……");
      System.out.println(user.getUsername());
      System.out.println(user.getAccounts());
      System.out.println("结束……");
      System.out.println();
      }
      }

一级缓存

二、缓存

MySQL使用sql语句缓存。例如,select * from user和select *         from user,看起来时同一个表,但是,在MySQL缓存中,用第二表查询不到User表中的数据
MyBtais将数据进行缓存。

一级缓存

级别: SqlSession级别

  • SqlSession对象使用Map集合(Key存储执行的SQL语句,value存放查询的对象)存储相互的缓存数据
  • 查询时,先从SqlSession的缓存中查找,如果有,直接返回;如果没有,查询数据库。
  • 一级缓存的生命周期和SqlSession的生命周期相同,SqlSession对象关闭,一级缓存也关闭。
    • session.clearCache();:清除缓存
    • 调用调用SqlSession的update、insert、delete、commit和close等方法的时候也会清空缓存。

实操案例:

  1. 在UserMapper2接口类中编写方法
    package com.tx.mapper;
    import com.tx.entity.User;
    public interface UserMapper2 {
    // 一/二级缓存
    public User findById(Integer id);
    }
  2. 在UserMapper2.xml中配置Sql语句
    <select id="findById" parameterType="int" resultType="user">
      select * from user where id = #{id}
    </select>
  3. 测试代码
    package com.tx.test;
    import com.tx.entity.User;
    import com.tx.mapper.UserMapper2;
    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.IOException;
    import java.io.InputStream;
    public class Test02 {
    private InputStream in;
    private SqlSessionFactory factory;
    private SqlSession session;
    @Before
    public void init() throws IOException {
    // 1. 加载主配置文件,目的是构建SqlSessionFactory对象
    in = Resources.getResourceAsStream("SqlMapConfig.xml");
    // 2. 创建SqlSessionFactory(Sql会话工厂)对象
    factory = new SqlSessionFactoryBuilder().build(in);
    // 3. 获取session对象,使用SqlSessionFactory工厂对象创建SqlSession对象
    session = factory.openSession();
    }
    @After
    public void destory() throws IOException {
    // 5. 释放资源
    session.close();
    in.close();
    }
    // 4.1 一级缓存:会话级别
    @Test
    public void testFindById(){
    // 获取代理对象
    UserMapper2 mapper = session.getMapper(UserMapper2.class);
    // 调用方法,通过主键查询
    // 先查询一级缓存,没有数据。
    // 会查数据库,都会有sql语句,把查询出来的数据存储到一级缓存中
    User user = mapper.findById(1);
    System.out.println(user);
    System.out.println("==================================");
    // 清除缓存
    // session.clearCache();
    // 在查询一次
    // 先查询一级缓存,存在数据。
    // 从缓存中把数据返回,就没有sql语句
    User user1 = mapper.findById(1);
    // 这两个user对象地址一样
    System.out.println(user1);
    }
    }

二级缓存

级别: SqlSessionFactory级别
需要手动开启二级缓存:

  • 在主配置文件开启二级缓存
    <!--配置延迟加载-->
      <settings>
        <!-- 开启延迟加载 -->
          <setting name="lazyLoadingEnabled" value="true"/>
          <!-- 将积极加载改为消极加载及按需加载 -->
            <setting name="aggressiveLazyLoading" value="false"/>
            <!--开启二级缓存-->
              <setting name="cacheEnabled" value="true"/>
            </settings>
  • 在子配置文件开启二级缓存
    <!--开启二级缓存-->
      <cache/>

除此之外,还有一个重要的条件:

  • 实体类一定要继承Serializable对象流

实操案例:

  • 测试代码
// 4.2 二级缓存:会话工厂级别
// 二级缓存的使用对象地址不同,但是也是从缓存加载。原因是二级缓存存储的是零散数据,组装出来的对象
@Test
public void testFindById2(){
// 获取代理对象
UserMapper2 mapper = session.getMapper(UserMapper2.class);
// 调用方法,通过主键查询
// 先查询一级缓存,没有数据。
// 会查数据库,都会有sql语句,把查询出来的数据存储到一级缓存中
User user = mapper.findById(1);
System.out.println(user);
System.out.println("==================================");
// 关闭会话
session.close();
// 获取session对象 和 获取代理对象
session = factory.openSession();
UserMapper2 mapper1 = session.getMapper(UserMapper2.class);
// 在查询一次
// 先查询一级缓存,存在数据。
// 从缓存中把数据返回,就没有sql语句
User user1 = mapper1.findById(1);
// 这两个user对象地址一样
System.out.println(user1);
}

二级缓存

posted @ 2026-01-25 15:08  gccbuaa  阅读(3)  评论(0)    收藏  举报