【问题标题】:Cache ignored using Spring 4 @Transactional + Hibernate 4 + EHCache使用 Spring 4 @Transactional + Hibernate 4 + EHCache 忽略缓存
【发布时间】:2014-06-04 03:25:54
【问题描述】:

几天以来,我在 Spring 上下文中使用 @Transactional 注释的 Hibernate 缓存被阻止了。

我尝试了在网上找到的所有解决方案,但没有成功......

唯一可行的解​​决方案是使用 @Cacheable Spring 注释(来自 spring-context-support),但我不满意,因为我不能在我的实体上使用 Hibernate @Cache 注释。 @Cacheable 只能用于诸如服务方法之类的方法上,而我在没有方法的情况下检索实体...

例如:

我调用以下获取 CollectionEntity 的服务

@Override
@Transactional(readOnly = true)
public CollectionEntity getById(Integer collectionId) throws Exception {
    if(collectionId < 1) {
        logger.debug("Cannot retrieve a collection from identifier inferior to 1.");
        return null;
    }
    return collectionDao.getById(collectionId);
}

一个 CollectionEntity 包含一个 ProgramEntity 集

@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinTable(name = CollectionProgram.TABLE, joinColumns = { @JoinColumn(name = COLUMN_COLLECTION_ID, referencedColumnName = CollectionProgram.COLUMN_COLLECTION_ID) }, inverseJoinColumns = { @JoinColumn(name = CollectionProgram.COLUMN_PROGRAM_ID, referencedColumnName = ProgramEntity.COLUMN_PROGRAM_ID) })
private Set<ProgramEntity> programs = new HashSet<ProgramEntity>(0);

这些节目包含一个 ProgramBroadcasting 集

@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@JoinColumn(name = ProgramBroadcastingEntity.COLUMN_PROGRAM_ID)
private Set<ProgramBroadcastingEntity> broadcastings = new HashSet<ProgramBroadcastingEntity>(0);

而这些广播的节目包含一个ChannelEntity(一个参考数据)

@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = COLUMN_CHANNEL_ID, nullable = false)
private ChannelEntity channel;

所以如果我想缓存 ChannelEntity,我通常只需要在它的类上加上以下注解。

