MyBatis_Day3_MyBatis延迟加载、MyBatis缓存机制、PerpetualCache缓存类、MyBatis注解开发

MyBatis延迟加载

Lazy Loading(延迟加载)是一种优化数据获取的机制

在需要用到数据时才进行加载,不需要用到数据时就不加载数据。

延迟加载也称懒加载

合理使用 MyBatis 的延迟加载机制,可以根据实际业务需求,有效地优化数据的获取和处理,提升系统的整体性能。

优点:

减少不必要的数据查询,降低数据库的压力

在处理复杂关系时提高系统性能

缺点:

可能会增加一定的代码复杂性,需要适当配置和处理

如果配置不当,可能会导致多次数据库查询,反而降低性能

全局开启

MyBatisConfig.xml:

<!--开启全局的懒加载(默认值:false)-->
<setting name="lazyLoadingEnabled" value="true"/>

局部开启

一对一

在Mapper.xml中对单个SQL语句开启

DeptDao.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.xxx.dao.DeptDao">
​
    <select id="findById" resultType="dept">
        select * from dept where id=#{id}
    </select>
</mapper>

EmpDao.xml:

<resultMap id="empAllMap" type="com.xxx.entity.Emp">
    <id column="id" property="id"></id>
    <result column="name" property="name"></result>
    <result column="age" property="age"></result>
    <result column="addr" property="addr"></result>
    <result column="salary" property="salary"></result>
​
    <!--
        fetchType: 是否开启懒加载
            lazy: 开启
            eager: 立即加载
     -->
    <association property="dept" javaType="com.xxx.entity.Dept"
                 select="com.xxx.dao.DeptDao.findById" column="dept_id" fetchType="lazy">
    </association>
</resultMap>
​
<select id="findAll" resultMap="empAllMap">
    select * from emp
</select>

参数:

select:使用SQL方法名

column:当前SQL查询结构中作为后续SQL语句中的参数的列

fetchType: 是否开启懒加载

一对多

EmpDao.xml:

<select id="findByDeptId" resultType="emp">
    select * from emp where dept_id=#{deptId}
</select>

DeptDao.xml:

<resultMap id="deptResultMap" type="dept">
    <id column="id" property="id"></id>
    <result column="name" property="name"></result>
    <result column="location" property="location"></result>
​
    <!--
        property: dept中的需要映射的属性名称
        ofType: 集合中泛型的类型
        column: 在结果集中选择column这一列当做参数传递到select指定的方法中
        fetchType: 是否延迟加载  lazy: 延迟加载  eager: 立即加载
        select: 映射empList的方法名
    -->
    <collection property="empList" ofType="com.xxx.entity.Emp"
            column="id" fetchType="lazy"
            select="com.xxx.dao.EmpDao.findByDeptId">
​
        <result column="name" property="name"></result>
        <result column="addr" property="addr"></result>
        <result column="salary" property="salary"></result>
    </collection>
</resultMap>
​
<select id="findAll" resultMap="deptResultMap">
    select * from dept;
</select>

MyBatis缓存机制:

当一级缓存和二级缓存同时存在时,先查询二级缓存再查询一级缓存

都没有才会查询数据库,再将数据的数据存入一级缓存

一级缓存

基于session级别的缓存,也称为本地缓存。默认开启,作用域是当前Session。

当session flush或close后,该session中的一级缓存将被清空

默认开启,但不能被关闭,可以调用clearCache() 来清空一级缓存

多次查询同一语句的结果集存入的内容是内存地址值

清空一级缓存:

localCacheScope

将一级缓存的作用域设置为语句级别(localCacheScope设置为STATEMENT)

MyBatisConfig.xml

<!--日志配置-->
<settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
​
    <!--
    更改一级缓存的作用域
        SESSION: 会话级别缓存,默认值
        STATEMENT: 语句级别缓存,缓存只对当前执行的语句生效(类似于关闭一级缓存)
    -->
    <setting name="localCacheScope" value="STATEMENT"/>
