簡體   English   中英

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

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

幾天以來,我一直在使用@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放在其上以僅緩存此數據。 這是個玩笑...我不會為我所有的參照數據類都這么做...

真正的解決方案是在ChannelEntity上放置一個有效的Hibernate @Cache注釋。

為了使它發揮功能,我什至開發了自己的“ 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.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在第二次調用Hibernate之后將ChannelEntity放在了緩存上。 ChannelService獲取緩存並檢索通道實體! 但是,Hibernate仍將執行以下數據庫請求並檢索數據庫值。 真奇怪!

從通道this_中選擇this_.ChannelId作為ChannelI1_3_0_,this_.Code作為Code2_3_0_,this_.Label作為Label3_3_0_,其中this_.Code =?

有人對這種奇怪的行為有想法嗎?

非常感謝您的幫助!

第二級和查詢緩存有一些陷阱,看起來很奇怪,但實際上是正常的。

例如,實體上的@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真是太酷了!

我為實體,它們的FetchType.EAGER屬性和查詢定義了EHCache緩存。

然后,我在實體及其FetchType.EAGER屬性上添加了@Cache注釋。 我還在服務上添加了“可緩存”屬性,以允許檢索定義為FetchType.LAZY的實體屬性的值以及其他查詢。

一切正常!

非常感謝您給我缺少實現緩存的概念!!! :-)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM