由于Hibernate在Spring上下文中使用@Transactional注释进行缓存,我被阻止了几天。
我尝试了网上找到的所有解决方案,但都没有成功。。。
唯一有效的解决方案是使用@CacheableSpring注释(来自Spring上下文支持),但我不满意,因为我不能在实体上使用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);
这些程序包含一个程序广播集
@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();
}
但这不是真实的背景。在我的服务层上,我不直接操作事务,我使用了@TransactionalSpring注释。这里有一个更现实的测试:
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...
}
}
这是频道服务:
@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后,在缓存中执行get并检索通道实体!但是。。。,Hibernate仍然执行以下数据库请求并检索数据库值。真奇怪!
从通道this_where this_Code=?
有人知道这种奇怪的行为吗??
非常感谢你的帮助!
第二级和查询缓存有一些gotchas,看起来很奇怪,但实际上很正常。
例如,实体上的@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注释。我还在服务上添加了"cacheable"属性,允许检索定义为FetchType.LAZY的实体属性和其他查询的值。
一切都很好!!
非常感谢您给我实现缓存所缺少的概念!!!:-)