</settings>
clearCache

测试代码:

@Test
public void test2() throws Exception {          // 测试clearCache
​
    // 开启一级缓存
    SqlSession session = factory.openSession();
​
    EmpDao mapper = session.getMapper(EmpDao.class);
​
    // 首先从一级缓存里面查询,没查询到,然后去数据库查询,将查询的结果放入一级缓存
    Emp emp = mapper.findById(1);
    System.out.println(emp);
​
    session.clearCache();           // 清空一级缓存
​
    // 首先从一级缓存里面查询(没有),去数据库查询,将查询的结果放入一级缓存
    Emp emp2 = mapper.findById(1);
    System.out.println(emp2);
​
    System.out.println(emp == emp2);        // false
​
    // 关闭session(一级缓存清空)
    session.close();
}
flushCache

执行完当前语句是否清空缓存,默认为false

EmpDao.xml:

<mapper namespace="com.xxx.dao.EmpDao">
​
    <!--
        flushCache:
            true:   每次执行完这个查询语句都清空一级缓存
            false:  默认值,执行完该SQL语句不清空一级缓存
    -->
    <select id="findById" resultType="emp" flushCache="true">
        select * from emp where id=#{id}
    </select>
​
</mapper>
增删改:

在MyBatis中,执行任何的增删改都会导致一级缓存清空

增删改会导致select标签中的flushCache参数(false)失效,依旧清空一级缓存

@Test
public void test5() throws Exception {           
​
    // 开启一级缓存
    SqlSession session = factory.openSession();
​
    EmpDao mapper = session.getMapper(EmpDao.class);
​
    // 将查询结果放入一级缓存
    Emp emp = mapper.findById(1);
    System.out.println(emp);
​
    Emp saveEmp = new Emp();
    saveEmp.setName("test");
​
    mapper.save(saveEmp);           // 任何的增删改都会导致缓存失效
​
    // 首先从一级缓存里面查询(没有),去数据库查询,将查询的结果放入一级缓存
    Emp emp2 = mapper.findById(1);
    System.out.println(emp2);
​
    System.out.println(emp == emp2);        // false
​
    // 关闭session(一级缓存清空)
    session.close();
}

session关闭

测试代码:

@Test
public void test4() throws Exception {
​
    // 开启一级缓存
    SqlSession session = factory.openSession();
​
    EmpDao mapper = session.getMapper(EmpDao.class);
​
    // 将查询的结果放入一级缓存
    Emp emp = mapper.findById(1);
    System.out.println(emp);
​
    // session关闭一级缓存清空(session关闭了代表与数据库的会话一级结束了,不可以再发送任何的SQL语句了)
    session.close();
​
    // 重新获取一次会话
    SqlSession session2 = factory.openSession();
    EmpDao mapper2 = session2.getMapper(EmpDao.class);
​
    // 新的session的一级缓存中并没有数据,发送SQL去数据库查询
    Emp emp2 = mapper2.findById(1);
    System.out.println(emp2);
    System.out.println(emp == emp2);
​
    // session关闭,一级缓存清空
    session2.close();
}

一级缓存的引用问题

当多次查询同一SQL语句时,MyBatis会优先查询一级缓存中的内容,并且引用地址值给后续查询的结果集

这就导致了当其中之一的结果集对内容进行修改时,会间接修改其他结构集的值

二级缓存

在全局设置中默认开启(支持),但是还需要在Mapper文件中单独开启,作用域是当前Mapper文件

当一级缓存的session关闭的时候,会将一级缓存中的内容存入(序列化)二级缓存

因为一级缓存的内容是序列化到二级缓存的,因此存入二级缓存的对象需要实现序列化接口(Serializable),当查询到二级缓存中有对应数据时再反序列化到结果集中

开启Mapper的二级缓存

MyBatisConfig.xml:

<settings>
    <!--日志配置-->
    <setting name="logImpl" value="STDOUT_LOGGING"/>
