chengaofeng
发布于 2024-07-01 / 6 阅读
0
0

MyBatis高级特性

MyBatis日志管理

什么是日志?

  1. 日志文件是用于记录系统操作事件的记录文件或文件集合

  2. 日志保存历史数据,是诊断问题以及理解系统活动的重要依据

SLF4j与Logback

  1. 日志门面:Simple Logging Facade For Java(SLF4j) 和 Apache Commons-Logging

  2. 桥接

  3. 日志实现: log4j、logback、java.util.logging

  4. 实现与门面分开可以很方便的切换底层实现的包,如log4j和logback互相切换,使用的日志门面Simple Logging Facade For Java不需要修改,由日志门面适配

  5. 目前工作中主流:logback组件

  6. MyBatis底层就是通过Simple Logging Facade For Java(SLF4j) 支持 logback

配置日志

  1. 添加依赖包 pom.xml

        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.6</version>
        </dependency>
  1. 配置文件,不创建使用默认配置
    src/main/resources/logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
    <!--
        日志级别:TRACE < DEBUG < INFO < WARN < ERROR
        TRACE: 追踪信息,最详细的信息
        DEBUG: 调试信息
        INFO: 一般信息
        WARN: 警告信息
        ERROR: 错误信息
        debug表示输出DEBUG及以上级别的日志信息
        生产环境中,一般设置为INFO
        大小写不敏感
    -->
    <root level="debug">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>
  1. 更多查看官方文档:https://logback.qos.ch/

MyBatis动态SQL

动态SQL是指根据参数数据动态组织SQL的技术

