首页   >   代码编程

mybatis整合ehcache配置二级缓存

Mybatis默认开启了一级缓存,这个缓存是sqlsession级别的,在同一个sqlsession中执行相同的sql,第一次会发送sql去数据库查询,后面执行时就会触发缓存,而在不同的sqlsession中,就算是相同的sql也依旧会每次都发送sql去数据库查询。

不过他还提供了二级缓存,这个缓存是mapper级别的,可以克服一级缓存的不足,只要是相同mapper中的查询方法,第一次会发送sql,后面都会触发缓存,但是mybatis默认是不开启二级缓存的,需要在配置中手动开启(这里是总开关,具体使用的时候还需要在mapper.xml中配置cache)

<?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>

    <!--在application.properties中配置也可以,但是这里配置文件就不能使用了-->
    <settings>
        <!--开启二级缓存-->
        <setting name="cacheEnabled" value="true"/>
        <!--开启日志打印-->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

</configuration>

mybatis并没有写死二级缓存的实现,而是提供了一个Cache接口来写入缓存、获取缓存、删除缓存

/**
 *    Copyright 2009-2015 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.cache;

import java.util.concurrent.locks.ReadWriteLock;

/**
 * SPI for cache providers.
 * 
 * One instance of cache will be created for each namespace.
 * 
 * The cache implementation must have a constructor that receives the cache id as an String parameter.
 * 
 * MyBatis will pass the namespace as id to the constructor.
 * 
 * <pre>
 * public MyCache(final String id) {
 *  if (id == null) {
 *    throw new IllegalArgumentException("Cache instances require an ID");
 *  }
 *  this.id = id;
 *  initialize();
 * }
 * </pre>
 *
 * @author Clinton Begin
 */

public interface Cache {

  /**
   * @return The identifier of this cache
   */
  String getId();

  /**
   * @param key Can be any object but usually it is a {@link CacheKey}
   * @param value The result of a select.
   */
  void putObject(Object key, Object value);

  /**
   * @param key The key
   * @return The object stored in the cache.
   */
  Object getObject(Object key);

  /**
   * As of 3.3.0 this method is only called during a rollback 
   * for any previous value that was missing in the cache.
   * This lets any blocking cache to release the lock that 
   * may have previously put on the key.
   * A blocking cache puts a lock when a value is null 
   * and releases it when the value is back again.
   * This way other threads will wait for the value to be 
   * available instead of hitting the database.
   *
   * 
   * @param key The key
   * @return Not used
   */
  Object removeObject(Object key);

  /**
   * Clears this cache instance
   */  
  void clear();

  /**
   * Optional. This method is not called by the core.
   * 
   * @return The number of elements stored in the cache (not its capacity).
   */
  int getSize();
  
  /** 
   * Optional. As of 3.2.6 this method is no longer called by the core.
   *  
   * Any locking needed by the cache must be provided internally by the cache provider.
   * 
   * @return A ReadWriteLock 
   */
  ReadWriteLock getReadWriteLock();

}

只提供了接口,具体实现可以让开发者自定义,非常灵活,现在市面上有很多种缓存实现,比如说:redis、ehcache等,甚至可以直接用一个hashmap来实现,这都是允许的,今天来看一下官方提供的ehcache版本,关于ehcache,在这里就不多说了,大家可以直接将它理解成一个小型的内存缓存。

引入相关的jar包

<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache-core</artifactId>
    <version>2.6.11</version>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.0.0</version>
</dependency>

创建ehcache.xml,配置默认缓存

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd">

    <!-- ehcache官网配置示例:http://www.ehcache.org/ehcache.xml -->

    <!-- 缓存的bean,必须实现Serializable序列化 -->

    <!--
    一级缓存失效的四种情况:

    1.SqlSession不同,Cache会失效
      SqlSession ss2=ssf.openSession();
      EmployeeMapper mapper2=ss2.getMapper(EmployeeMapper.class);

      Employee emp2=mapper2.getEmployeeById(38);
      System.out.println(emp2);

    2.sqlSession相同,查询条件相同,但两次查询之间执行了增删改操作
      mapper.addEmployeeBysql(new Employee(null,"yiwen","yiwen@qq.com","1"));

    3.SqlSession相同,查询条件不同
      Employee emp3=mapper.getEmployeeById(41);
      System.out.println(emp3);

    4.sqlSession相同,查询条件相同,手动清除了缓存clearCache()
      ss.clearCache();
    -->

    <!-- 缓存文件存放的磁盘路径,如果没有自定义,会使用系统默认的磁盘路径 -->
    <!-- <diskStore path="F:/cache"/> -->
