我启用了 L2 和查询缓存,当我有一个已缓存的查询时,我遇到了一个奇怪的问题。实体中的所有关系都是惰性初始化的。这是我正在查询的实体的示例:
@Entity
@Cache(usage = READ_WRITE)
@Data
@NoArgsConstructor
@Accessors
@EqualsAndHashCode(of = "id", callSuper = false)
public class TestEntity {
/** The Constant serialVersionUID. */
private static final long serialVersionUID = 1L;
@Id
@Column(updatable = false)
private Long id;
@OneToOne(cascade = ALL, fetch = LAZY)
private AnotherTestEntity anotherTestEntity;
}
@Entity
@Cache(usage = READ_WRITE)
@Data
@NoArgsConstructor
@Accessors
@EqualsAndHashCode(of = "id", callSuper = false)
public class AnotherTestEntity {
/** The Constant serialVersionUID. */
private static final long serialVersionUID = 1L;
@Id
@Column(updatable = false)
private Long id;
@Column
private String property;
}
当我执行未缓存的查询时:
@Transactional(readOnly = true)
public TestEntity findTestEntity() {
TestEntity testEntity = testEntityRepository.findOne(1);
testEntity.getAnotherTestEntity().getProperty();
return testEntity;
}
我第一次调用此方法时,它会查询数据库并将实体添加到二级缓存中。我第二次调用它时,它从 L2 缓存加载实体,并且仍然工作正常。
当我调用缓存的查询时,问题就出现了。这是一个例子:
@Repository
public interface TestEntityRepository {
@Cachable(cacheNames = "testQuery")
TestEntity findOne(Long id);
}
我会用同样的方法:
@Transactional(readOnly = true)
public TestEntity findTestEntity() {
TestEntity testEntity = testEntityRepository.findOne(1);
testEntity.getAnotherTestEntity().getProperty();
return testEntity;
}
当我第一次调用它时,它仍然可以正常工作 - 从数据库加载数据。当第二次调用使用查询缓存时,问题就出现了。当我访问惰性初始化关系时抛出此异常:
Caused by: org.hibernate.LazyInitializationException: could not initialize proxy - no Session
我可以看到延迟初始化实体的会话为空,但我不明白为什么会发生这种情况。正如我们所知,查询缓存仅包含与该查询关联的实体的 id,然后它会从 L2 检索它们(参考:https://dzone.com/articles/pitfalls-hibernate-second-0)。所以我不明白为什么第一个例子(没有查询缓存)工作正常,而第二个例子却表现得如此奇怪。有人可以解释并告诉我我做错了什么吗?
所以我只是深入研究这个问题,结果发现 Spring 缓存抽象不能与 hibernate 延迟加载代理一起使用。 Spring 为您提供了一个抽象,而他们不了解 hibernate 和 hazelcast。然后 hazelcast 提供了与 spring 一起使用的实现。因此,当调用带有 @Cachable 注释的方法时,Spring 方面会检查缓存(使用提供的 CacheManager - 在本例中为 HazelcastCacheManager)并检索缓存中的内容。这里的问题是 hibernate 代理中的会话是瞬态的(这种情况绝对正常),因此我们从缓存中检索一个没有 hibernate 会话的实体,并且由于 spring 不想与 hibernate 耦合,因此会话是未设置。然后我们收到 LazyInitializationException。但归根结底,这是一个非常常见的问题,奇怪的是到了春天还没有解决方案。有效的方法是使用 hibernate 查询缓存,但使用 hazelcast 还存在其他一些缺点。
这可能是一个错误,但可以肯定的是,您需要使用此测试用例模板将其复制到最新的 Hibernate ORM。
如果无法重现,则意味着问题已修复,需要升级 Hibernate,或者问题源于 Spring,而不是 Hibernate。
根据我的说法,最初您正在发送一个 get 请求,并且缓存正在存储数据,以便它可以在第二次使用相同的 Id 调用 get 方法时直接检索数据。 现在发生的情况是,您有一个延迟加载的实体,在您第一次发出 get 请求时,其数据未由缓存存储,现在第二次缓存检索延迟加载的实体数据,但因为不存在数据且没有活动会话是否从数据库中获取该实体,它抛出一个 LazyInitialization 异常。
如何解决: 1->使用fetch=FetchType.EAGER加载 2-> 不要发送这个延迟加载的实体作为响应 3-> 使用 Dto 对象并返回该对象而不是您的实体,您可以轻松地在 Web 中了解它,使用 dto 避免了很多异常。