​
    <!--开启二级缓存,默认是开启状态,false为关闭二级缓存-->
    <setting name="cacheEnabled" value="true"/>
</settings>

Mapper.xml

<mapper namespace="com.dfbz.dao.EmpDao">
    <!--
    <cache />: 开启当前Mapper的二级缓存
        eviction: 缓存的回收策略
            • LRU  – 最近最少使用的:移除最长时间不被使用的对象。
                • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
                • SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
                • WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
        flushInterval:缓存刷新间隔,缓存多长时间清空一次,默认不清空,可以通过此参数设置一个毫秒值
        readOnly:是否只读
            true:只读;mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。
                 mybatis为了加快获取速度,直接就会将数据在缓存中的引用交给用户。不安全,速度快
            false:非只读:mybatis觉得获取的数据可能会被修改。
                mybatis会利用序列化&反序列的技术克隆一份新的数据给你。安全,速度慢
        size:二级缓存中最大能够缓存存放多少元素
        type:指定自定义缓存的全类名(实现Cache接口)
    -->
    <cache />
    
      <!--
        useCache: 当一级缓存关闭时,是否将本次SQL语句的结果集存入二级缓存
            true: 存入(默认值)
            false: 不存入
    -->
    <select id="findById" resultType="emp">
        select * from emp where id=#{id}
    </select>
​
    <insert id="save" >
        insert into emp(id,name) values(null,#{name});
    </insert>
</mapper>

二级缓存的清空/失效

useCache

可以在select标签中添加参数useCache以用于当前SQL语句是否存入二级缓存,默认为true

<select id="findById" resultType="emp" useCache="true">
    select * from emp where id=#{id}
</select>
增删改

会清空一级缓存也会清空二级缓存

flushCache

让当前SQL语句执行完后决定是否清空缓存,默认为false

会清空一级缓存也会清空二级缓存

增删改不会使该参数(false)失效,依旧保留二级缓存

PerpetualCache缓存类

MyBatis 跟缓存相关的类都在cache 包里面,其中有一个Cache 接口,

只有一个默认的实现类 PerpetualCache,该类是MyBatis的缓存实现类;

包括一级缓存和二级缓存都是采用PerpetualCache类来实现的

一级缓存和二级缓存都在MyBatis加载时(创建session工厂时)创建,不过只在session关闭时才会将此次session的一级缓存序列号写入二级缓存,再将一级缓存清空

执行任意增删改操作或者flushCache时销毁

重写二级缓存

Redis替换二级缓存

Cache接口

public interface Cache {
    String getId();
    void putObject(Object var1, Object var2);
    Object getObject(Object var1);
    Object removeObject(Object var1);
    void clear();
    int getSize();
    
    default ReadWriteLock getReadWriteLock() {
        return null;
    }
}

自定义缓存

依赖

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.1</version>
</dependency>
​
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.62</version>
</dependency>

重写Cache接口

public class MyCache implements Cache {
    private Jedis jedis = new Jedis();
    private String id;
​
    public MyCache(String id) {
        this.id = id;
    }
​
    @Override
    public String getId() {
        return id;
    }
    
    /**
     * 添加缓存
     * @param key: 缓存的key
     * @param val: 需要存储到缓存的值(是一个List类型)
     */
    @Override
    public void putObject(Object key, Object val) {
        jedis.set(key.toString(), JSON.toJSONString(val));
        System.out.println("putObject..............");
    }
​
    /**
     * 获取缓存中的内容
     * @param key
     * @return
     */
    @Override
    public Object getObject(Object key) {
        System.out.println("getObject................");
        String jsonStr = jedis.get(key.toString());
        List<Emp> list = JSON.parseArray(jsonStr, Emp.class);
        return list;
    }
​
    /**
     * 删除缓存
     * @param key
     * @return
     */
    @Override
    public Object removeObject(Object key) {
        System.out.println("removeObject................");
        return jedis.del(key.toString());
    }
​
    /**
     * 清空缓存
     */
    @Override
    public void clear() {
        System.out.println("clear................");
        jedis.flushDB();
    }
​
    /**
     * 获取缓存中的key数量
     * @return
     */
    @Override
    public int getSize() {
        System.out.println("getSize................");
        return Integer.parseInt(jedis.dbSize() + "");
    }
}

Emp.xml

<mapper namespace="com.dfbz.dao.EmpDao">
    <!--替换为我们自定义的缓存-->
    <cache type="com.dfbz.cache.MyCache"></cache>
    <select id="findById" resultType="Emp">
        select * from emp where id=#{id}
    </select>
</mapper>

测试类

public class Demo01 {
    SqlSessionFactory factory;
​
    @Before
    public void before() throws Exception {
        factory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("MyBatisConfig.xml"));
    }
​
    @Test
    public void test1() throws Exception {
​
        // 开启一级缓存
        SqlSession session = factory.openSession();
​
        EmpDao mapper = session.getMapper(EmpDao.class);
​
        // 先从二级缓存里面查询,再从一级缓存里面查询,再从数据库查询(发送SQL),将查询到的结果集存入一级缓存
        Emp emp = mapper.findById(1);
        System.out.println(emp);
​
        // 关闭一级缓存,把数据写入二级缓存(写入到redis)
        session.close();
​
        // 开启一级缓存
        SqlSession session2 = factory.openSession();
​
        EmpDao mapper2 = session2.getMapper(EmpDao.class);
​
        // 先从二级缓存里面获取(从redis里面获取)
        Emp emp2 = mapper2.findById(1);
        System.out.println(emp2);
        System.out.println(emp == emp2);            // false ,两个对象的属性值一样,但是内存地址值不一样(反序列化出来的)
​
        session2.close();
    }
}

