简体   繁体   English

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

[英]Cache ignored using Spring 4 @Transactional + Hibernate 4 + EHCache

I'm blocked since a few days with Hibernate caching in a Spring context using @Transactional annotations. 几天以来,我一直在使用@Transactional批注在Spring上下文中使用Hibernate缓存来阻止我。

I tried all the solutions found on the web without success... 我尝试了所有在网络上找到的解决方案,但都没有成功...

The single solution that work is to use the @Cacheable Spring annotation (from spring-context-support), but I'm not satisfied because I cannot use the Hibernate @Cache annotation on my entities. 唯一可行的解​​决方案是使用@Cacheable Spring注释(来自spring-context-support),但我不满意,因为我无法在实体上使用Hibernate @Cache注释。 @Cacheable can just be used on methods like services methods, and I retrieve entities without methods... @Cacheable只能用于服务方法之类的方法,而我检索没有方法的实体...

For exemple: 举个例子:

I call the following service that fetch a CollectionEntity 我调用以下获取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);
}

A CollectionEntity contains a ProgramEntity set 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);

These programs contain a ProgramBroadcasting set 这些程序包含一个ProgramBroadcasting集

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

And these programs broadcasting contain a ChannelEntity (a referential data) 并且这些广播的节目包含ChannelEntity(参考数据)

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

So if I want to cache the ChannelEntity, I normally just need to put the following annotation on its class. 因此,如果要缓存ChannelEntity,通常只需要在其类上放置以下注释。

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

The "EAGER" fetching is a very attractive solution for referential data! “ EAGER”提取对于参考数据而言是非常有吸引力的解决方案! But if a want to use @Cacheable, which is at the moment the only solution, I must declare the ChannelEntity with FetchType.LAZY and write a service class to put the @Cacheable on it just to cache this data. 但是,如果要使用@Cacheable(这是目前唯一的解决方案),则必须使用FetchType.LAZY声明ChannelEntity并编写一个服务类以将@Cacheable放在其上以仅缓存此数据。 It's a joke... I won't do it for all my referential data classes... 这是个玩笑...我不会为我所有的参照数据类都这么做...

A real solution is just to have a working Hibernate @Cache annotation to put on the ChannelEntity. 真正的解决方案是在ChannelEntity上放置一个有效的Hibernate @Cache注释。

To get it functional, I even have developed my own "SingletonEhCacheRegionFactory" class initialize with the Spring class "EhCacheManagerFactoryBean" as cache manager. 为了使它发挥功能,我什至开发了自己的“ SingletonEhCacheRegionFactory”类,并使用Spring类“ EhCacheManagerFactoryBean”作为缓存管理器进行了初始化。 And the data continue to be fetched from database. 并且数据继续从数据库中获取。 This solution worked for one of my colleagues, but with older versions of Spring (<4) and Hibernate (<4). 该解决方案适用于我的一位同事,但使用的是Spring(<4)和Hibernate(<4)的旧版本。 So, with the new versions, it doesn't seem to be a good solution... 因此,对于新版本,这似乎不是一个好的解决方案...

So, I really need your help. 所以,我真的需要您的帮助。

Before to give you my configuration, here are my quick tests on a main class to retrieve a ChannelEntity from database and then from cache. 在给您配置之前,这是我在主类上进行的快速测试,以从数据库中然后从缓存中检索ChannelEntity。

Here the test is OK (it work): 此处的测试正常(可以工作):

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();
}

But it's not the real context. 但这不是真正的背景。 On my service layer, I don't manipulate directly the transaction, I used the @Transactional Spring annotation. 在服务层上,我不直接操作事务,而是使用@Transactional Spring批注。 Here is a more realistic test: 这是一个更现实的测试:

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...
    }
}

Here is the ChannelService: 这是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);
    }
}

Now, my configuration... 现在,我的配置...

Dependencies: 依存关系:

<!-- 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 configuration: 休眠配置:

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 configuration: EHCache配置:

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

And I use Spring 4.0.3.RELEASE, Hibernate 4.3.4.Final and EHCache 2.6.8. 我使用的是Spring 4.0.3.RELEASE,Hibernate 4.3.4.Final和EHCache 2.6.8。

To be more accurate, it seems that the @Cache Hibernate annotation work, but not completely... In fact, I put several breakpoints on the Hibernate source code and I noted that Hibernate put the ChannelEntity on the cache and after my second call to the ChannelService do a get on the cache and retrieve the channel entity! 更准确地说,似乎@Cache Hibernate批注可以正常工作,但不能完全...实际上,我在Hibernate源代码上放置了几个断点,并且我注意到Hibernate在第二次调用Hibernate之后将ChannelEntity放在了缓存上。 ChannelService获取缓存并检索通道实体! BUT..., Hibernate still execute the following database request and retrieve the database value. 但是,Hibernate仍将执行以下数据库请求并检索数据库值。 Really strange! 真奇怪!

select this_.ChannelId as ChannelI1_3_0_, this_.Code as Code2_3_0_, this_.Label as Label3_3_0_ from Channel this_ where this_.Code=? 从通道this_中选择this_.ChannelId作为ChannelI1_3_0_,this_.Code作为Code2_3_0_,this_.Label作为Label3_3_0_,其中this_.Code =?

Anyone has an idea about this strange behaviour?? 有人对这种奇怪的行为有想法吗?

Thanks a lot for you help! 非常感谢您的帮助!

The second level and query caches have a few gotchas that look like strange behaviour but it's actually normal. 第二级和查询缓存有一些陷阱,看起来很奇怪,但实际上是正常的。

For example @Cache on entities will only cache entities loaded by their Id. 例如,实体上的@Cache将仅缓存由其ID加载的实体。 If you want to cache the result of queries other than load by Id, you need to mark the query as cacheable: 如果要缓存除按ID加载以外的查询结果,则需要将查询标记为可缓存:

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

or in the case of criteria queries: 或就条件查询而言:

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

Also one to many relations by default are not cached. 默认情况下,也不缓存一对多关系。 If you want to cache associations, you need to mark them independently with @Cache . 如果要缓存关联,则需要使用@Cache独立标记它们。 For example: 例如:

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

That's really cool jhadesdev! jhadesdev真是太酷了!

I defined EHCache caches for my entities, their FetchType.EAGER attributes and for my queries. 我为实体,它们的FetchType.EAGER属性和查询定义了EHCache缓存。

Then, I added @Cache annotation on the entities and on their FetchType.EAGER attributes. 然后,我在实体及其FetchType.EAGER属性上添加了@Cache注释。 I also added the "cacheable" attribute on the services allowing to retrieve the values of my entities attributes defined as FetchType.LAZY and on my others queries. 我还在服务上添加了“可缓存”属性,以允许检索定义为FetchType.LAZY的实体属性的值以及其他查询。

Everything works fine!! 一切正常!

A big thank you to give me the missing concept to implement the cache!!! 非常感谢您给我缺少实现缓存的概念!!! :-) :-)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM