zoukankan      html  css  js  c++  java
  • 0065 MyBatis一级缓存与二级缓存

    数据库中数据虽多,但访问频率却不同,有的数据1s内就会有多次访问,而有些数据几天都没人查询,这时候就可以将访问频率高的数据放到缓存中,就不用去数据库里取了,提高了效率还节约了数据库资源

    MyBatis有两种缓存,分为一级和二级缓存。
    一级缓存作用域为SqlSession,同一个SqlSession才能访问到,采用HashMap存储数据,当该SqlSession进行了DML操作或者调用close()方法,其中的一级缓存就会清空。一级缓存默认开启。

    二级缓存的作用域是mapper映射配置文件,多个SqlSession共用二级缓存,二级缓存仍然使用HashMap存储数据,如果多个SqlSession执行同一个命名空间的同一条sql,且传入的参数也相同,就是说最终执行相同的sql的话,就会从二级缓存中拿数据。二级缓存需要配置,默认不开启

    一级缓存

    sql映射器配置xml文件:

    <mapper namespace="net.sonng.mbt.mapper.BookMapper">
        <select id="findAll" resultType="net.sonng.mbt.entity.Book">
            SELECT * FROM book
        </select>
        
        <select id="findBookById" parameterType="int" resultType="net.sonng.mbt.entity.Book">
            SELECT * FROM book WHERE id=#{id}
        </select>
        
        <update id="updateBook" parameterType="net.sonng.mbt.entity.Book" >
            UPDATE book 
            <set>
                <if test="name!=null" >`name`=#{name},</if>
                <if test="press!=null" >press=#{press},</if>
                <if test="author!=null" >author=#{author},</if>
                <if test="isbn!=null" >isbn=#{isbn},</if>
                <if test="douban!=null" >douban=#{douban}</if>
            </set>
            WHERE id=${id}
        </update>
        <select id="findBookInId" parameterType="list" resultType="net.sonng.mbt.entity.Book">
            SELECT * FROM book WHERE id In 
            <foreach item="id" index="i" collection="list" open="(" separator="," close=")">
                #{id}
            </foreach>
        </select>
    </mapper>
    

    映射器接口略
    测试类:

    package net.sonng.mbt.test;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.util.ArrayList;
    import java.util.List;
    
    import net.sonng.mbt.entity.Book;
    import net.sonng.mbt.mapper.BookMapper;
    
    import org.apache.ibatis.io.Resources;
    import org.apache.ibatis.session.SqlSession;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.apache.ibatis.session.SqlSessionFactoryBuilder;
    
    public class BookTest {
        public static void main(String[] args) throws IOException{
            InputStream inputStream=Resources.getResourceAsStream("mybatis-config.xml");
            SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
            SqlSession session=sqlSessionFactory.openSession();
            BookMapper bookMapper=session.getMapper(BookMapper.class);
            
            Book book0=bookMapper.findBookById(5);      //第一次查询
            System.out.println(book0);
            
            Book book1=bookMapper.findBookById(5);      //同样的语句,第二次查询
            System.out.println(book1);
            book1.setDouban(9.9f);
            bookMapper.updateBook(book1);               //DML语句,更改了数据
            session.commit();                           //注意这里,有没有commit效果一样,都会更新数据库,清除一级缓存
            
            Book book2=bookMapper.findBookById(5);      //同样的语句,第三次查询
            System.out.println(book2);
            session.close();
        }
    }
    

    输出如下:

    DEBUG [main] - ==> Preparing: SELECT * FROM book WHERE id=? //第一次查询
    DEBUG [main] - > Parameters: 5(Integer)
    DEBUG [main] - <
    Total: 1
    Book [id=5, name=深入理解Java 7 核心技术与最佳实践, press=机械工业出版社, author=成富著, isbn=9787111380399, douban=9.9]
    Book [id=5, name=深入理解Java 7 核心技术与最佳实践, press=机械工业出版社, author=成富著, isbn=9787111380399, douban=9.9] //第二次查询,没有执行sql语句,直接从SqlSession一级缓存中拿到数据
    DEBUG [main] - ==> Preparing: UPDATE book SET name=?, press=?, author=?, isbn=?, douban=? WHERE id=5 //更新了数据
    DEBUG [main] - > Parameters: 深入理解Java 7 核心技术与最佳实践(String), 机械工业出版社(String), 成富著(String), 9787111380399(String), 9.9(Float) //update语句清空了一级缓存
    DEBUG [main] - <
    Updates: 1
    DEBUG [main] - ==> Preparing: SELECT * FROM book WHERE id=? //第三次查询,一级缓存已被情况,执行sql语句入库查询
    DEBUG [main] - > Parameters: 5(Integer)
    DEBUG [main] - <
    Total: 1
    Book [id=5, name=深入理解Java 7 核心技术与最佳实践, press=机械工业出版社, author=成富著, isbn=9787111380399, douban=9.9]

    测试结论之一:不管前面的查询有没有提交事务,查询结果都会写到一级缓存中;如果session执行了DML,一级缓存会被清空
    特别注意的是:要是同一条sql语句才会从缓存中拿数据,比如看下面的测试代码:

    package net.sonng.mbt.test;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.util.ArrayList;
    import java.util.List;
    
    import net.sonng.mbt.entity.Book;
    import net.sonng.mbt.mapper.BookMapper;
    
    import org.apache.ibatis.io.Resources;
    import org.apache.ibatis.session.SqlSession;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.apache.ibatis.session.SqlSessionFactoryBuilder;
    
    public class BookTest {
        public static void main(String[] args) throws IOException{
            InputStream inputStream=Resources.getResourceAsStream("mybatis-config.xml");
            SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
            SqlSession session=sqlSessionFactory.openSession();
            BookMapper bookMapper=session.getMapper(BookMapper.class);
            List<Integer> ids=new ArrayList<Integer>();
            ids.add(1);
            ids.add(2);
            ids.add(4);
            ids.add(5);
            List<Book> books0=bookMapper.findBookInId(ids);  //第一次查询
            for(Book b:books0){
                System.out.println(b);
            }
            
            List<Book> books1=bookMapper.findBookInId(ids);  //同一条语句,第二次查询
            for(Book b:books1){
                System.out.println(b);
            }
            
            Book book0=bookMapper.findBookById(5);           //虽然缓存中有这条记录,但还是执行了一次sql查询
            System.out.println(book0);
            
            session.close();
        }
    }
    

    输出如下:

    DEBUG [main] - ==> Preparing: SELECT * FROM book WHERE id In ( ? , ? , ? , ? ) //第一次查询
    DEBUG [main] - > Parameters: 1(Integer), 2(Integer), 4(Integer), 5(Integer)
    DEBUG [main] - <
    Total: 4
    Book [id=1, name=深入理解Java虚拟机 JVM高级特性与最佳实践, press=机械工业出版社, author=周志明著, isbn=9787111421900, douban=8.8]
    Book [id=2, name=疯狂Java讲义 第3版, press=电子工业出版社, author=李刚著, isbn=9787121236693, douban=7.8]
    Book [id=4, name=Java编程思想 第4版, press=机械工业出版社, author=(美)Bruce Eckel著, isbn=9787111213826, douban=9.1]
    Book [id=5, name=深入理解Java 7 核心技术与最佳实践, press=机械工业出版社, author=成富著, isbn=9787111380399, douban=9.9]
    Book [id=1, name=深入理解Java虚拟机 JVM高级特性与最佳实践, press=机械工业出版社, author=周志明著, isbn=9787111421900, douban=8.8] //同一条语句,第二次查询,从缓存中拿数据,没有执行新的sql查询
    Book [id=2, name=疯狂Java讲义 第3版, press=电子工业出版社, author=李刚著, isbn=9787121236693, douban=7.8]
    Book [id=4, name=Java编程思想 第4版, press=机械工业出版社, author=(美)Bruce Eckel著, isbn=9787111213826, douban=9.1]
    Book [id=5, name=深入理解Java 7 核心技术与最佳实践, press=机械工业出版社, author=成富著, isbn=9787111380399, douban=9.9]
    DEBUG [main] - ==> Preparing: SELECT * FROM book WHERE id=? //其实要查询的数据在缓存已经存在,但是还是执行了一次查询
    DEBUG [main] - > Parameters: 5(Integer)
    DEBUG [main] - <
    Total: 1
    Book [id=5, name=深入理解Java 7 核心技术与最佳实践, press=机械工业出版社, author=成富著, isbn=9787111380399, douban=9.9]

    测试结论之二:要最终执行相同的sql语句,才会到一级缓存中拿数据

    MyBatis使用HashMap存储缓存信息,key根据查询出来的对象生成,value是查询出来的对象

    使用一级缓存要特别注意:缓存的作用范围是SqlSession,假如SqlSessionA中已经有了一级缓存,而SqlSessionB修改了数据库的数据,此时SqlSessionA再执行相同查询,会从SqlSessionA的一级缓存中拿数据,导致跟数据库中的不同。关于这一点,是否有别的配置可以解决,还没见到,或许要通过编程解决,看下面的测试代码:

    package net.sonng.mbt.test;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.util.ArrayList;
    import java.util.List;
    
    import net.sonng.mbt.entity.Book;
    import net.sonng.mbt.mapper.BookMapper;
    
    import org.apache.ibatis.io.Resources;
    import org.apache.ibatis.session.SqlSession;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.apache.ibatis.session.SqlSessionFactoryBuilder;
    
    public class BookTest {
        public static void main(String[] args) throws IOException{
            InputStream inputStream=Resources.getResourceAsStream("mybatis-config.xml");
            SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
            SqlSession session1=sqlSessionFactory.openSession();
    
            BookMapper bookMapper1=session1.getMapper(BookMapper.class);
            Book book1=bookMapper1.findBookById(5);                      //session1的第一次查询,入库查询
            System.out.println(book1);
            Book book2=bookMapper1.findBookById(5);                      //session1的第二次查询,从一级缓存中拿数据
            System.out.println(book2);
            
            SqlSession session2=sqlSessionFactory.openSession();     
            BookMapper bookMapper2=session2.getMapper(BookMapper.class);
            Book book3=bookMapper2.findBookById(5);                      //session2的第一次查询,入库查询
            book3.setDouban(10.0f);
            bookMapper2.updateBook(book3);
            session2.commit();                                           //session2修改了数据库中的数据,并且提交了事务
            Book book4=bookMapper2.findBookById(5);                      //session2的第二次查询,入库查询
            System.out.println(book4);
            session2.commit();
            
            Book book5=bookMapper1.findBookById(5);                       //session1的第三次查询,从一级缓存中拿到过时的数据
            System.out.println(book5);
        }
    }
    

    输出如下:

    DEBUG [main] - ==> Preparing: SELECT * FROM book WHERE id=? //session1第一次查询,执行sql,入库查询
    DEBUG [main] - > Parameters: 5(Integer)
    DEBUG [main] - <
    Total: 1
    Book [id=5, name=深入理解Java 7 核心技术与最佳实践, press=机械工业出版社, author=成富著, isbn=9787111380399, douban=6.9]
    Book [id=5, name=深入理解Java 7 核心技术与最佳实践, press=机械工业出版社, author=成富著, isbn=9787111380399, douban=6.9] //session1第二次查询,从一级缓存中拿数据
    DEBUG [main] - ==> Preparing: SELECT * FROM book WHERE id=? //session2第一次查询,执行sql,入库查询
    DEBUG [main] - > Parameters: 5(Integer)
    DEBUG [main] - <
    Total: 1
    DEBUG [main] - ==> Preparing: UPDATE book SET name=?, press=?, author=?, isbn=?, douban=? WHERE id=5 //session2修改了数据库的数据,且提交了事务
    DEBUG [main] - > Parameters: 深入理解Java 7 核心技术与最佳实践(String), 机械工业出版社(String), 成富著(String), 9787111380399(String), 10.0(Float)
    DEBUG [main] - <
    Updates: 1
    DEBUG [main] - ==> Preparing: SELECT * FROM book WHERE id=? //session2第二次查询,执行sql,入库查询
    DEBUG [main] - > Parameters: 5(Integer)
    DEBUG [main] - <
    Total: 1
    Book [id=5, name=深入理解Java 7 核心技术与最佳实践, press=机械工业出版社, author=成富著, isbn=9787111380399, douban=10.0] //由此可以看出,数据库中的数据已经被修改
    Book [id=5, name=深入理解Java 7 核心技术与最佳实践, press=机械工业出版社, author=成富著, isbn=9787111380399, douban=6.9] //session1第三次查询,从一级缓存中拿到了过时的数据

    测试结论之三:sessionA修改了数据库的数据,不会影响到sessionB的一级缓存数据

    二级缓存

    二级缓存是跨SqlSession的,其作用范围是同一个mapper映射文件,就是同一个命名空间。
    MyBatis默认开启一级缓存,但没有开启二级缓存,因此还需要配置

    在mybatis-config.xml的settings元素中开启二级缓存

        <settings>
            <setting name="logImpl" value="LOG4J" />
            <setting name="cacheEnabled" value="true" />
        </settings>
    

    因为二级缓存是基于mapper映射文件的,因此映射文件中还要进行一些配置:

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper PUBLIC "-//ibatis.apache.org//DTDMapper 3.0//EN" "http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd">
    <mapper namespace="net.sonng.mbt.mapper.BookMapper">
        <cache eviction="LRU" flushInterval="60000" size="512" readOnly="true" />  <!-- 二级缓存配置 -->
        <select id="findAll" resultType="net.sonng.mbt.entity.Book">
            SELECT * FROM book
        </select>
        
        <select id="findBookById" parameterType="int" resultType="net.sonng.mbt.entity.Book">
            SELECT * FROM book WHERE id=#{id}
        </select>
        <select id="findBooks" parameterType="net.sonng.mbt.entity.Book" resultType="net.sonng.mbt.entity.Book">
            SELECT * FROM book WHERE 
                <choose>
                    <when test="isbn!=null" >
                        isbn=#{isbn}
                    </when>
                    <when test="name!=null">
                        `name`=#{name}
                    </when>
                    <otherwise>
                        douban&gt;7
                    </otherwise>
                </choose>
        </select>
        
        <update id="updateBook" parameterType="net.sonng.mbt.entity.Book" >
            UPDATE book 
            <set>
                <if test="name!=null" >`name`=#{name},</if>
                <if test="press!=null" >press=#{press},</if>
                <if test="author!=null" >author=#{author},</if>
                <if test="isbn!=null" >isbn=#{isbn},</if>
                <if test="douban!=null" >douban=#{douban}</if>
            </set>
            WHERE id=${id}
        </update>
        <select id="findBookInId" parameterType="list" resultType="net.sonng.mbt.entity.Book">
            SELECT * FROM book WHERE id In 
            <foreach item="id" index="i" collection="list" open="(" separator="," close=")">
                #{id}
            </foreach>
        </select>
        
        <select id="findBookLikeName" parameterType="string" resultType="net.sonng.mbt.entity.Book">
            <bind name="pattern" value="'%'+_parameter+'%'" />
            SELECT * FROM book WHERE `name` LIKE #{pattern}
        </select>
    </mapper>
    

    cache元素的几个属性:
    ----flushInterval:刷新间隔,单位毫秒。默认不设置,即不刷新,仅在调用语句时刷新。
    ----size:缓存的数目,默认1024
    ----readOnly:是否只读,默认false。true表示多次查询时返回的是这个对象的引用;false表示返回的是对象的拷贝
    ----eviction:收回策略,默认LRU。
    --------LRU:最近最少使用策略,移除长时间不被使用的对象
    --------FIFO:先进先出策略,按对象进入缓存的顺序移除
    --------SOFT:软引用策略,移除基于垃圾回收器状态和软引用规则的对象
    --------WEAK:弱引用策略

    实体类还要实现Serializable接口

    二级缓存测试之一:

    package net.sonng.mbt.test;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.util.ArrayList;
    import java.util.List;
    
    import net.sonng.mbt.entity.Book;
    import net.sonng.mbt.mapper.BookMapper;
    
    import org.apache.ibatis.io.Resources;
    import org.apache.ibatis.session.SqlSession;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.apache.ibatis.session.SqlSessionFactoryBuilder;
    
    public class BookTest {
        public static void main(String[] args) throws IOException{
            InputStream inputStream=Resources.getResourceAsStream("mybatis-config.xml");
            SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
            
            SqlSession session1=sqlSessionFactory.openSession();
            BookMapper bookMapper1=session1.getMapper(BookMapper.class);
            Book book1=bookMapper1.findBookById(5);  //第一次查询
            System.out.println(book1);
            session1.commit();  //session1提交事务,特别注意:如果这里不commit,那么没有二级缓存
            
            SqlSession session2=sqlSessionFactory.openSession();
            BookMapper bookMapper2=session2.getMapper(BookMapper.class);
            Book book2=bookMapper2.findBookById(5);  //第二次查询
            System.out.println(book2);
            session2.close();
            session1.close();
        }
    }
    

    输出如下:

    DEBUG [main] - Cache Hit Ratio [net.sonng.mbt.mapper.BookMapper]: 0.0 //session1第一次查询,二级缓存中没有数据
    DEBUG [main] - ==> Preparing: SELECT * FROM book WHERE id=?
    DEBUG [main] - > Parameters: 5(Integer)
    DEBUG [main] - <
    Total: 1
    Book [id=5, name=深入理解Java 7 核心技术与最佳实践, press=机械工业出版社, author=成富著, isbn=9787111380399, douban=10.0]
    DEBUG [main] - Cache Hit Ratio [net.sonng.mbt.mapper.BookMapper]: 0.5 //session2第一次查询,从二级缓存中命中数据
    Book [id=5, name=深入理解Java 7 核心技术与最佳实践, press=机械工业出版社, author=成富著, isbn=9787111380399, douban=10.0]

    测试结论之四:sessionA执行一次查询,如果提交了事务,查询的结果会写到二级缓存中,sessionB执行相同查询,会查询到二级缓存中的数据。注意要提交事务才会写到二级缓存中。

    二级缓存测试之二:

    package net.sonng.mbt.test;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.util.ArrayList;
    import java.util.List;
    
    import net.sonng.mbt.entity.Book;
    import net.sonng.mbt.mapper.BookMapper;
    
    import org.apache.ibatis.io.Resources;
    import org.apache.ibatis.session.SqlSession;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.apache.ibatis.session.SqlSessionFactoryBuilder;
    
    public class BookTest {
        public static void main(String[] args) throws IOException{
            InputStream inputStream=Resources.getResourceAsStream("mybatis-config.xml");
            SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
            
            SqlSession session1=sqlSessionFactory.openSession();
            BookMapper bookMapper1=session1.getMapper(BookMapper.class);
            Book book1=bookMapper1.findBookById(5);  //session1第一次查询
            System.out.println(book1);
            session1.commit();
            book1.setDouban(10.0f);
            bookMapper1.updateBook(book1);            //session1修改了数据
            session1.commit();               //标注一。
            
            SqlSession session2=sqlSessionFactory.openSession();
            BookMapper bookMapper2=session2.getMapper(BookMapper.class);
            Book book2=bookMapper2.findBookById(5);   //session2第一次查询
            System.out.println(book2);
            session2.close();
        }
    }
    

    输出如下:

    DEBUG [main] - Cache Hit Ratio [net.sonng.mbt.mapper.BookMapper]: 0.0
    DEBUG [main] - ==> Preparing: SELECT * FROM book WHERE id=?
    DEBUG [main] - > Parameters: 5(Integer)
    DEBUG [main] - <
    Total: 1
    Book [id=5, name=深入理解Java 7 核心技术与最佳实践, press=机械工业出版社, author=成富著, isbn=9787111380399, douban=6.9]
    DEBUG [main] - ==> Preparing: UPDATE book SET name=?, press=?, author=?, isbn=?, douban=? WHERE id=5
    DEBUG [main] - > Parameters: 深入理解Java 7 核心技术与最佳实践(String), 机械工业出版社(String), 成富著(String), 9787111380399(String), 10.0(Float)
    DEBUG [main] - <
    Updates: 1
    DEBUG [main] - Cache Hit Ratio [net.sonng.mbt.mapper.BookMapper]: 0.0 //session2的第一次查询,没有命中,需要入库查询,说明session1的update语句清除了二级缓存
    DEBUG [main] - ==> Preparing: SELECT * FROM book WHERE id=?
    DEBUG [main] - > Parameters: 5(Integer)
    DEBUG [main] - <
    Total: 1
    Book [id=5, name=深入理解Java 7 核心技术与最佳实践, press=机械工业出版社, author=成富著, isbn=9787111380399, douban=10.0]

    如果把测试代码二的标注一这行代码注释掉的话,也就是不提交事务,输出如下:

    DEBUG [main] - Cache Hit Ratio [net.sonng.mbt.mapper.BookMapper]: 0.0
    DEBUG [main] - ==> Preparing: SELECT * FROM book WHERE id=?
    DEBUG [main] - > Parameters: 5(Integer)
    DEBUG [main] - <
    Total: 1
    Book [id=5, name=深入理解Java 7 核心技术与最佳实践, press=机械工业出版社, author=成富著, isbn=9787111380399, douban=6.9]
    DEBUG [main] - ==> Preparing: UPDATE book SET name=?, press=?, author=?, isbn=?, douban=? WHERE id=5
    DEBUG [main] - > Parameters: 深入理解Java 7 核心技术与最佳实践(String), 机械工业出版社(String), 成富著(String), 9787111380399(String), 10.0(Float)
    DEBUG [main] - <
    Updates: 1
    DEBUG [main] - Cache Hit Ratio [net.sonng.mbt.mapper.BookMapper]: 0.5 //session2的第一次查询,命中了数据,二级缓存中还有数据
    Book [id=5, name=深入理解Java 7 核心技术与最佳实践, press=机械工业出版社, author=成富著, isbn=9787111380399, douban=10.0]

    再看数据库,数据并没有被修改,这里出现了脏读。没有提交事务,这里的update语句只修改了缓存中的数据,数据库中的数据并没有修改,另一个sqlSession脏读到了缓存中的数据。
    测试结论之五:sessionA如果执行了DML操作且提交了事务,会清空二级缓存;如果只执行DML操作但不提交事务,那么会修改二级缓存中的数据,而不会实际修改数据库的数据,这时候会导致sessionA或其他session的脏读。
    实际上只要该mapper命名空间下的sql执行了DML并提交事务,该命名空间的二级缓存都会清空。

    小结

    一级缓存默认开启,二级缓存默认不开启,需要到MyBatis-config.xml中开启
    一级缓存的作用范围是SqlSession,二级缓存是跨SqlSession的,是mapper映射文件级别的
    一级缓存不管是否提交事务,前面的查询结果都会写到一级缓存中;但二级缓存有区别,前面的查询提交了事务,才会把结果写到二级缓存中
    一级缓存会在session执行了DML时清空
    二级缓存会在命名空间下的DML语句被执行并提交了事务时清空
    顺序:二级缓存、一级缓存、入库查询
    其他:
    CSDN 《深入理解mybatis原理》 MyBatis的二级缓存的设计原理
    CSDN mybatis 一级缓存和二级缓存简介

  • 相关阅读:
    HIVE 2.1.0 安装教程。(数据源mysql)
    Linux基础命令—sleep
    Linux基础命令—echo
    C语言的基本数据类型
    Linux基础命令—rmdir
    Linux基础命令—mkdir
    Linux基础命令—cd
    Linux基础命令—pwd
    Linux周期性执行任务(crontab)
    Linux执行单一时刻定时任务管理操作(at)
  • 原文地址:https://www.cnblogs.com/sonng/p/6685040.html
Copyright © 2011-2022 走看看