@Entity
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = "Channel")
@Table(name = ChannelEntity.TABLE)
public class ChannelEntity implements java.io.Serializable {

“EAGER”提取是一个非常有吸引力的参考数据解决方案!但是如果想使用@Cacheable,这是目前唯一的解决方案,我必须用 FetchType.LAZY 声明 ChannelEntity 并编写一个服务类来将@Cacheable 放在它上面只是为了缓存这些数据。 开个玩笑……我不会对所有引用数据类都这样做……

真正的解决方案是在 ChannelEntity 上添加一个有效的 Hibernate @Cache 注解。

为了让它发挥作用,我什至开发了我自己的“SingletonEhCacheRegionFactory”类,使用 Spring 类“EhCacheManagerFactoryBean”作为缓存管理器进行初始化。 并且继续从数据库中获取数据。该解决方案适用于我的一位同事,但适用于旧版本的 Spring (

所以,我真的需要你的帮助。

在给你我的配置之前,这是我对主类的快速测试,以从数据库中检索 ChannelEntity,然后从缓存中检索。

这里的测试没问题(它工作):

private static void testGetChannelFromCache2(BeanFactory factory) throws Exception {
    SessionFactoryImpl sessionFactoryImpl = ((SessionFactoryImpl) factory.getBean("sessionFactory"));
    Session session = sessionFactoryImpl.openSession();
    session.beginTransaction();

    ChannelEntity channel1 = (ChannelEntity) session.load(ChannelEntity.class, new Integer(1));
    System.out.println(channel1.getLabel());

    session.getTransaction().commit();
    session.close();

    Session anotherSession = sessionFactoryImpl.openSession();
    anotherSession.beginTransaction();

    // Here I put a breakpoint and I update the value directly on database.

    channel1 = (ChannelEntity) anotherSession.load(ChannelEntity.class, new Integer(1));
    System.out.println(channel1.getLabel()); // Here I print the cached value, not the new database value. Good!

    anotherSession.getTransaction().commit();
    anotherSession.close();
}

但这不是真正的上下文。在我的服务层,我不直接操作事务,我使用了@Transactional Spring 注释。 这是一个更现实的测试:

private static void testGetChannelFromCache1(BeanFactory factory) throws Exception {
    ChannelService service = (ChannelService) factory.getBean("channelServiceImpl");
    ChannelEntity entity1 = service.getChannelByCode(ChannelCode.ARTE);
    if(entity1 != null) {
        System.out.println(entity1.getLabel());
    }

    // Here I put a breakpoint and I update the value directly on database.

    ChannelEntity entity2 = service.getChannelByCode(ChannelCode.ARTE);
    if(entity2 != null) {
        System.out.println(entity2.getLabel()); // Here I print the new database value, not the cached value. Not good...
    }
}

这里是 ChannelService:

@Service
@Transactional(rollbackFor = Exception.class)
public class ChannelServiceImpl implements ChannelService {

    @Log
    private Logger logger;

    @Inject
    private ChannelDao channelDao;

    @Override
    @Transactional(readOnly = true)
    public ChannelEntity getChannelByCode(final ChannelCode code) throws Exception {
        if(code == null) {
            logger.debug("Cannot find Channel from null code.");
            return null;
        }
        return channelDao.getByCode(code);
    }
}

现在,我的配置...

依赖关系:

<!-- Hibernate -->
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-entitymanager</artifactId>
</dependency>

<!-- Ehcache -->
<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache-core</artifactId>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-ehcache</artifactId>
</dependency>

休眠配置:

hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory
hibernate.cache.use_second_level_cache=true
hibernate.cache.use_query_cache=true
hibernate.cache.generate_statistics=true
net.sf.ehcache.configurationResourceName=/config/ehcache/ehcache.xml

EHCache 配置:

<cache name="Channel"
       maxEntriesLocalHeap="10000"
       eternal="true"
       overflowToDisk="false">

我使用 Spring 4.0.3.RELEASE、Hibernate 4.3.4.Final 和 EHCache 2.6.8。

更准确地说,@Cache Hibernate 注解似乎有效,但并不完全... 事实上,我在 Hibernate 源代码上设置了几个断点,我注意到 Hibernate 将 ChannelEntity 放在缓存中,在我第二次调用 ChannelService 之后,在缓存上执行获取并检索通道实体! 但是...,Hibernate 仍然执行以下数据库请求并检索数据库值。真奇怪!

从 Channel this_ 中选择 this_.ChannelId 作为 ChannelI1_3_0_,this_.Code 作为 Code2_3_0_,this_.Label 作为 Label3_3_0_ where this_.Code=?

有人知道这种奇怪的行为吗??

非常感谢您的帮助!

【问题讨论】:

  • 我已经发布了一些可能出错的事情,看看我写的关于这些缓存的陷阱的这篇博客文章,它可能会有所帮助 -> blog.jhades.org/…

标签: java spring hibernate caching ehcache


【解决方案1】:

二级缓存和查询缓存有一些看起来很奇怪但实际上很正常的问题。

例如,实体上的@Cache 将仅缓存由其 Id 加载的实体。如果要缓存查询的结果而不是按Id加载,则需要将查询标记为可缓存:

@NamedQuery(name="account.queryName",
   query="select acct from Account ...",
   hints={
       @QueryHint(name="org.hibernate.cacheable",
       value="true")
   }     
})

或者在条件查询的情况下:

List cats = session.createCriteria(Cat.class)
    .setCacheable(true)
    .list();

默认情况下,一对多关系也不会被缓存。如果要缓存关联,则需要用@Cache 独立标记它们。例如:

@Cache(CacheConcurrencyStrategy.READ_WRITE)
@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<ProgramEntity> programs = new HashSet<ProgramEntity>(0);

【讨论】:

  • 太棒了!非常感谢 jhadesdev!我天真地认为,当我在实体上添加 @Cache 注释时,无论我以何种方式获取该实体(实体、集合、查询),该实体都会添加到同一个缓存区域中。这就是为什么“testGetChannelFromCache2()”方法有效(我通过 id 获取实体)而不是“testGetChannelFromCache1()”方法(我通过代码获取它)。显然,当我将相应的查询标记为可缓存时,第二个有效...... ;-) 根据我正在编写的测试,集合似乎并不容易......但没关系!谢谢!
【解决方案2】:

这真是太酷了 jhadesdev!

我为我的实体、它们的 FetchType.EAGER 属性和我的查询定义了 EHCache 缓存。

然后,我在实体及其 FetchType.EAGER 属性上添加了 @Cache 注释。 我还在服务上添加了“可缓存”属性,允许检索定义为 FetchType.LAZY 的实体属性的值以及我的其他查询。

一切正常!!

非常感谢你给了我实现缓存的缺失概念!!! :-)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-04-24
    • 1970-01-01
    • 2017-09-19
    • 2012-12-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多