精华内容
下载资源
问答
  • mybaits提供一级缓存,和二级缓存。 缓存模式图如图 一级缓存是SqlSession级别的缓存。在操作数据库时需要构造 sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的...
    查询缓存

    1.什么是查询缓存
    mybatis提供查询缓存,用于减轻数据压力,提高数据库性能。
    mybaits提供一级缓存,和二级缓存。

    缓存模式图如图



    一级缓存是SqlSession级别的缓存。在操作数据库时需要构造 sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。

    二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。

    为什么要用缓存?
    如果缓存中有数据就不用从数据库中获取,大大提高系统性能。

    2.一级缓存
    2.1一级缓存工作原理

    如图



    第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息。
    得到用户信息,将用户信息存储到一级缓存中。

    如果sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。

    第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息。

    2.2一级缓存测试
    mybatis默认支持一级缓存,不需要在配置文件去配置。

    按照上边一级缓存原理步骤去测试。

    测试1:
    //测试一级缓存
    @Test
    public void testCache1() throws Exception{
    	SqlSession sqlSession = sqlSessionFactory.openSession();//创建代理对象
    	UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    	
    	//下边查询使用一个SqlSession
    	//第一次发起请求,查询id为1的用户
    	User user1 = userMapper.findUserById(1);
    	System.out.println(user1.getUsername());
    
    	//第二次发起请求,查询id为1的用户
    	User user2 = userMapper.findUserById(1);
    	System.out.println(user2.getUsername());
    	
    	sqlSession.close();
    	
    }

    测试结果和输出日志:
    DEBUG [main] - Opening JDBC Connection
    DEBUG [main] - Created connection 19140890.
    DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.Connection@124111a]
    DEBUG [main] - ==>  Preparing: SELECT * FROM USER WHERE id=? 
    DEBUG [main] - ==> Parameters: 1(Integer)
    DEBUG [main] - <==      Total: 1
    张三
    张三
    DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.Connection@124111a]
    DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.Connection@124111a]
    DEBUG [main] - Returned connection 19140890 to pool.

    可以看到第二次取user2的时候没有发出sql查询,说明调用了一级缓存

    接下来测试看看在中间执行了增删改查的时候会不会清空一级缓存
    //测试一级缓存
    @Test
    public void testCache1() throws Exception{
    	SqlSession sqlSession = sqlSessionFactory.openSession();//创建代理对象
    	UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    	
    	//下边查询使用一个SqlSession
    	//第一次发起请求,查询id为1的用户
    	User user1 = userMapper.findUserById(1);
    	System.out.println(user1.getUsername());
    	
    	/*如果sqlSession去执行commit操作(执行插入、更新、删除),
    	 * 清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
    	 */
    	
    	//更新user1的信息
    	user1.setUsername("测试用户22");
    	userMapper.updateUser(user1);
    	//执行commit操作去清空缓存
    	sqlSession.commit();
    	
    	//第二次发起请求,查询id为1的用户
    	User user2 = userMapper.findUserById(1);
    	System.out.println(user2.getUsername());
    	
    	sqlSession.close();
    	
    }
    

    测试结果和输出日志:
    DEBUG [main] - Opening JDBC Connection
    DEBUG [main] - Created connection 6778431.
    DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.Connection@676e3f]
    DEBUG [main] - ==>  Preparing: SELECT * FROM USER WHERE id=? 
    DEBUG [main] - ==> Parameters: 1(Integer)
    DEBUG [main] - <==      Total: 1
    张三
    DEBUG [main] - ==>  Preparing: update user set username=?,birthday=?,sex=?,address=? where id=? 
    DEBUG [main] - ==> Parameters: 测试用户2(String), 2015-06-07(Date), 男(String), 河南焦作(String), 1(Integer)
    DEBUG [main] - <==    Updates: 1
    DEBUG [main] - Committing JDBC Connection [com.mysql.jdbc.Connection@676e3f]
    DEBUG [main] - ==>  Preparing: SELECT * FROM USER WHERE id=? 
    DEBUG [main] - ==> Parameters: 1(Integer)
    DEBUG [main] - <==      Total: 1
    测试用户2
    DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.Connection@676e3f]
    DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.Connection@676e3f]
    DEBUG [main] - Returned connection 6778431 to pool.

    发现第二次取数据时发出了sql请求,说明执行增删改查的时候会清空一级缓存
    12:06 2015/6/21
    2.3一级缓存应用

    正式开发,是将mybatis和spring进行整合开发,事务控制在service中。
    一个service方法中包括 很多mapper方法调用。


    service{
    //开始执行时,开启事务,创建SqlSession对象
    //第一次调用mapper的方法findUserById(1)

    //第二次调用mapper的方法findUserById(1),从一级缓存中取数据
    //方法结束,sqlSession关闭
    }



    如果是执行两次service调用查询相同 的用户信息,不走一级缓存,因为service方法结束,sqlSession就关闭,一级缓存就清空。这个时候就需要使用二级缓存,详情看下一篇总结

    转载请注明出处:http://blog.csdn.net/acmman/article/details/46696265

    展开全文
  • Android图片三级缓存原理及用LruCache、DiskLruCache动手打造个三级缓存的ImageLoader三级缓存概述三级缓存的流程强引用 、弱引用 、软引用、虚引用的区别:内存缓存LruCacheDiskLruCache: 三级缓存概述 缓存是...

    首先附上我的几篇其它文章链接感兴趣的可以看看,如果文章有异议的地方欢迎指出,共同进步,顺便点赞谢谢!!!
    Android framework 源码分析之Activity启动流程(android 8.0)
    Android studio编写第一个NDK工程的过程详解(附Demo下载地址)
    面试必备1:HashMap(JDK1.8)原理以及源码分析
    面试必备2:JDK1.8LinkedHashMap实现原理及源码分析
    Android事件分发机制原理及源码分析
    View事件的滑动冲突以及解决方案
    Handler机制一篇文章深入分析Handler、Message、MessageQueue、Looper流程和源码

    三级缓存概述

    缓存是一种通用的思想可以用在很多场景中,但在实际的开发中经常用于Bitmap的缓存,用于提高图片的加载效率、提升产品的用户体验和节省用户流量。目前常见的缓存策略是LruCache和DiskLruCachey用它们分别实现内存缓存和硬盘缓存。Lru是Least Recently Used的缩写即最近最少使用算法,这种算法思想是:当缓存容量快满时,会删除最近做少使用的缓存对象。在这里提醒大家一句三级缓存一般针对的是加载网络图片时常用的缓存策略。
    附上我自定义的ImageLoader的代码地址去理解三级缓存原理:https://github.com/mayanhu/ImageLoader

    三级缓存的流程

    当我们要加载一张网络上的图片时一般流程:

    1. 按照一定的规则生成一个缓存Key,生成可以的算法自己可以定义保证唯一性就行,需要注意的是我们的key不要直接用图片的URL因为URL中的的特殊字符不方便处理
    2. 先去内存中通过key去读取内存中缓存的读取图片资源,如果内存中有则直接返回该对象
    3. 如果内存中没有则根据key从磁盘中读取,如果磁盘缓存有则:1. 返回该资源 2.将该资源重新放入内存缓存中 3. 将改资源从磁盘缓存中移除
    4. 如果磁盘缓存中也没有改图片资源,则发起网络请求,从网络获取图片资源

    强引用 、弱引用 、软引用、虚引用的区别:

    因为LruCache内部采用LinkedHashMap以强引用的形式缓存外界的对象,所以在讲LruCache前需要先了解Java对象(即堆内存中对象)引用的四个级别以及各自的特点,以便我们能更好的掌握内存缓存LruCache的实现原理。

    1. 强引用:是指我们直接引用的对象,如String s=new Stirng(“abc”),特点是:只要引用存在,垃圾回收器永远不会回收,如果无限制的使用new 强对象会抛出OOM异常。
    2. 软引用SoftReference:特点是:当一个对象只有软引用存在时,当系统内存不足时此对象会被GC回收。
    3. 弱引用WeakReference:当一个对象只有弱引用存在时,此对象随时会被GC回收。
    4. 虚引用PhantomReference:如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收器回收的活动。
    5. 在此我只是简单描述了一下对象引用的特点,以便理解LruCache的实现原理,详细的用法和特点需要大家自行查阅资料,在这里不做过多描述。

    内存缓存LruCache

    首先简单说一下三级缓存为什么优先从内存中加载:

    • 内存的加载速度时最快的,比从磁盘加载快的多
    • 另一方面内存缓存的是Bitmap对象,可直接使用,而磁盘缓存的是文件需要我们先转换成对象才能使用

    LruCache内部采用LinkedHashMap以强引用的形式缓存外界的对象,就是以键值对的形式缓存我们的Bitmap对象。LruCache是一个范型类,它是线程安全的因为它对数据的操作都加了锁。它的使用也很简单代码如下:

        LruCache<String,Bitmap> mLruCache;
        public void init(){
            //当前进程的可用内存
            int mxaMeory=(int)(Runtime.getRuntime().maxMemory()/1024);
            //当前进程的可用内存/8为缓存大小
            int meoryChache=mxaMeory/8;
            //初始化缓存大小 复写sizeOf计算缓存对象的大小,单位要和总容量的一直
            mLruCache=new LruCache<String, Bitmap>(meoryChache){
                @Override
                protected int sizeOf(String key, Bitmap bitmap) {
                    return bitmap.getRowBytes()*bitmap.getHeight()/1024;//单位要和meoryChache保持一致
                }
    
                @Override
                protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
                    //此方法有需要是复写,做一些资源回收动做,在LruCache移除最近最少使用的对象时自动调用
                    //比如为了提高内存缓存的效率,我可在用一个弱引用的LinkHashMap去存储不常使用的对象,
                    //实现进一步的缓存
                    super.entryRemoved(evicted, key, oldValue, newValue);
                }
            };
        }
    

    除了创建外LruCache还提供了:

      //存储
       		mLruCache.put(key,value);
      //获取
           // mLruCache.get(key);
      删除
    //        mLruCache.remove(key)
    

    DiskLruCache

    第二级缓存,硬盘缓存DiskLruCache,它是通过将缓存对象写入文件系统从而实现缓存,需要注意的是DiskLruCache它不是AndroidSDK的源码,但是他得到了官方文档的推荐,使用它时需要从如下网址下载:
    https://android.googlesource.com/platform/libcore/+/android-4.1.1_r1/luni/src/main/java/libcore/io/DiskLruCache.java (注意需要翻墙)。

    1:创建DiskLruCache

    DiskLruCache不能通过构造方法创建,它提供了open方法用于创建自身,如下是open()方法的源码:

    /**
       * Opens the cache in {@code directory}, creating a cache if none exists
       * there.
       *
       * @param directory  缓存目录
       * @param  appVersion 表示应用版本号一般设置为1即可,当其发生变化时DiskLruCache会清空之前所有的缓存文件
       * @param valueCount  表示一个键对应几个值,即一个缓存Key对应几个缓存文件, 一般设置为1即可
       * @maxSize  最大缓存容量
       */
      public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
          throws IOException {
        if (maxSize <= 0) {
          throw new IllegalArgumentException("maxSize <= 0");
        }
        if (valueCount <= 0) {
          throw new IllegalArgumentException("valueCount <= 0");
        }
    
        // If a bkp file exists, use it instead.
        File backupFile = new File(directory, JOURNAL_FILE_BACKUP);
        if (backupFile.exists()) {
          File journalFile = new File(directory, JOURNAL_FILE);
          // If journal file also exists just delete backup file.
          if (journalFile.exists()) {
            backupFile.delete();
          } else {
            renameTo(backupFile, journalFile, false);
          }
        }
    
    	//初始化如果存在则执行文件尾加操作,否者创建Dir创建在创建DiskLruCache的实例
        // Prefer to pick up where we left off.
        DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
        if (cache.journalFile.exists()) {
          try {
            cache.readJournal();
            cache.processJournal();
            cache.journalWriter = new BufferedWriter(
                new OutputStreamWriter(new FileOutputStream(cache.journalFile, true), Util.US_ASCII));
            return cache;
          } catch (IOException journalIsCorrupt) {
            System.out
                .println("DiskLruCache "
                    + directory
                    + " is corrupt: "
                    + journalIsCorrupt.getMessage()
                    + ", removing");
            cache.delete();
          }
        }
    
        // Create a new empty cache.
        directory.mkdirs();
        cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
        cache.rebuildJournal();
        return cache;
      }
    

    2:DiskLruCache的增加图片操作

    DiskLruCache的缓存操作是通过Editor完成,通过缓存key区获取Editor对象,Editor表示缓存对象的编辑对象,通过Editor对象获取一个输出流指向缓存文件,实现添加缓存操作,这里需要注意的是图片的缓存一般不直接使用该图片的URL,因为URL中有可能有特殊字符影响使用。一般采用URL的MD5作为key。添加缓存操作代码如下
    1: 先去获取缓存key

       
        /**
         * 根据url  获取MD5key  因为url中可能含有特殊字符 影响在Android中直接使用
         * @param url
         * @return
         */
        private String hashKeyFormUrl(String url){
            String cacheKey;
            try {
                MessageDigest messageDigest=MessageDigest.getInstance("MD5");
                messageDigest.update(url.getBytes());
                cacheKey=bytesToHexString(messageDigest.digest());
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
                cacheKey=String.valueOf(url.hashCode());
            }
            return cacheKey;
        }
    
        /**
         *
         * @param digest
         * @return
         */
        private String bytesToHexString(byte[] digest) {
            StringBuilder sb=new StringBuilder();
            for (int i = 0; i < digest.length; i++) {
                String hex=Integer.toHexString(0xFF & digest[i]);
                if (hex.length()==1){
                    sb.append('0');
                }
                sb.append(hex);
            }
            return sb.toString();
        }
    

    2:通过缓存key获取Editor对象实现添加操作:

     /**
         * 添加一个缓存图片
         * @param urlKey
         */
        public void addDiskCacheEdit(String urlKey){
            if (mDiskLruCache==null) {
                return;
            }
            String key = hashKeyFormUrl(url);
            try {
                //通过key拿到edit对象---》outputStream
                DiskLruCache.Editor edit = mDiskLruCache.edit(key);
                if (edit != null) {
                    OutputStream outputStream = edit.newOutputStream(DISK_CACHE_INDEX);
                    //因为open方法中设置一个键对应一个值,所以DISK_CACHE_INDEX一设置为0 即可
                    //如果文件下载成功提交编辑
                    if (downloadUrlToStream(url,outputStream)){
                        //提交写操作进行提交到文件系统
                        edit.commit();
                    }else {
                        //图片下载异常 回退整个操作
                        edit.abort();
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    /**
         * 下载图片资源时,将期存入硬盘缓存目录
         * @param urlImage
         * @param outputStream
         * @return
         */
        private boolean downloadUrlToStream(String urlImage, OutputStream outputStream) {
            HttpURLConnection urlConnection=null;
            BufferedInputStream in=null;
            BufferedOutputStream out=null;
            try {
                URL url=new URL(urlImage);
                urlConnection= (HttpURLConnection) url.openConnection();
                in=new BufferedInputStream(urlConnection.getInputStream());
                out =new BufferedOutputStream(outputStream);
                int b;
                while ((b=in.read())!=-1){
                    out.write(b);
                }
                return true;
            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
               release(urlConnection,in,out);
            }
            return false;
        }
    
    

    3:DiskLruCache的获取图片操作

    和图片的添加操作类似,查找操作也是需要将url转换为key,然后通过DiskLruCache的get(String key)获取Snapshot对象,通过Snapshot获取缓存文件的输入流,通过输入流去获取缓存的Bitmap对象去使用,只是在使用时存入了内存缓存中。下面是我的获取硬盘缓存的代码:

    /**
         * 获取一个硬盘缓存图片  并且通过控件宽高进行缩放加载  避免OOM
         * @param imageUrl
         * @return
         */
        public Bitmap getBitmapChache(String imageUrl,int reqWith,int reqHeight){
            Bitmap bitmap=null;
            String keyFormUrl = hashKeyFormUrl(imageUrl);
            FileInputStream inputStream=null;
            try {
                DiskLruCache.Snapshot snapshot = mDiskLruCache.get(keyFormUrl);
                if (snapshot != null) {
                    //inputStream是一种有序的文件流,通过Options缩放存在问题,两次decodeStream影响了文件流的位置属性,第二次decodeStream时得到的的为null
                     inputStream= (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX);
                    //解决方案是通过文件夹流来得到对应文件的描述符让后通过BitmapFactory.decodeFileDescriptor来加载一张缩略图
                    FileDescriptor fileDescriptor = inputStream.getFD();
                    bitmap=BitmapUtils.decodeSampledBitmapFileDescriptor(fileDescriptor,reqWith,reqHeight);
                    if (bitmap != null) {
                        // TODO: 2019/3/6 添加到内存缓存  自定义ImageLoader
                    }
                   return bitmap;
                }
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                if (inputStream != null) {
                    try {
                        inputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            return bitmap;
        }
    
    /**
         * 文件夹流
         * @param fileDescriptor  文件夹流
         * @param reqWidth
         * @param reqHeight
         * @return
         */
      public static Bitmap decodeSampledBitmapFileDescriptor(FileDescriptor fileDescriptor,int reqWidth,int reqHeight){
          BitmapFactory.Options options=new BitmapFactory.Options();
          //inJustDecodeBounds=true只会解析图片的原始宽高信息,不会真正的去加载图片
          options.inJustDecodeBounds=true;
          //第一次decode  加载原始图片数据
          BitmapFactory.decodeFileDescriptor(fileDescriptor,null,options);
          //计算缩放比例
          options.inSampleSize=calculateInSampleSize(options,reqWidth,reqHeight);
          //重新设置加载图片
          options.inJustDecodeBounds=false;
          return BitmapFactory.decodeFileDescriptor(fileDescriptor,null,options);
      }
    

    4:删除缓存文件

     /**
         * 删除指定的缓存文件
         * @param url
         */
        private void reloveDiskCache(String url){
            String key = hashKeyFormUrl(url);
            try {
                mDiskLruCache.remove(key);
            } catch (IOException e) {
                e.printStackTrace();
            }
    
        }
    
        /**
         * 删除所有的缓存文件
         * @param 
         */
        private void clearALLCache(){
           
            try {
                mDiskLruCache.delete();
            } catch (IOException e) {
                e.printStackTrace();
            }
    
        }
    
    

    总结:

    1. 内存缓存LruCache和DiskLruCache都是通过LRU 算法实现 ,对数据的操作都是用来同步锁机制,即都是线程安全的。
    2. 内存缓存LruCache内部采用LinkedHashMap以强引用的形式缓存外界的对象,就是以键值对的形式缓存我们的Bitmap对象。
    3. DiskLruCache添加缓存图片操作是通过缓存key获取Eidtor对象,并通过Editor对象获取指向改缓存文件的输出流,通过该输出流实现添加操作。
    4. DiskLruCache获取缓存图片的操作是通过缓存key获取Snapshot对象,通过Snapshot对象可得到缓存文件输出流,通过该流获取缓存对象。
    5. 三级缓存的核心就是:LruCache和DiskLruCache的使用,即先从内存缓存中获取,如果获取不到再从硬盘缓存中获取,如果在硬盘中获取到了则:添加到内存缓存中,并从硬盘缓存中移除,如果磁盘中也没有获取到则发起网络请求进行图片下载下载成功后进行缓存。

    这里只是简单的描述了三级缓存的的原理和使用, 它也是现在主流的图片加载框架GLide、ImageLoader实现图片缓思想,只是它们的每一步做的更加精密严谨,使用更加方便,内部使用了多种设计模式实现了代码的高度解耦。

    展开全文
  • 1 MyBatis的一级缓存是基于SqlSession级别的,也就是说某个SqlSession进行某个查询操作后会将该结果暂时缓存起来,而后在所有的SqlSession没有对该表进行插入、修改、删除操作的情况下,当这个SqlSession再次发起此...

    1、 MyBatis默认开启一级缓存,MyBatis的一级缓存是基于SqlSession级别的,也就是说一级缓存的生命周期和SqlSession相同。某个SqlSession进行某个查询后会将该结果放在自己的Map对象(MyBatis自己实现了一套本地缓存,在cache包下,有个默认实现PerpetualCache,该缓存是通过HashMap实现的,DefaultSqlSession拥有几个属性,它将缓存属性放在Executor这个属性里面封装了起来。)中缓存起来。如果在之后所有的查询操作到来之前有任意的SqlSession对该表进行插入、修改、删除写操作,就会清空这个Map中所有缓存信息,重复第一次查询过程;否则当这SqlSession再次发起查询时将先从缓存中获取结果,在缓存中没有获取到结果的情况下才会进行连库查询。注意:分布式环境下可能会出现脏数据。

    2、 MyBatis的二级缓存是基于Mapper级别的,也就是说多个SqlSession去使用某个Mapper的查询语句时,得到的缓存数据是可共用的。由于是Mapper级别的缓存,多个Mapper公用一张表时,数据互不影响,因此可能出现脏数据,因此一张表只能对应一个命名空间。每次更新操作都会清空缓存,因此适合于查询多的表。二级缓存开启后,查询就会走二级缓存,没查到直接查库。MyBatis默认不开启二级缓存,要使用二级缓存需要做到两点:

    • 在MyBatis配置中指定cache-anable属性为true
    • 在需要缓存的Mapper.xml文件中添加<cache />标签,指定type缓存实现PerpetualCache,缓存丢弃策略eviction(LRU、FIFO等),刷新时间flushInterval等
    展开全文
  • 一、前言MyBatis将数据缓存设计成两级结构,分为一级缓存、二级缓存:一级缓存是Session会话级别的缓存,位于表示一次数据库会话的SqlSession对象之中,又被称之为本地缓存。一级缓存是MyBatis内部实现的一个特性,...

    一、前言

    MyBatis将数据缓存设计成两级结构,分为一级缓存、二级缓存
    一级缓存是Session会话级别的缓存,位于表示一次数据库会话的SqlSession对象之中,又被称之为本地缓存。一级缓存是MyBatis内部实现的一个特性,用户不能配置,默认情况下自动支持的缓存,用户没有定制它的权利(不过这也不是绝对的,可以通过开发插件对它进行修改);
    每当我们使用MyBatis开启一次和数据库的会话,MyBatis会创建出一个SqlSession对象表示一次数据库会话。在对数据库的一次会话中,我们有可能会反复地执行完全相同的查询语句,如果不采取一些措施的话,每一次查询都会查询一次数据库,而我们在极短的时间内做了完全相同的查询,那么它们的结果极有可能完全相同,由于查询一次数据库的代价很大,这有可能造成很大的资源浪费。
    为了解决这一问题,减少资源的浪费,MyBatis会在表示会话的SqlSession对象中建立一个简单的缓存,将每次查询到的结果结果缓存起来,当下次查询的时候,如果判断先前有个完全一样的查询,会直接从缓存中直接将结果取出,返回给用户,不需要再进行一次数据库查询了。

    如下图所示,MyBatis会在一次会话的表示—-一个SqlSession对象中创建一个本地缓存(local cache),对于每一次查询,都会尝试根据查询的条件去本地缓存中查找是否在缓存中,如果在缓存中,就直接从缓存中取出,然后返回给用户;否则,从数据库读取数据,将查询结果存入缓存并返回给用户。


    这一节,我们将简单的说说什么是一级缓存以及什么情况下会导致一级缓存失效?

    二、案例

    创建一个新的Maven工程MyBatisPrimaryCache,工程结构目录如下


    在测试类MyBatis中增加测试方法testPrimaryCache


    打印查看一下控制台

    2017-08-17 21:08:20,836 [main] [com.queen.mybatis.mapper.EmpMapper.findEmpById]-[DEBUG] ==>  Preparing: select id,emp_name empName,emp_email empEmail, dept_id deptId from t_emp where id = ? 
    2017-08-17 21:08:20,888 [main] [com.queen.mybatis.mapper.EmpMapper.findEmpById]-[DEBUG] ==> Parameters: 1(Integer)
    2017-08-17 21:08:20,924 [main] [com.queen.mybatis.mapper.EmpMapper.findEmpById]-[DEBUG] <==      Total: 1
    Emp [id=1, empName=queen3aasd21, empEmail=queen123@sina.com, deptId=1]
    Emp [id=1, empName=queen3aasd21, empEmail=queen123@sina.com, deptId=1]
    true

    从控制台我们可以看到,即便Java代码中查询Emp信息,写了两段代码,但是实际上去后台数据库查询只做了一次SQL调用,第二次查询并没有去数据库查询,第二次获取的数据与第一次一样,它就直接从缓存中拿了数据。

    如上我们简单的体验了一下一级缓存的存在

    一级缓存我们也称作SqlSession级别的缓存,一级缓存默认就是开启的。既然是SqlSession级别的缓存,那就说明每个SqlSession对象拥有自己的一份数据,每个SqlSession之间是不能共享缓存的。但是每个独立的SqlSession缓存也有失效的时候。

    下面我们通过案例看看哪些情况会导致一级缓存失效

    ①SqlSession不同

    上面第一个测试案例,我们查询emp01和emp02都是使用的同一个mapper对象,如果mpper不是同一个会是什么情况,修改代码如下:


    测试打印,查看一下控制台

    2017-08-17 21:28:41,663 [main] [com.queen.mybatis.mapper.EmpMapper.findEmpById]-[DEBUG] ==>  Preparing: select id,emp_name empName,emp_email empEmail, dept_id deptId from t_emp where id = ? 
    2017-08-17 21:28:41,731 [main] [com.queen.mybatis.mapper.EmpMapper.findEmpById]-[DEBUG] ==> Parameters: 1(Integer)
    2017-08-17 21:28:41,774 [main] [com.queen.mybatis.mapper.EmpMapper.findEmpById]-[DEBUG] <==      Total: 1
    Emp [id=1, empName=queen3aasd21, empEmail=queen123@sina.com, deptId=1]
    
    2017-08-17 21:28:41,786 [main] [com.queen.mybatis.mapper.EmpMapper.findEmpById]-[DEBUG] ==>  Preparing: select id,emp_name empName,emp_email empEmail, dept_id deptId from t_emp where id = ? 
    2017-08-17 21:28:41,787 [main] [com.queen.mybatis.mapper.EmpMapper.findEmpById]-[DEBUG] ==> Parameters: 1(Integer)
    2017-08-17 21:28:41,789 [main] [com.queen.mybatis.mapper.EmpMapper.findEmpById]-[DEBUG] <==      Total: 1
    Emp [id=1, empName=queen3aasd21, empEmail=queen123@sina.com, deptId=1]
    false

    如上,我们发现控制台打印了两段SQL,第二次查询取数据库重新获取了数据,封装成了一个新的对象,emp01==emp02 为false,说明不是同一个对象。一级缓存失效。

    ②SqlSession相同,但是查询条件不一样(当前一级缓存中还没有数据)


    必然上面查询会发出两条SQL,查询2号员工时,因为本来就是第一次查询,所以不可能存在于缓存中的数据,需要调用数据库查询数据。

    ③SqlSession相同,两次查询之间增加了增删改操作


    测试打印,查看一下控制台如下:

    2017-08-17 21:45:12,885 [main] [com.queen.mybatis.mapper.EmpMapper.findEmpById]-[DEBUG] ==>  Preparing: select id,emp_name empName,emp_email empEmail, dept_id deptId from t_emp where id = ? 
    2017-08-17 21:45:12,960 [main] [com.queen.mybatis.mapper.EmpMapper.findEmpById]-[DEBUG] ==> Parameters: 1(Integer)
    2017-08-17 21:45:13,000 [main] [com.queen.mybatis.mapper.EmpMapper.findEmpById]-[DEBUG] <==      Total: 1
    Emp [id=1, empName=queen3aasd21, empEmail=queen123@sina.com, deptId=1]
    2017-08-17 21:45:13,052 [main] [com.queen.mybatis.mapper.EmpMapper.updateEmp]-[DEBUG] ==>  Preparing: update t_emp SET emp_name=?, emp_email=? where id=? 
    2017-08-17 21:45:13,053 [main] [com.queen.mybatis.mapper.EmpMapper.updateEmp]-[DEBUG] ==> Parameters: tom123(String), tom123@sina.com(String), 3(Integer)
    2017-08-17 21:45:13,053 [main] [com.queen.mybatis.mapper.EmpMapper.updateEmp]-[DEBUG] <==    Updates: 1
    =====数据修改成功!======
    2017-08-17 21:45:13,054 [main] [com.queen.mybatis.mapper.EmpMapper.findEmpById]-[DEBUG] ==>  Preparing: select id,emp_name empName,emp_email empEmail, dept_id deptId from t_emp where id = ? 
    2017-08-17 21:45:13,054 [main] [com.queen.mybatis.mapper.EmpMapper.findEmpById]-[DEBUG] ==> Parameters: 1(Integer)
    2017-08-17 21:45:13,056 [main] [com.queen.mybatis.mapper.EmpMapper.findEmpById]-[DEBUG] <==      Total: 1
    Emp [id=1, empName=queen3aasd21, empEmail=queen123@sina.com, deptId=1]
    false

    如上所述,一级缓存失效了,虽然两次都是查询的同一条数据,但是却发了两次SQL,封装了两次对象且不是同一个对象。所以这种情况也会导致缓存失效。这种情况也比较容易理解,因为你在做修改的时候,可能就会修改到这条数据,当你修改了这条数据后,难道我还要去缓存中拿数据吗?当然是不行的。只要数据库有变化,就要再次去数据库查询数据。

    ④SqlSession相同,但是我们手动清除了一级缓存(缓存清空)


    测试打印,查看一下控制台如下:

    2017-08-17 21:56:21,619 [main] [com.queen.mybatis.mapper.EmpMapper.findEmpById]-[DEBUG] ==>  Preparing: select id,emp_name empName,emp_email empEmail, dept_id deptId from t_emp where id = ? 
    2017-08-17 21:56:21,680 [main] [com.queen.mybatis.mapper.EmpMapper.findEmpById]-[DEBUG] ==> Parameters: 1(Integer)
    2017-08-17 21:56:21,723 [main] [com.queen.mybatis.mapper.EmpMapper.findEmpById]-[DEBUG] <==      Total: 1
    Emp [id=1, empName=queen3aasd21, empEmail=queen123@sina.com, deptId=1]
    2017-08-17 21:56:21,724 [main] [com.queen.mybatis.mapper.EmpMapper.findEmpById]-[DEBUG] ==>  Preparing: select id,emp_name empName,emp_email empEmail, dept_id deptId from t_emp where id = ? 
    2017-08-17 21:56:21,724 [main] [com.queen.mybatis.mapper.EmpMapper.findEmpById]-[DEBUG] ==> Parameters: 1(Integer)
    2017-08-17 21:56:21,726 [main] [com.queen.mybatis.mapper.EmpMapper.findEmpById]-[DEBUG] <==      Total: 1
    Emp [id=1, empName=queen3aasd21, empEmail=queen123@sina.com, deptId=1]
    false

    我们发现这个时候也一样,缓存也失效了,第二次查询还是去数据库重新查询了一遍数据。因为缓存中已经没有数据了。


    =======欢迎大家拍砖,小手一抖,多多点赞哟!=======

    版权声明:本文为博主原创文章,允许转载,但转载必须标明出处。


    展开全文
  • mybatis的一级缓存实现原理

    千次阅读 2015-10-23 16:53:03
    0.写在前面  MyBatis是一个简单,小巧但功能非常强大...本文的目的则是向读者详细介绍MyBatis的一级缓存,深入源码,解析MyBatis一级缓存的实现原理,并且针对一级缓存的特点提出了在实际使用过程中应该注意的事项。
  • 一级缓存2.1 一级缓存的命中场景2.2 触发清空一级缓存2.3 一级缓存源码分析3 二级缓存3.1 二级缓存的设计3.2 二级缓存的使用3.3 二级缓存的命中场景3.4 二级缓存源码分析3.4.1 query查询操作。3.4.2 commit提交操作...
  • Mybatis二级缓存原理

    千次阅读 2018-12-12 12:32:57
    最近看了下Mybatis的源码,分析了二级缓存的实现方式,在这里把他记下来。虽然这不复杂,对这方面的博客也有很多,写的也很好。但我坚信看懂了是其,能够教别人或者描述清楚记下来才能真正的掌握。曹金桂 cao_...
  • 级缓存原理

    热门讨论 2017-03-31 22:01:15
     sqlSession1去查询用户id为1的用户信息,查询到用户信息会将查询数据存储到二级缓存中。  如果SqlSession3去执行相同mapper下sql,执行commit提交,清空该 mapper下的二级缓存区域的数据。  sqlSession2去查询...
  • Hibernate、二级缓存原理

    千次阅读 2014-07-16 14:21:22
    Hibernate的,二级缓存策略:  Hibernate中提供了两级Cache,第级别的缓存是Session级别的缓存,它是属于事务范围的缓存。这 级别的缓存由hibernate管理的,一般情况下无需进行干预;第二级别的缓存...
  • 级缓存原理 1.原理 首先看图 首先开启mybatis的二级缓存。 sqlSession1去查询用户id为1的用户信息,查询到用户信息会将查询数据存储到二级缓存中。 如果SqlSession3去执行相同 mapper下sql,执行commit提交...
  • 关于mybatis里面的一级缓存: mybatis里面的一级缓存和二级缓存实际上和hibernate里面的差别不大。 一级缓存其实通俗地来讲就是,在sqlsession里面创建一个本地缓存,然后第二次进行相同的查询时候,就不会到...
  • 本文的目的则是向读者详细介绍MyBatis的一级缓存,深入源码,解析MyBatis一级缓存的实现原理,并且针对一级缓存的特点提出了在实际使用过程中应该注意的事项。 读完本文,你将会学到: 1、什么是一级缓存?为...
  • 本节目录1、从 SQL 查询流程看一二级缓存1.1 创建Executor1.2 CachingExecutor#query1.2.1 二级缓存1.2.1.1 MappedStatement#cache属性创建机制1.2.1.2 cache标签解析1.2.1.3 cacheRef1.2.2 一级缓存2、从SQL更新...
  • Mybatis 一级缓存和二级缓存的使用

    万次阅读 2021-01-19 10:28:52
    目录Mybatis缓存一级缓存二级缓存缓存原理 Mybatis缓存 官方文档:https://mybatis.org/mybatis-3/zh/sqlmap-xml.html#cache MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制 Mybatis中...
  • hibernate二级缓存(一)一级缓存与二级缓存 1.hibernate一级缓存 hibernate的一级缓存是session级别的缓存,一级缓存hibernate默认启用且不能被卸载,一个事务内有效。 特点: 使用一级缓存的目的是为了...
  • mybatis两级缓存原理剖析

    千次阅读 2018-07-16 14:05:26
    mybatis的缓存分一级缓存与二级缓存。下面分别对这两种缓存进行详细解说。 mybatis 的调用链 首先我们需要大致的了解一下mybatis的调用链,然后才能更好的理解mybatis的缓存。 主要的api罗列如下: public ...
  • CPU中的一级缓存,二级缓存,三级缓存

    千次阅读 多人点赞 2018-06-14 13:49:56
    CPU中的一级缓存,二级缓存,三级缓存 缓存又叫高速缓冲存储器,其作用在于缓解主存速度慢、跟不上CPU读写速度要求的矛盾。 缓存的实现原理,是把CPU最近最可能用到的少量信息(数据或指令)从主存复制到CACHE中,当...
  • Mybatis 实现原理级缓存

    千次阅读 2018-10-15 15:19:20
    本片文章讲述的是Mybatis是如何无感知的让用户使用到级缓存,以及级缓存的实现细节和实现原理。 结论:Mybatis 下文通过代码DEMO的展示, 以及源码的解说介绍JDK动态代理, 和Mybatis对其的应用。 ...
  • 1、#{}和${}的区别是什么? #{}是预编译处理,${}是字符串替换。 Mybatis在处理#{}时,会...如果支持,它的实现原理是什么? 答:Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,associa.
  • Android图片三级缓存原理

    千次阅读 2015-10-18 02:02:05
    缓存原理: 从集合中读取:集合中没有的时候,...以下代码实现了图片缓存原理,并且可以作为个工具类来使用: 详情请看代码: public class ImageCache { private LruCache lurCache; private File cacheDi
  • Mybatis原理剖析之一级缓存(四)

    千次阅读 热门讨论 2018-12-18 13:42:14
    一级缓存是SqlSession级别的缓存,缓存的作用是,一是提升查询速度,二是降低数据库的并发请求压力。
  • hibernate二级缓存)二级缓存的实现原理 在前面我们将过hibernate二级缓存类似于个插件,将缓存的具体实现分离,缓存的具体实现是通过hibernate.cache.region.factory_class参数配置指定。 1. hibernate二...
  • 1、先判断二级缓存是否开启,如果没开启,再判断一级缓存是否开启,如果没开启,直接查数据库 2、如果一级缓存关闭,即使二级缓存开启也没有数据,因为二级缓存的数据从一级缓存获取 3、一般不会关闭一级缓存 4、...
  • MyBatis中的一级缓存和二级缓存介绍

    千次阅读 多人点赞 2017-06-13 20:07:22
    先说缓存,合理使用缓存是优化...一级缓存 一级缓存是SqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,在对象中有一个数据结构用于存储缓存数据。不同的sqlSession之间的缓存数据区域是互相不影响的。也
  • 在前边的学习中我们大概得了解了mybatis扫描和注入原理和sql的执行过程,还有mybatis插件的原理,在后边我们学习了mybatis的一级缓存原理。那么今天我们主要学习一下myb...
  • 其实在前面的介绍二级缓存的时候我说过,二级缓存是利用一级缓存的数据在SqlSession调用commit或者close时导入到二级缓存的,那么如果在利用一个SqlSession实现了更新等操作时便会刷新一级缓存从而导致在提交了事务...
  • hibernate二级缓存原理分析

    千次阅读 2010-03-08 16:32:00
    多人对二级缓存都不太了解,或者是有错误的认识,我一直想写一篇文章... hibernate的session提供了一级缓存,每个session,对同一个id进行两次load,不会发送两条sql给数据库,但是session关闭的时候,一级缓存就失
  • Mybatis的一级缓存和二级缓存详解

    千次阅读 多人点赞 2018-11-19 19:11:59
    注:本笔记是根据尚硅谷的MyBatis视频记录的 对于任何一个持久层框架,都有缓存机制;... 两个关于mybatis缓存额外的链接: ...关于Mybatis的一级缓存和二级缓存执行顺序具体可参考:Mybatis的一级缓存和二级缓存执行...
  • MySql缓存机制MyBatis一级缓存MyBatis一级缓存是sesssion会话级别的缓存,是基于当前SqlSession对象的,MyBatis一级缓存实际上是以当前sql为key对执行语句进行缓存(跟mysql缓存一样,修改任何参数的值都会导致缓存...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 209,361
精华内容 83,744
关键字:

一级缓存原理