缓存学习(一):EhCache

本文详细介绍了EhCache的使用,包括基本配置、XML配置、硬编码配置和直接创建Cache的方式。此外,文章还探讨了EhCache与Spring的结合,包括2.x版本的EhCacheCacheManager和3.x版本的JCacheCacheManager。最后,文章讨论了EhCache作为Hibernate和Mybatis二级缓存的配置和实践。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

1.基本使用

1.1 XML配置

1.2 硬编码

1.3 直接创建Cache

2.结合Spring使用

2.1 使用EhCacheCacheManager —— 仅支持2.x版本

2.2 使用JCacheCacheManager —— 仅支持3.x版本

3.作为二级缓存使用

3.1 Hibernate:同时支持EhCache2、3

3.2 Mybatis:仅支持2.x


EhCache是一个以Java实现的开源本地缓存框架,但也支持分布式部署。它符合JSR107标准,具有简单、高性能的特点,官方宣称是为大型高并发系统设计,广泛地与Hibernate、Spring等开源框架结合使用。由于EhCache 3.x分布式部署需要结合Terracotta服务器使用,这里就不再介绍了。

EhCache目前的最新版本是3.7.0(需要Java 8以上版本),文中除非显式说明,否则本文的介绍均基于该版本。

1.基本使用

使用时,需要使用Maven引入ehcache包:

<dependency>
  <groupId>org.ehcache</groupId>
  <artifactId>ehcache</artifactId>
  <version>3.7.0</version>
</dependency>

EhCache的核心是CacheManager,每个CacheManager可包含多个Cache。因此,EhCache的创建过程就是CacheManager和Cache的配置过程,3.x版本有如下几种配置方式:

1.1 XML配置

首先需要创建一个XML配置文件,这里在resource目录下创建名为config.xml的文件,以下是示例配置:

<?xml version="1.0" encoding="UTF-8"?>
<ehcache:config
        xmlns:ehcache="http://www.ehcache.org/v3">
    <ehcache:cache alias="myCache">
        <ehcache:key-type>java.lang.String</ehcache:key-type>
        <ehcache:value-type>java.lang.String</ehcache:value-type>
        <ehcache:expiry>
            <ehcache:tti unit="minutes">20</ehcache:tti>
        </ehcache:expiry>
        <ehcache:heap>200</ehcache:heap>
    </ehcache:cache>
</ehcache:config>

config标签代表一个CacheManager定义,cache代表一个Cache定义,key-type和value-type分别代表键和值的类型,这里全部是String类型,expiry代表缓存存活时间设置,这里设置了TTI为20分钟,heap代表缓存容量,默认为entries,即存放多少个缓存对象,如果选择B、KB、MB、GB等,就是不限数量,但是限制缓存总大小。

其他的标签及其作用可参见:XML配置

然后读取配置文件,创建缓存管理器和缓存对象:

URL url=getClass().getResource("config.xml");
Configuration conf=new XmlConfiguration(url);
CacheManager cacheManager=CacheManagerBuilder.newCacheManager(conf);
cacheManager.init();
Cache cache=cacheManager.getCache("myCache",String.class,String.class);
cache.put("hello","world");
System.out.println(cache.get("hello"));
cacheManager.close();

该段代码会输出“world”。

因为都是存储键值对,所以Cache的API和Map差不多。CacheManager需要显式关闭,也可以使用try-resource形式,实现自动关闭:

try(CacheManager cacheManager=CacheManagerBuilder.newCacheManager(conf)){
    ...
}catch(Exception e){
    ...
}

1.2 硬编码

XML配置虽然容易理解,但也很麻烦,尤其是Cache的键、值类型已经在XML中定义过了,实例化时还要再定义一次,多此一举,完全可以舍弃XML,直接使用硬编码形式配置缓存,下面的配置效果和上面的XML配置完全一致:

CacheConfiguration conf=CacheConfigurationBuilder
    .newCacheConfigurationBuilder(String.class,String.class, ResourcePoolsBuilder.heap(200L))
    .withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofMinutes(20L)))
    .build();
CacheManager manager=CacheManagerBuilder.newCacheManagerBuilder()
    .withCache("myCache",conf)
    .build(true);
Cache cache=manager.getCache("myCache",String.class,String.class);
cache.put("hello","world");
System.out.println(cache.get("hello"));
manager.close();

实际上就是把XML配置变成了一个CacheConfiguration对象而已,使用起来还是挺麻烦的

1.3 直接创建Cache

