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接口类中编写方法

    java 复制代码
    package com.tx.mapper;
    
    import com.tx.entity.Account;
    
    import java.util.List;
    
    public interface AccountMapper {
        // 延迟加载:多对一查询,查询某一个用户的所有账户信息
        public List<Account> findAccountAll();
    }
  • 在AccountMapper.xml中进行配置和SQL语句

    xml 复制代码
    <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接口类中编写方法

    java 复制代码
    package com.tx.mapper;
    
    import com.tx.entity.User;
    
    import java.util.List;
    
    public interface UserMapper {
        // 延迟加载:多对一查询
        public List<User> findUserById(Integer uid);
    }
  • 在UserMapper.xml中进行配置文件

    xml 复制代码
    <mapper namespace="com.tx.mapper.UserMapper">
        <!--配置延迟加载:多对一查询-->
        <select id="findUserById" parameterType="int" resultType="user">
            select * from user where id = #{id}
        </select>
    </mapper>
  • 在主配置文件中开启延迟加载

    xml 复制代码
    <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>
  • 测试代码

    java 复制代码
    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接口类中编写方法

    java 复制代码
    package com.tx.mapper;
    
    import com.tx.entity.User;
    
    import java.util.List;
    
    public interface UserMapper1 {
        // 延迟加载:一对多查询
        public List<User> findUserAll();
    }
  • 在UserMapper.xml中进行配置和SQL语句

    xml 复制代码
    <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接口类中编写方法

    java 复制代码
    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语句

    xml 复制代码
    <mapper namespace="com.tx.mapper.AccountMapper1">
        <!--延迟加载:一对多查询-->
        <!--通过用户的id查询账户信息-->
        <select id="findAccountByUid" parameterType="int" resultType="account">
            select * from account where uid = #{uid}
        </select>
    </mapper>
  • 在主配置文件中开启延迟加载

    xml 复制代码
    <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>
  • 测试代码

    java 复制代码
    // 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接口类中编写方法

    java 复制代码
    package com.tx.mapper;
    
    import com.tx.entity.User;
    
    public interface UserMapper2 {
        // 一/二级缓存
        public User findById(Integer id);
    }
  2. 在UserMapper2.xml中配置Sql语句

    xml 复制代码
    <select id="findById" parameterType="int" resultType="user">
        select * from user where id = #{id}
    </select>
  3. 测试代码

    java 复制代码
    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级别
需要手动开启二级缓存:

  • 在主配置文件开启二级缓存

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

    xml 复制代码
    <!--开启二级缓存-->
    <cache/>

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

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

实操案例:

  • 测试代码
java 复制代码
// 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);
}
相关推荐
n***26561 小时前
【MySQL】MVCC详解, 图文并茂简单易懂
android·数据库·mysql
0***86331 小时前
【SpringBoot3】Spring Boot 3.0 集成 Mybatis Plus
spring boot·后端·mybatis
曹牧1 小时前
C#中的StartsWith
java·服务器·c#
u***28471 小时前
Python连接SQL SEVER数据库全流程
数据库·python·sql
g***86691 小时前
【mybatis】基本操作:详解Spring通过注解和XML的方式来操作mybatis
xml·spring·mybatis
o***Y3631 小时前
【MySQL】表空间丢失处理(Tablespace is missing for table 错误处理)
数据库·mysql
他们叫我技术总监1 小时前
从 WM_CONCAT 到 LISTAGG:Oracle 字符串聚合按时间排序完整方案
数据库·人工智能·oracle
4***72131 小时前
flask后端开发(8):Flask连接MySQL数据库+ORM增删改查
数据库·mysql·flask