【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]}

相关推荐
黄公子学安全1 小时前
Java的基础概念(一)
java·开发语言·python
Sunyanhui11 小时前
牛客网 SQL36查找后排序
数据库·sql·mysql
程序员一诺2 小时前
【Python使用】嘿马python高级进阶全体系教程第10篇:静态Web服务器-返回固定页面数据,1. 开发自己的静态Web服务器【附代码文档】
后端·python
小木_.2 小时前
【Python 图片下载器】一款专门为爬虫制作的图片下载器,多线程下载,速度快,支持续传/图片缩放/图片压缩/图片转换
爬虫·python·学习·分享·批量下载·图片下载器
Jiude2 小时前
算法题题解记录——双变量问题的 “枚举右,维护左”
python·算法·面试
唐小旭2 小时前
python3.6搭建pytorch环境
人工智能·pytorch·python
是十一月末3 小时前
Opencv之对图片的处理和运算
人工智能·python·opencv·计算机视觉
Mitch3113 小时前
【漏洞复现】CVE-2021-45788 SQL Injection
sql·web安全·docker·prometheus·metersphere
爱学测试的李木子3 小时前
Python自动化测试的2种思路
开发语言·软件测试·python
网络安全King3 小时前
网络安全 - SQL Injection
sql·web安全·php