如果是创建大量有共性的缓存,使用前两种方式或许还不错,但是如果创建的缓存不多,或者没什么共同点,那么还不如在创建Cache的时候再指定其特性,可以使用UserManagedCache:

UserManagedCache cache= UserManagedCacheBuilder
    .newUserManagedCacheBuilder(String.class,String.class)
    .withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofMinutes(20L)))
    .withResourcePools(ResourcePoolsBuilder.heap(200L))
    .build(true);
cache.put("hello","world");
System.out.println(cache.get("hello"));

可以看到,这种方式代码量少了不少,看着很简洁。

2.结合Spring使用

缓存一般不会单独使用,而是配合其它框架,EhCache最常见的使用场景就是作为Hibernate等ORM组件的二级缓存,不过和Spring的结合也不少见,Spring Cache就为其提供了支持。

2.1 使用EhCacheCacheManager —— 仅支持2.x版本

由于这里仅支持2.x版本,因此这里将依赖包换为2.10.0版本,同时引入Spring相关依赖:

<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>2.10.0</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>5.0.7.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.0.7.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>5.0.7.RELEASE</version>
</dependency>

由于2.x版本和3.x版本配置文件有差异,需要重新创建,这里需要注意的是,ehcache.xsd文件无法自动获取,需要手动下载下来放到classpath下:

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="ehcache.xsd">
    <defaultCache maxElementsInMemory="10000" memoryStoreEvictionPolicy="LRU"/>
    <cache name="myCache" timeToLiveSeconds="7200" maxEntriesLocalHeap="200"/>
</ehcache>

Spring配置文件中需要配置EhCacheManagerFactoryBean和EhCacheCacheManager两个Bean:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
       xmlns:cache="http://www.springframework.org/schema/cache"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/cache
       http://www.springframework.org/schema/cache/spring-cache.xsd">
    <cache:annotation-driven/>
    <bean id="cacheManagerFactory" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
        <property name="configLocation" value="classpath:config.xml"/>
    </bean>
    <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
        <property name="cacheManager" ref="cacheManagerFactory"/>
    </bean>
    <bean id="cache" class="cache.DoCache"/>
</beans>

cache:annotation-driven表示开启Spring Cache注解。下面是用注解配置的Bean类,包含两个方法,一个用来存入缓存,一个用来清除缓存:

public class DoCache {
    @Cacheable(value="myCache")
    public String putHello(){
        return "Hello World";
    }

    @CacheEvict(allEntries = true,value="myCache")
    public void removeHello(){

    }
}

最后就是创建Spring容器和CacheManager,调用Bean方法进行测试:

ApplicationContext context=new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
DoCache doCache= (DoCache) context.getBean("cache");
EhCacheCacheManager cacheManager= (EhCacheCacheManager) context.getBean("cacheManager");
Cache cache=cacheManager.getCacheManager().getCache("myCache");
doCache.putHello();
System.out.println(cache.get(cache.getKeys().get(0)));
doCache.removeHello();
System.out.println(cache.getSize());

注意这里要先调用EhCacheCacheManager的getCacheManager,再getCache,或者直接获取名为cacheManagerFactory的Bean。输出如下:

[ key = SimpleKey [], value=Hello World, version=1, hitCount=1, CreationTime = 1555055664418, LastAccessTime = 1555055664418 ]
0

Spring Cache提供了五个常用注解:

  • @Cacheable:会将返回值存入缓存,如果没有参数,则默认key为SimpleKey.EMPTY,只有第一次会真正调用方法,之后再调用到方法时,会直接从缓存中取值返回,类似的有@CacheResult
  • @CachePut:基本类似于@Cacheable,不同之处在于,被该注解标记的方法每次都会真实调用,并将产生的值作为新的缓存值更新到缓存中
  • @CacheEvict:触发缓存实效操作,通过cacheName和key指定要清除的缓存,或者用allEntries清除所有缓存,类似的有@CacheRemove、@CacheRemoveAll
  • @Caching:可以设置多个@Cacheable、@CachePut、@CacheEvict
  • @CacheConfig:作用于类上,用于配置cacheName等公共属性

2.2 使用JCacheCacheManager —— 仅支持3.x版本

由于EhCache 3.x基于JSR107实现,因此需要额外引入javax.cache包,完整的依赖列表如下:

<dependency>
    <groupId>org.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>3.7.0</version>
</dependency>
<dependency>
    <groupId>javax.cache</groupId>
    <artifactId>cache-api</artifactId>
    <version>1.1.0</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>5.0.7.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.0.7.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>5.0.7.RELEASE</version>
