首页 文章

使用Spring 4 @Transactional Hibernate 4 EHCache忽略缓存

提问于
浏览
0

我使用@Transactional注释在Spring上下文中使用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放在其上只是为了缓存这些数据 . 这是一个笑话......我不会为我所有的参考数据类做到这一点......

一个真正的解决方案就是让一个有效的Hibernate @Cache注释放在ChannelEntity上 .

为了实现它的功能,我甚至开发了自己的“SingletonEhCacheRegionFactory”类,使用Spring类“EhCacheManagerFactoryBean”作为缓存管理器进行初始化 . 并且数据继续从数据库中获取 . 这个解决方案适用于我的一个同事,但使用旧版本的Spring(<4)和Hibernate(<4) . 所以,对于新版本,它似乎不是一个好的解决方案......

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

在给出我的配置之前,这里是我对主类的快速测试,以从数据库然后从缓存中检索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配置:

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仍然执行以下数据库请求并检索数据库值 . 真奇怪!

选择this_.ChannelId作为ChannelI1_3_0_,this_.Code作为Code2_3_0_,this_.Label作为Label3_3_0_来自Channel this_,其中this_.Code =?

有人对这种奇怪的行为有所了解吗?

非常感谢你的帮助!

2 回答

  • 3

    第二级和查询缓存有一些看起来像奇怪的行为,但它实际上是正常的 .

    例如,实体上的 @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);
    
  • 0

    那真是很酷的jhadesdev!

    我为我的实体,FetchType.EAGER属性和我的查询定义了EHCache缓存 .

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

    一切正常!!

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

相关问题