【MyBatis】五、MyBatis的缓存机制与逆向工程

MyBatis的缓存机制

MyBatis的一级缓存

MyBatis的一级缓存是默认开启的,是基于SqlSession级别的缓存,也就是说,只要是同一个sqlSession,只要执行的语句相同,则不会去数据库中进行查找,而是会从缓存中找到对应的结果。

MyBatis一级缓存失效的四种情况

  • 使用了不同的sqlsession对象
  • 同一个sqlsession对象,但查询条件不同
  • 两次查询之间进行了增删改操作(增删改操作会清空缓存)
  • 手动清空缓存(sqlsession.clearCache())

MyBatis二级缓存

MyBatis的二级缓存是SqlSessionFactory级别的,也就是说,通过同一个SqlSessionFactory创建的SqlSession对象所查询的结果都会被缓存,而在查询时也都会优先从缓存中获取结果。

二级缓存的开启

MyBatis的二级缓存默认是不开启的

  • 在核心配置文件(mybatis-config.xml)中配置属性cacheEnable="true",默认为true不用刻意配置

  • 在映射文件中设置标签:

    xml 复制代码
    <mapper namespace="com.qinghe.mybatis.mapper.DynamicSQLMapper">
    <!-- 在命名空间下写一个这个就可以了 -->
        <cache />
    
        <sql id="empColumn">eid, emp_name, age, sex, email</sql>
  • 对应的实体类必须序列化

    java 复制代码
    public class Emp implements Serializable {

注意:只有在sqlSession关闭或提交之后,信息才会被提交到二级缓存中。

java 复制代码
    @Test
    public void testTwoCache() {
        try {
//            获取mybatis配置文件的字节输入流
            InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
//            工厂模式,创建一个创建sqlSession的工厂,(通过刚刚获取到的输入流)
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
//            创建一个sqlSession对象,开启自动提交
            SqlSession sqlSession1 = sqlSessionFactory.openSession(true);
            CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class);
            System.out.println(mapper1.getEmpByEid(1));
//            只有sqlsession关闭或提交之后二级缓存才生效
            sqlSession1.commit();
            SqlSession sqlSession2 = sqlSessionFactory.openSession(true);
            CacheMapper mapper2 = sqlSession2.getMapper(CacheMapper.class);
            System.out.println(mapper2.getEmpByEid(1));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

注意:在两次查询之间进行了增删改操作的话,会使缓存失效

手动清空缓存不会清空二级缓存

二级缓存的相关信息

  • eviction属性:缓存回收策略

    • LRU(默认):最近最少使用策略
    • FIFO:先进先出
    • SOFT-软引用:移除基于垃圾回收器状态和软引用规则的对象
    • WEAK-弱引用:更积极的移除基于垃圾回收器状态和弱引用规则的对象
  • flushInterval属性:刷新间隔(二级缓存的清空间隔)

    默认为不刷新,只在进行了增删改之后刷新。

  • size属性:引用数目,正整数

    代表缓存中可以存储多少个对象,不要设置太大,容易造成内存溢出

  • readOnly属性:默认为false

    • true:缓存返回的对象为只读缓存,其会给调用者返回缓存对象相同的实例,因此这些对象不能修改,这提供了很大的性能优势
    • false:缓存返回的对象为读写缓存(通过序列化),这会慢一些但是保证了数据的安全性。

MyBatis的缓存查询顺序

  • 先查询二级缓存,因为二级缓存中可能会存在其他程序已经查询出来的数据,其可以直接拿来使用。
  • 如果二级缓存没有命中,会去查询一级缓存(还未提交到二级缓存的数据可能会存在于这里)
  • 如果一级缓存也没有命中,查询数据库
  • SqlSession关闭之后,将一级缓存中的内容存储到二级缓存

MyBatis整合EHCache

MyBatis作为持久层框架,其也提供了整合第三方缓存机制的接口,例如可以整合EHCache:

添加依赖:

xml 复制代码
        <dependency>
            <groupId>org.mybatis.caches</groupId>
            <artifactId>mybatis-ehcache</artifactId>
            <version>1.2.1</version>

        </dependency>

<!--        slf4j的一个具体实现(logback)-->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>

添加encache的配置文件:ehcache.xml(文件名固定)

xml 复制代码
<?xml version="1.0" encoding="utf-8" ?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
    <!-- 磁盘保存路径 -->
    <diskStore path="D:\atguigu\ehcache"/>
    <defaultCache
            maxElementsInMemory="1000"
            maxElementsOnDisk="10000000"
            eternal="false"
            overflowToDisk="true"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU">
    </defaultCache>
</ehcache>

设置二级缓存的类型

  • 在xxxMapper.xml文件中设置二级缓存类型
xml 复制代码
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

加入logback日志

  • 存在SLF4J时,作为简易日志的log4j将失效,此时我们需要借助SLF4J的具体实现logback来打印日志。创建logback的配置文件logback.xml,名字固定,不可改变
xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
    <!-- 指定日志输出的位置 -->
    <appender name="STDOUT"
              class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <!-- 日志输出的格式 -->
            <!-- 按照顺序分别是:时间、日志级别、线程名称、打印日志的类、日志主体内容、换行 -->
            <pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger] [%msg]%n</pattern>
        </encoder>
    </appender>
    <!-- 设置全局日志级别。日志级别按顺序分别是:DEBUG、INFO、WARN、ERROR -->
    <!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志。 -->
    <root level="DEBUG">
        <!-- 指定打印日志的appender,这里通过"STDOUT"引用了前面配置的appender -->
        <appender-ref ref="STDOUT" />
    </root>
    <!-- 根据特殊需求指定局部日志级别 -->
    <logger name="com.atguigu.crowd.mapper" level="DEBUG"/>
</configuration>

EHCache配置文件说明

属性名 是否必须 作用
maxElementsInMemory 在内存中缓存的element的最大数目
maxElementsOnDisk 在磁盘上缓存的element的最大数目,若是0表示无穷大
eternal 设定缓存的elements是否永远不过期。 如果为true,则缓存的数据始终有效, 如果为false那么还要根据timeToIdleSeconds、timeToLiveSeconds判断
overflowToDisk 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上
timeToIdleSeconds 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时, 这些数据便会删除,默认值是0,也就是可闲置时间无穷大
timeToLiveSeconds 缓存element的有效生命期,默认是0.,也就是element存活时间无穷大
diskSpoolBufferSizeMB DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区
diskPersistent 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false
diskExpiryThreadIntervalSeconds 磁盘缓存的清理线程运行间隔,默认是120秒。每个120s, 相应的线程会进行一次EhCache中数据的清理工作
memoryStoreEvictionPolicy 当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。 默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出

MyBatis逆向工程

引入依赖

xml 复制代码
<dependencies>
    <!-- MyBatis核心依赖包 -->
    <!-- Mybatis核心 -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.7</version>
    </dependency>
    <!-- junit测试 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <!-- MySQL驱动 -->
    <!-- https://mvnrepository.com/artifact/com.mysql/mysql-connector-j -->
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <version>8.0.32</version>
    </dependency>

    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>


</dependencies>
<!-- 控制Maven在构建过程中相关配置 -->
<build>
    <!-- 构建过程中用到的插件 -->
    <plugins>
        <!-- 具体插件,逆向工程的操作是以构建过程中插件形式出现的 -->
        <plugin>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-maven-plugin</artifactId>
            <version>1.3.0</version>
            <!-- 插件的依赖 -->
            <dependencies>
                <!-- 逆向工程的核心依赖 -->
                <dependency>
                    <groupId>org.mybatis.generator</groupId>
                    <artifactId>mybatis-generator-core</artifactId>
                    <version>1.3.2</version>
                </dependency>
                <!-- 数据库连接池 -->
                <dependency>
                    <groupId>com.mchange</groupId>
                    <artifactId>c3p0</artifactId>
                    <version>0.9.2</version>
                </dependency>
                <!-- MySQL驱动 -->
                <dependency>
                    <groupId>mysql</groupId>
                    <artifactId>mysql-connector-java</artifactId>
                    <version>8.0.27</version>
                </dependency>
            </dependencies>
        </plugin>
    </plugins>
</build>

创建mybatis核心配置文件

mybatis-config.xml:

xml 复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <!--    配置jdbc.properties文件,使数据库的连接属性们从jdbc.properties读取-->
    <properties resource="jdbc.properties"/>
    <!--    typeAliases是设置别名的意思,其将全类名设置别名为最后的类名-->
    <typeAliases>
        <!--        type属性是要设置别名的类,alias属性是设置的类名
                    alias属性可以不进行设置,若不进行设置则其默认的别名就是类名,且不区分大小写,若设置了alias就区分大小写了,严格按照设置的alias属性进行匹配
        -->
        <!--        也可以以包为单位,将包中的所有类都设置为默认的类名的别名(不区分大小写)-->
        <package name="com.qinghe.mybatis.pojo"></package>
    </typeAliases>

    <!--设置连接数据库的环境-->
    <environments default="development">
        <environment id="development">
            <!--
                        transactionManager标签
                        JDBC表示使用原生的JDBC事务管理方式,例如commit和rollback
                        MANAGED表示被管理的事务管理方式,例如被Spring管理等
            -->
            <transactionManager type="JDBC"/>
            <!--
                        设置数据源
                        POOLED表示使用数据库连接池缓存进行连接
                        UNPOLLED表示不使用数据库连接池
                        JNDI表示使用上下文中的数据源
            -->
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
    <!--引入映射文件-->
    <mappers>
        <!--        这样是引入单个映射-->
        <!--        <mapper resource="mappers/UserMapper.xml"/>-->

        <!--        使用package标签可以做到引入一个包下的所有映射文件的效果-->
        <!--
                    ***************************
                    注意mapper接口所在的包的包名必须也映射配置文件所在的包名一致
                    mapper接口的名字也必须与xml文件的名字保持一致
                    *****************************
        -->
        <package name=""></package>
    </mappers>
</configuration>

创建逆向工程的配置文件

generatorConfig.xml(不得改名)

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
    <!--
    targetRuntime: 执行生成的逆向工程的版本
    MyBatis3Simple: 生成基本的CRUD(清新简洁版)
    MyBatis3: 生成带条件的CRUD(奢华尊享版)
    -->
    <context id="DB2Tables" targetRuntime="MyBatis3Simple">
        <!-- 数据库的连接信息 -->
        <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/mybatis"
                        userId="root"
                        password="123456">
        </jdbcConnection>
        <!-- javaBean的生成策略-->
<!--        enableSubPackages为是否在子包,trimStrings为是否去除数据库字段前后的空格-->
        <javaModelGenerator targetPackage="com.qinghe.mybatis.pojo" targetProject=".\src\main\java">
            <property name="enableSubPackages" value="true" />
            <property name="trimStrings" value="true" />
        </javaModelGenerator>
        <!-- SQL映射文件的生成策略 -->
        <sqlMapGenerator targetPackage="com.qinghe.mybatis.mapper"
                         targetProject=".\src\main\resources">
            <property name="enableSubPackages" value="true" />
        </sqlMapGenerator>
        <!-- Mapper接口的生成策略 -->
        <javaClientGenerator type="XMLMAPPER"
                             targetPackage="com.qinghe.mybatis.mapper" targetProject=".\src\main\java">
            <property name="enableSubPackages" value="true" />
        </javaClientGenerator>
        <!-- 逆向分析的表 -->
        <!-- tableName设置为*号,可以对应所有表,此时不写domainObjectName -->
        <!-- domainObjectName属性指定生成出来的实体类的类名 -->
        <table tableName="t_emp" domainObjectName="Emp"/>
        <table tableName="t_dept" domainObjectName="Dept"/>
    </context>
</generatorConfiguration>

之后在右侧的plugins中执行mybatis-generator就可以生成文件了,注意这样会将数据库中的下划线字段映射为驼峰,这样的生成方式只有基础的CRUD功能

MyBatis逆向工程奢华尊享版

在高级的MyBatis生成文件中,我们会生成类似于Mybatis-plus的sql映射,以以下方式使用:

注意xxxSeletive方法在操作时会不插入值为null的数据

java 复制代码
    @Test
    public void test1() {
        try {
            InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
            SqlSessionFactory sqlSessionFactory =  new SqlSessionFactoryBuilder().build(is);
            SqlSession sqlSession = sqlSessionFactory.openSession(true);
            EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
//            List<Emp> list = mapper.selectByExample(null);
//            创建条件
            EmpExample example = new EmpExample();
//            and条件这样添加
            example.createCriteria().andEmpNameEqualTo("张三").andAgeBetween(20, 90);
//            or条件这样添加
            example.or().andDidIsNotNull();
            List<Emp> list = mapper.selectByExample(example);
            list.forEach(item -> {
                System.out.println(item);
            });
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

MyBatis分页插件

配置依赖

xml 复制代码
        <!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper -->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>5.2.0</version>
        </dependency>

在mybatis-config中配置分页插件,注意其位置顺序:

properties、settings、typeAliases、typeHandlers、objectFactory、objectWrapperFactory、reflectorFactory、plugins、environments、databaseIdProvider、mappers

xml 复制代码
    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
    </plugins>

使用:

java 复制代码
    @Test
    public void testPageHelper() {
        try {
            InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
            SqlSession sqlSession = sqlSessionFactory.openSession(true);
            EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);

//            开启PageHelper分页
//            可以使用Page对象接收分页,page对象中会存储一些基本信息
            Page<Object> page = PageHelper.startPage(3, 2);
            List<Emp> list = mapper.selectByExample(null);
//            也可以在这里使用PageInfo对象接收分页信息,这之中有全部的详细信息
//            第二个形参代表导航的数量:......4、5、6、7、8、9......(例如这种就是有五个)
            PageInfo<Emp> pageInfo = new PageInfo<>(list, 5);
            System.out.println(page);
            System.out.println(pageInfo);
            /**
             * limit index, pageSize
             *
             */
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

Page对象中的信息:

Page{count=true, pageNum=3, pageSize=2, startRow=4, endRow=6, total=10, pages=5, reasonable=false, pageSizeZero=false}

Emp{eid=5, empName='田七', age=22, sex='男', email='345@qq.com', did=2}, Emp{eid=6, empName='a', age=null, sex='null', email='null', did=null}

PageInfo对象中的信息:

PageInfo{pageNum=3, pageSize=2, size=2, startRow=5, endRow=6, total=10, pages=5, list=Page{count=true, pageNum=3, pageSize=2, startRow=4, endRow=6, total=10, pages=5, reasonable=false, pageSizeZero=false}

Emp{eid=5, empName='田七', age=22, sex='男', email='345@qq.com', did=2}, Emp{eid=6, empName='a', age=null, sex='null', email='null', did=null},

prePage=2, nextPage=4, isFirstPage=false, isLastPage=false, hasPreviousPage=true, hasNextPage=true, navigatePages=5, navigateFirstPage=1, navigateLastPage=5, navigatepageNums=1, 2, 3, 4, 5}

相关推荐
临沂GEO4 小时前
GEO搜索优化科普|正规地理位置流量运营入门指南
大数据·人工智能·python·流量运营
大模型码小白5 小时前
Spring AI Tool 实现自然语言操作 MySQL 数据库详解
服务器·开发语言·数据库·人工智能·python·mysql·spring
荷蒲6 小时前
【小白量化Qbuddy】利用AI学习中文Python
人工智能·python·学习
数据狐(Datafox)7 小时前
京东商品列表API技术解析与落地应用(含标准 JSON 示例)
java·大数据·前端·人工智能·python·数据分析·json
量化吞吐机8 小时前
2026年下半年手工交易规则走向量化表达,产品该先接住哪一步
人工智能·python
Metaphor6928 小时前
使用 Python 读取和删除 Excel 文件属性
python·excel
动力 continue8 小时前
MySQL 基础篇 · SELECT 查询大
数据库·sql·mysql·select
迷迭香yy9 小时前
Python构建转融券多空识别系统从接口到因子的全流程 IG50免费开源股票数据API接口
开发语言·python·开源
月光船幽幽10 小时前
真实即粗糙,光滑是伪造
人工智能·python
一次旅行10 小时前
Attention机制从数学到工程:拆解缩放点积+多头注意力|附可运行PyTorch实现与踩坑指南
人工智能·pytorch·python