<!--    <diskStore path="/usr/local/ehcache"/>-->

    <!-- 自定义缓存属性 -->
    <defaultCache maxElementsInMemory="10000"
                  eternal="false"
                  timeToIdleSeconds="300"
                  timeToLiveSeconds="600"
                  overflowToDisk="true"/>

    <!-- 上面配置中,缓存最大存活60s,最少存活30s -->
    <!-- 如果创建之后无任何访问,30s之后就会被销毁;如果一直有访问,那么一直持续到60s才会被销毁 -->

    <!--
    name:                           Cache的唯一标识
    maxElementsInMemory:            缓存中允许创建的最大对象数
    maxElementsOnDisk:              磁盘中最大缓存对象数,若是0表示无穷大
    eternal:                        Element是否永久有效,一但设置了,timeout将不起作用,对象永不过期。
    timeToIdleSeconds:              缓存数据的钝化时间,也就是在一个元素消亡之前,两次访问时间的最大时间间隔值,这只能在元素不是永久驻留时有效,如果该值是 0 就意味着元素可以停顿无穷长的时间。
    timeToLiveSeconds:              缓存数据的生存时间,也就是一个元素从构建到消亡的最大时间间隔值,这只能在元素不是永久驻留时有效,如果该值是0就意味着元素可以停顿无穷长的时间。
    overflowToDisk:                 内存不足时,是否启用磁盘缓存。
    diskPersistent:                 是否缓存虚拟机重启期数据
    diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒
    diskSpoolBufferSizeMB:          这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区
    memoryStoreEvictionPolicy:      缓存满了之后的淘汰算法。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)
    -->

</ehcache>

注释写的比较多,在文章中就不再重复了,大家可以直接看注释,上面是默认的ehcache配置,如果在mapper.xml中没有重新配置cache,那么最终生效的缓存配置就是这一份。

配置文件默认名字是ehcache.xml,如果有特殊的名字,可以在框架配置中指定路径。

mapper.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="com.wolffy.reader.mapper.ApplyMapper">

    <!--开启二级缓存-->
    <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

    <resultMap id="applyMap" type="com.wolffy.reader.entity.Apply">
        ......
    </resultMap>
	
</mapper>

在使用的mapper中,必须使用<ehcache/>标签来开启缓存,type参数用来指定缓存类型,如果是自定义缓存,那这里就是自己定义的缓存,这里是ehcache,所以是EhcacheCache,它也实现了Cache接口

/*
 *    Copyright 2010 The MyBatis Team
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.mybatis.caches.ehcache;

import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;

import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.cache.CacheException;

/**
 * Cache adapter for Ehcache.
 *
 * @version $Id: EhcacheCache.java 3454 2010-12-29 20:35:44Z simone.tripodi $
 */
public final class EhcacheCache implements Cache {

    /**
     * The cache manager reference.
     */
    private static final CacheManager CACHE_MANAGER = createCacheManager();

    /**
     * Looks for "/ehcache.xml" classpath resource and builds the relative
     * {@code CacheManager}; if it's no found or it is impossible to load it,
     * returns the default manager.
     *
     * @return the application cache manager.
     */
    private static CacheManager createCacheManager() {
        CacheManager cacheManager;
        InputStream input = EhcacheCache.class.getResourceAsStream("/ehcache.xml");

        if (input != null) {
            try {
                cacheManager = CacheManager.create(input);
            } catch (Throwable t) {
                cacheManager = CacheManager.create();
            } finally {
                try {
                    input.close();
                } catch (IOException e) {
                }
            }
        } else {
            cacheManager = CacheManager.create();
        }

        return cacheManager;
    }

    /**
     * The {@code ReadWriteLock}.
     */
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    /**
     * The cache id.
     */
    private final String id;

    /**
     *
     *
     * @param id
     */
    public EhcacheCache(final String id) {
        if (id == null) {
            throw new IllegalArgumentException("Cache instances require an ID");
        }
        this.id = id;
        if (!CACHE_MANAGER.cacheExists(this.id)) {
            CACHE_MANAGER.addCache(this.id);
        }
    }