</dependency>

首先需要修改Spring配置文件,将EhCacheManagerFactoryBean和EhCacheCacheManager替换为JCache实现:

<cache:annotation-driven/>
    <bean id="cacheManagerFactory" class="org.springframework.cache.jcache.JCacheManagerFactoryBean">
    <property name="cacheManagerUri" value="classpath:config.xml"/>
</bean>
<bean id="cacheManager" class="org.springframework.cache.jcache.JCacheCacheManager">
    <property name="cacheManager" ref="cacheManagerFactory"/>
</bean>
<bean id="cache" class="cache.DoCache"/>

接下来,在EhCache 3.x配置文件基础上进行修改,增加JSR107配置:

<?xml version="1.0" encoding="UTF-8"?>
<ehcache:config
        xmlns:ehcache="http://www.ehcache.org/v3"
        xmlns:jsr107="http://www.ehcache.org/v3/jsr107">
    <ehcache:service>
        <jsr107:defaults>
            <jsr107:cache name="mycache" template="defaultCache"/>
        </jsr107:defaults>
    </ehcache:service>
    <ehcache:cache-template name="defaultCache">
        <ehcache:expiry>
            <ehcache:tti unit="minutes">20</ehcache:tti>
        </ehcache:expiry>
        <ehcache:heap>200</ehcache:heap>
    </ehcache:cache-template>
</ehcache:config>

可以看到,这里没有配置键值类型。接下来,CacheManager和Cache的构造方式也要改变:

ApplicationContext context=new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
DoCache doCache= (DoCache) context.getBean("cache");
JCacheCacheManager manager= (JCacheCacheManager) context.getBean("cacheManager");
Cache cache=manager.getCacheManager()
     .createCache("myCache",new MutableConfiguration<>());
doCache.sayHello();
cache.forEach(o-> System.out.println(((Cache.Entry)o).getValue()));
doCache.removeHello();
System.out.println(cache.iterator().hasNext());

此处变为调用getBean获取CacheManager,调用createCache创建Cache对象,且必须保证createCache发生在操作缓存之前。上述代码运行后的输出如下,表明运行成功:

Hello World
false

3.作为二级缓存使用

多级缓存在分布式系统中很常用,主要作用是通过多次缓冲,不断减缓数据流入的速度,例如一个Redis+Tomcat的应用,系统内部使用了Mybatis,那么请求首先到达Redis,未命中再进入Tomcat缓存,仍未命中再进入Mybatis缓存,此时如果还没有命中,才会去数据库取数据。如果没有这样的多级缓存,一旦Redis由于流量过大宕机,就会导致流量直接打向数据库,造成雪崩。通过Tomcat和Mybatis的拦截,至少可以在一定程度上减缓数据库收到的冲击。

Java Web应用最常用的Hibernate和Mybatis都支持二级缓存的配置,不过这里的二级缓存除了作为一级缓存的后备,存放一些热度或并发度较低的数据之外,还作为多个Session的共享缓存。

3.1 Hibernate:同时支持EhCache2、3

类似于Spring,Hibernate可以同时支持EhCache2、3,如果要使用EhCache2,需要引入hibernate-ehcache包,如果使用EhCache3,需要引入hibernate-jcache包。下面以3.x为例,EhCache的配置文件和1.1节相同。

首先需要配置hibernate的配置文件,这里使用注解来配置映射,所以mapper标签没有指定xml文件,此外hibernate-configuration不能带xmlns属性,否则会提示找不到元素:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-configuration>
    <session-factory>
        //设置JDBC属性
        <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="hibernate.connection.username">root</property>
        <property name="hibernate.connection.password">123456</property>
        <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/test</property>
        //进行一些配置,如是否打印sql语句等
        <property name="show_sql">true</property>
        <property name="format_sql">true</property>
        <property name="hbm2ddl.auto">update</property>
        <property name="hibernate.connection.autocommit">true</property>
        //启用二级缓存
        <property name="hibernate.cache.use_second_level_cache">true</property>
        //启用查询缓存
        <property name="hibernate.cache.use_query_cache">true</property>
        //以下两条为二级缓存实现类的配置
        <property name="hibernate.cache.provider_configuration_file_resource_path">config.xml</property>
        <property name="hibernate.cache.region.factory_class">org.hibernate.cache.jcache.internal.JCacheRegionFactory</property>
        //绑定session,以便通过getSession获取对象
        <property name="hibernate.current_session_context_class">thread</property>
        //开启统计
        <property name="hibernate.generate_statistics">true</property>
        //配置实体映射
        <mapping class="cache.User"/>
    </session-factory>