MyBatis注解开发

替代xml

注解功能
@Insert新增
@Update更新
@Delete删除
@Select查询
@Result结果集封装
@Results@Result 一起使用,封装多个结果集
@One一对一结果集封装
@Many一对多结果集封装

增删改查:

public interface UserDao {
​
    //查
    @Select("select * from user where id=#{id}")
    User findById(int id);
​
    //查
    @Select("select * from user")
    List<User> findAll();
​
    //增
    @Insert("insert into user values(null,#{username},#{birthday},#{sex},#{address})")
    void save(User user);
​
    //改
    @Update("update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id}")
    void update(User user);
​
    //删
    @Delete("delete from user where id=#{id}")
    void delete(int id);
}

一对一映射:

public interface EmpDao {

	//查
    @Select("select * from emp where id=#{id}")
	//取结果集
    @Results({
		//封装结果集
        @Result(id = true, property = "id", column = "id"),
        @Result(property = "name", column = "name"),
        @Result(property = "age", column = "age"),
        @Result(property = "addr", column = "addr"),
        @Result(property = "salary", column = "salary"),
        @Result(property = "dept", column = "dept_id", javaType = Dept.class,
        //一对一结果集封装,开启懒加载
                    one = @One(select = "com.dfbz.dao.DeptDao.findById",fetchType = FetchType.LAZY)
            ),

    })
    Emp findById(Integer id);
}

一对多映射:

DeptDao:

//查
@Select("select * from dept where id=#{id}")
//取结果集
@Results({
	//结果集封装
    @Result(id = true,property = "id",column = "id"),
    @Result(property = "name",column = "name"),
    @Result(property = "location",column = "location"),
    @Result(property = "empList",column = "id",javaType = List.class,
            //一对多封装,开启懒加载
            many = @Many(select = "com.dfbz.dao.EmpDao.findByDeptId",fetchType = FetchType.LAZY)
        )
})
Dept findDeptAllById(Integer id);

EmpDao:

@Select("select * from emp where dept_id=#{deptId}")
List<Emp> findByDeptId(Integer deptId);

  • 22
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值