当前位置: 首页 > 知识库问答 >
问题:

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

何晗昱
2023-03-14

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

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

唯一可行的解决方案是使用@Cacheable-Spring注释(来自Spring上下文支持),但我并不满意,因为我无法在实体上使用Hibernate@Cache注释@Cacheable只能用于服务方法之类的方法,我检索实体时不使用方法。。。

例如:

我调用以下获取集合实体的服务

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

集合实体包含一个编程实体集

@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);

这些广播节目包含一个通道实体(参考数据)

@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 {

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

真正的解决方案是在ChannelEntity上放置一个工作的Hibernate@Cache注释。

为了实现它的功能,我甚至开发了自己的“SingletonEhCache区域工厂”类初始化,使用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配置:

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">

我使用Spring4.0。3.发布,Hibernate4.3。4.最终版本和EHCache 2.6。8.

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

选择此选项。ChannelId为ChannelI1\u 3\u 0\u,这是。代码为代码2\u 3\u 0\u,这是。标签为Label3_3_0_,来自此频道,此处为。代码=?

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

非常感谢你的帮助!

共有2个答案

宗意蕴
2023-03-14

这真的很酷jhadesdev!

我为我的实体定义了EHCache缓存,它们的FetchType。急切的属性和我的查询。

然后,我在实体及其FetchType上添加了@Cache注释。渴望的属性。我还在服务上添加了“cacheable”属性,允许检索定义为FetchType的实体属性的值。懒惰和对别人的质疑。

一切正常!!

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

微生自怡
2023-03-14

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

例如,实体上的@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);
 类似资料:
  • 我正在为maven多模块项目研究缓存实现(exstremescale),我在下面添加了maven依赖项 添加了缓存注释 cache-manager.xml 缓存只适用于项目的一个maven模块,我可以看到缓存拦截器调用,对于maven模块的其余部分,它忽略了@cacheable注释(它不会进入拦截器)。 我们没有PostConstructor或自我调用 我们使用atomikos作为事务管理器和CX

  • 下面是我用来缓冲和转换传入事件的代码: 这里的问题是,每秒返回一个空列表,尽管没有发布到的事件。 是否有一种方法来保持直到至少有一个对象? 注意:看起来有办法在C#中做到这一点(这里描述:https://stackoverflow.com/a/30090185/668148)。但是Java怎么做呢?

  • 问题内容: 我正在开发一个使用Spring-boot,关系数据库和Elasticsearch的应用程序。 我在代码的2个不同位置使用JSON序列化: 在REST API的响应中。 当代码与Elasticsearch交互时。 我在Elasticsearch中需要一些属性,但我想向应用程序用户隐藏(例如,来自关系数据库的内部ID)。 这是一个实体的例子: 问题 :当对象持久化在Elasticsearc

  • 有什么办法能做到这一点吗? 谢谢!

  • 我使用"mvn test"来运行cucumber测试,但是当我尝试用-Dcucumber.options=...在命令行上传递选项时,这些选项将被忽略,并使用运行类中@CucumberOptions中指定的选项。例如,如果我只是试图显示cucumber帮助,它会忽略它,只是运行测试: 这是我的跑步班 这是我的pom.xml. 我尝试了surefire插件的3.0.0-M5版本,而不是2.22.2版

  • 我正在我的一个应用程序中使用新的android导航框架。应用程序的目的是充当启动器。 有时,当我尝试更改片段(使用navcontroller导航)时,它不会更改片段,而是记录下来 我知道之前有人问过这个问题,忽略navigate()调用:FragmentManager已经保存了它的状态,但没有解决方案。 我使用以下代码导航: Navigation.findNavController(视图)。id.