src/main/resources/mappers/goods.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="cn.chengaofeng.mapper.GoodsMapper">
    <!--结果映射-->
    <resultMap id="rmGoods" type="cn.chengaofeng.dto.GoodsDTO">
        <!--设置字段与属性映射-->
        <id property="goods.goodsId" column="goods_id"/>
        <result property="goods.title" column="title"/>
        <result property="goods.originalCost" column="original_cost"/>
        <result property="goods.discount" column="discount"/>
        <result property="goods.isFreeDelivery" column="is_free_delivery"/>
        <result property="goods.categoryId" column="category_id"/>
        <result property="category.categoryId" column="category_id"/>
        <result property="category.categoryName" column="category_name"/>
        <result property="category.parentId" column="parent_id"/>
        <result property="category.categoryLevel" column="category_level"/>
        <result property="category.categoryOrder" column="category_order"/>
        <result property="test" column="test"/>
    </resultMap>
    <select id="selectGoodsDTO" resultMap="rmGoods">
        select g.*, c.*, '1' as test from t_goods g, t_category c
        where g.category_id = c.category_id
    </select>

    <select id="dynamicSQL" parameterType="cn.chengaofeng.entity.Goods" resultMap="rmGoods">
        select g.*, c.*, '1' as test from t_goods g, t_category c
        where g.category_id = c.category_id
        <if test="goodsId != null">
            and g.goods_id = #{goodsId}
        </if>
        <if test="title != null">
            and g.title like concat('%', #{title}, '%')
        </if>
    </select>
</mapper> 

src/test/java/cn/chengaofeng/MybatisTest.java

    @Test
    public void testSelectByDynamicSQL() throws Exception {
        SqlSession sqlSession = null;
        try {
            sqlSession = MyBatisUtils.openSession();
            Goods goods = new Goods();
            // goods.setTitle("测试");
            goods.setGoodsId(739);
            List<Goods> list = sqlSession.selectList("cn.chengaofeng.mapper.GoodsMapper.dynamicSQL", goods);
            System.out.println(list);
        } catch (Exception e) {
            throw e;
        } finally {
            MyBatisUtils.closeSession(sqlSession);
        }
    }

说明

  1. 在使用where时,因为动态sql里面都有and,如果没加条件时会造成sql语法错误,可以使用where 1=1

  2. 为了解决这个问题,MyBatis提供了<where>标签,就不需要1=1这种写法了,会自动去掉and,保证语法正确

MyBatis的二级缓存

  1. 一级缓存默认开启,缓存范围SqlSession会话

    @Test
    public void testLevel1Cache() throws Exception {
        SqlSession sqlSession = null;
        try {
            sqlSession = MyBatisUtils.openSession();
            Goods goods1 = sqlSession.selectOne("cn.chengaofeng.mapper.GoodsMapper.selectGoodsById", 739);
            Goods goods2 = sqlSession.selectOne("cn.chengaofeng.mapper.GoodsMapper.selectGoodsById", 739);
            System.out.println(goods1.hashCode() + "=" + goods2.hashCode());
        } catch (Exception e) {
            throw e;
        } finally {
            MyBatisUtils.closeSession(sqlSession);
        }

        try {
            sqlSession = MyBatisUtils.openSession();
            Goods goods1 = sqlSession.selectOne("cn.chengaofeng.mapper.GoodsMapper.selectGoodsById", 739);
            //  sqlSession.commit(); // 手动清空
            Goods goods2 = sqlSession.selectOne("cn.chengaofeng.mapper.GoodsMapper.selectGoodsById", 739);
            System.out.println(goods1.hashCode() + "=" + goods2.hashCode());
        } catch (Exception e) {
            throw e;
        } finally {
            MyBatisUtils.closeSession(sqlSession);
        }
    }

可以看出在同一个sqlSession中,多次查询会直接使用同一个缓存的内存数据,在closeSession后清空。

  1. 二级缓存需要手动开启,属于范围Mapper Namespace

<?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="cn.chengaofeng.mapper.GoodsMapper">
    <!--开启二级缓存-->
    <cache eviction="LRU" flushInterval="600000" size="512" readOnly="true" />
</mapper> 
    @Test
    public void testLevel2Cache() throws Exception {
        SqlSession sqlSession1 = null;
        SqlSession sqlSession2 = null;
        try {
            sqlSession1 = MyBatisUtils.openSession();
            Goods goods1 = sqlSession1.selectOne("cn.chengaofeng.mapper.GoodsMapper.selectGoodsById", 739);
            System.out.println(goods1.hashCode());
        } catch (Exception e) {
            throw e;
        } finally {
            MyBatisUtils.closeSession(sqlSession1);
        }

        try {
            sqlSession2 = MyBatisUtils.openSession();
            Goods goods2 = sqlSession2.selectOne("cn.chengaofeng.mapper.GoodsMapper.selectGoodsById", 739);
            System.out.println(goods2.hashCode());
        } catch (Exception e) {
            throw e;
        } finally {
            MyBatisUtils.closeSession(sqlSession2);
        }
    }

开启二级缓存后,不同sqlSession使用了相同的缓存

二级缓存运行规则

  1. 二级开启后默认所有查询操作都使用缓存

  2. 写操作commit提交时对该namespace缓存强制清空

  3. 配置useCache=false时,可以不用缓存

  4. 配置flushCache=true代表强制清空缓存

cache标签属性说明

  1. eviction是缓存的清除策略,当缓存对象数量达到上限后,自动触发对应算法对缓存对象清除
    - LRU - 最近最久未使用:移除最长时间不使用的对象(另外有个LFU算法,最近一段时间内访问最少的移除)
    - FIFO - 先进先出: 按对象进入缓存的顺序来移除它们
    - SOFT - 软引用: 移除基于垃圾收集器状态和软引用规则的对象
    - WEAK - 弱引用:更积极的移除基于垃圾收集器状态和弱引用规则的对象

  2. flushInterval 代表间隔多长时间自动清空缓存,单位毫秒,600000毫秒 = 10分钟,每隔十分钟自动对缓存进行清除

  3. size 缓存存储上限,用于保存对象或集合(1个集合算1个对象)的数量上限,建议设置为>表中总条数

  4. readOnly 设置为true, 代表返回只读缓存,每次从缓存取出的是缓存对象本身,这种执行效率较高
    设置为false,代表每次取出的是缓存对象的“副本”,每一次取出的对象都是不同的,这种安全性较高

开启二级缓存后,其它标签可以使用的属性

  1. useCache 是否使用缓存,比如查询所有的select标签上可以设置 useCache = "false"

  2. flushCache = "true" 在sql执行后强制清空缓存,效果跟commit是一样的。比如在insert标签上使用,在插入新数据后,清空缓存。select也可以清空,在清空后,这条查询数据也不会放入缓存


评论