</hibernate-configuration>

接下来是编写Entity,@Cache的usage属性配置了缓存策略:

@Entity(name = "user")
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class User implements Serializable {
    private static final long serialVersionUID = -570936907944909799L;
    @Id
    private int id;
    @Column(name="username")
    private String userName;
    @Column(name = "password")
    private String passWord;

    //下面是setter、getter和toString
}

 然后进行两次查询,以测试缓存效果:

    public static void main(String[] args) {
        Configuration conf=new Configuration();
        conf.configure();
        SessionFactory factory=conf.buildSessionFactory();

        Session session1=factory.openSession();
        session1.beginTransaction();
        Query query=session1.createQuery("from user");
        query.setCacheable(true);
        System.out.println(query.list());
        session1.getTransaction().commit();

        Session session2=factory.openSession();
        session2.beginTransaction();
        User user = session2.load(User.class,1);
        System.out.println(user);

        Statistics s=factory.getStatistics();
        System.out.println("put:"+s.getSecondLevelCachePutCount());
        System.out.println("hit:"+s.getSecondLevelCacheHitCount());
        System.out.println("miss:"+s.getSecondLevelCacheMissCount());
    }

可以看到,这里进行的两次查询分别属于两个Session,由于一级缓存是绑定在Session作用域的,所以第二次查询时不会用到它,而是从二级缓存中取值,最后一段的统计信息中,如果hit值不为0,说明确实是从缓存中取的值。输出如下:

Hibernate: 
    select
        user0_.id as id1_0_,
        user0_.password as password2_0_,
        user0_.username as username3_0_ 
    from
        user user0_
[User{id=1, userName='zhangsan', passWord='123456'}, User{id=2, userName='lisi', passWord='123abc'}]
User{id=1, userName='zhangsan', passWord='123456'}
put:2
hit:1
miss:0

3.2 Mybatis:仅支持2.x

mybatis提供了mybatis-ehcache包,但是没有找到mybatis-jcache包,因此这里认为Mybatis仅支持ehcache 2.x。下面是一个使用示例,EhCache配置文件和2.1节相同,需要注意的是,和Hibernate不同,这里还需要引入mybatis本体。此处的例子是在 学习Mybatis(1):独立使用 的基础上进行修改。

首先是修改mybatis_config.xml文件,增加日志配置,以便观察缓存效果,其他配置保持不变:

<configuration>
    <settings>
        ...
        <setting name="logImpl" value="STDOUT_LOGGING"></setting>
    </settings>
    ...
</configuration>

然后是修改UserService.xml,增加缓存配置:

<mapper namespace="mybatis.UserService">
    <cache type="org.mybatis.caches.ehcache.EhcacheCache">
        <property name="maxEntriesLocalHeap" value="100"/>
        <property name="timeToLiveSeconds" value="3600"/>
    </cache>
    ...
</mapper>

这里的属性设置是默认设置,如果用户编写了ehcache的配置文件,会优先读取配置文件。然后就是进行两次查询操作:

    public static void main(String[] args) throws IOException {
        SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis_config.xml"));
        SqlSession sqlSession=sqlSessionFactory.openSession();
        UserService userMapper=sqlSession.getMapper(UserService.class);
        User user=userMapper.getUser(1);
        System.out.println(user);
        sqlSession.close();

        SqlSession sqlSession2=sqlSessionFactory.openSession();
        UserService userMapper2=sqlSession2.getMapper(UserService.class);
        User user2=userMapper2.getUser(1);
        System.out.println(user2);
        sqlSession2.close();
    }

这里也是建立了两个SqlSession进行查询,如果二级缓存生效,则日志会显示命中:

==>  Preparing: select * from User where id=?; 
==> Parameters: 1(Integer)
<==    Columns: id, username, password
<==        Row: 1, zhangsan, 123456
<==      Total: 1
User{id=1, userName='zhangsan', passWord='123456'}
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@5c1a8622]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@5c1a8622]
Returned connection 1545242146 to pool.
Cache Hit Ratio [cache.UserService]: 0.5
User{id=1, userName='zhangsan', passWord='123456'}

可以看到“Cache Hit”字样,说明缓存生效。顺带一提,这里如果注释掉mybatis_config.xml中cacheEnable的配置,二级缓存依然生效,也印证了我在 学习Mybatis(8):Mybatis缓存机制的内部实现 中所说的,二级缓存不需要显式开启,而是默认就启用了的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值