    /**
     * {@inheritDoc}
     */
    public void clear() {
        this.getCache().removeAll();
    }

    /**
     * {@inheritDoc}
     */
    public String getId() {
        return this.id;
    }

    /**
     * {@inheritDoc}
     */
    public Object getObject(Object key) {
        try {
            Element cachedElement = this.getCache().get(key.hashCode());
            if (cachedElement == null) {
                return null;
            }
            return cachedElement.getObjectValue();
        } catch (Throwable t) {
            throw new CacheException(t);
        }
    }

    /**
     * {@inheritDoc}
     */
    public ReadWriteLock getReadWriteLock() {
        return this.readWriteLock;
    }

    /**
     * {@inheritDoc}
     */
    public int getSize() {
        try {
            return this.getCache().getSize();
        } catch (Throwable t) {
            throw new CacheException(t);
        }
    }

    /**
     * {@inheritDoc}
     */
    public void putObject(Object key, Object value) {
        try {
            this.getCache().put(new Element(key.hashCode(), value));
        } catch (Throwable t) {
            throw new CacheException(t);
        }
    }

    /**
     * {@inheritDoc}
     */
    public Object removeObject(Object key) {
        try {
            Object obj = this.getObject(key);
            this.getCache().remove(key.hashCode());
            return obj;
        } catch (Throwable t) {
            throw new CacheException(t);
        }
    }

    /**
     * Returns the ehcache manager for this cache.
     *
     * @return the ehcache manager for this cache.
     */
    private Ehcache getCache() {
        return CACHE_MANAGER.getCache(this.id);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (!(obj instanceof Cache)) {
            return false;
        }

        Cache otherCache = (Cache) obj;
        return this.id.equals(otherCache.getId());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int hashCode() {
        return this.id.hashCode();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        return "EHCache {"
                + this.id
                + "}";
    }

}

序列化javabean

package com.wolffy.reader.entity;

import java.io.Serializable;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class Apply implements Serializable {

}

这一步非常重要,如果没有序列化,是缓存不了查询出来的java对象的,直接会抛一个异常:

java.io.NotSerializableException: com.wolffy.reader.entity.Apply
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at java.util.ArrayList.writeObject(ArrayList.java:762)
	at sun.reflect.GeneratedMethodAccessor234.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.__invoke(DelegatingMethodAccessorImpl.java:43)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:45009)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:45012)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:1028)
	at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496)
	at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
	at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
	at java.io.ObjectOutputStream.defaultWriteObject(ObjectOutputStream.java:441)
	at net.sf.ehcache.Element.writeObject(Element.java:835)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.__invoke(DelegatingMethodAccessorImpl.java:43)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:45009)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:45012)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:1028)
	at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496)
	at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at net.sf.ehcache.util.MemoryEfficientByteArrayOutputStream.serialize(MemoryEfficientByteArrayOutputStream.java:97)
	at net.sf.ehcache.store.disk.DiskStorageFactory.serializeElement(DiskStorageFactory.java:405)
	at net.sf.ehcache.store.disk.DiskStorageFactory.write(DiskStorageFactory.java:384)
	at net.sf.ehcache.store.disk.DiskStorageFactory$DiskWriteTask.call(DiskStorageFactory.java:485)
	at net.sf.ehcache.store.disk.DiskStorageFactory$PersistentDiskWriteTask.call(DiskStorageFactory.java:1088)
	at net.sf.ehcache.store.disk.DiskStorageFactory$PersistentDiskWriteTask.call(DiskStorageFactory.java:1072)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180)
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

配置完毕,我们来看一下效果,可以看到查询的效率提升了很多

mybatis整合ehcache配置二级缓存

而且在指定的缓存目录/usr/local/ehcache下,也生成了很多缓存文件

mybatis整合ehcache配置二级缓存

QQ群Ⅰ: 686430774 (已满)

QQ群Ⅱ: 718410762 (已满)

QQ群Ⅲ: 638620451 (已满)

QQ群Ⅳ: 474195684

如果文章有帮到你,可以考虑请博主喝杯咖啡!

分享到:

欢迎分享本文,转载请注明出处!

作者:不忘初心

发布时间:2019-05-01

永久地址:https://www.jiweichengzhu.com/article/290c5c795b65474fb9636c9c80d7e0d6

评论