休眠升级后无法写入只读对象

Can't write readonly object after hibernate upgrade

我正在升级到 Hibernate 4.3.8,我 运行 在单元测试中遇到了一个奇怪的问题。

我有一个用户 class 引用了一种语言 class

public class User{
   @ManyToOne(cascade=CascadeType.MERGE,optional = true, fetch=FetchType.EAGER)
   @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
   @Fetch(FetchMode.SELECT)
   private Language language;

   @Column
   @NaturalId(mutable=false)
   private String email;

}

@Entity
@Immutable
@Cache(usage=CacheConcurrencyStrategy.READ_ONLY)
@NaturalIdCache(region=CacheRegion.NATURAL)
public class Language{

}

所以,升级后,我开始看到这个异常,因为用户 class 是这样加载的:

    User user = (User) getSessionFactory().getCurrentSession()
    .bySimpleNaturalId(User.class)
    .load(email);

异常:

java.lang.UnsupportedOperationException: Can't write to a readonly object
    at org.hibernate.cache.ehcache.internal.strategy.ReadOnlyEhcacheEntityRegionAccessStrategy.update(ReadOnlyEhcacheEntityRegionAccessStrategy.java:115)
    at org.hibernate.cache.ehcache.internal.nonstop.NonstopAwareEntityRegionAccessStrategy.update(NonstopAwareEntityRegionAccessStrategy.java:216)
    at org.hibernate.engine.internal.TwoPhaseLoad.doInitializeEntity(TwoPhaseLoad.java:211)
    at org.hibernate.engine.internal.TwoPhaseLoad.initializeEntity(TwoPhaseLoad.java:144)
    at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.performTwoPhaseLoad(AbstractRowReader.java:244)
    at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.finishUp(AbstractRowReader.java:215)
    at org.hibernate.loader.plan.exec.process.internal.ResultSetProcessorImpl.extractResults(ResultSetProcessorImpl.java:140)
    at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:138)
    at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:102)
    at org.hibernate.loader.entity.plan.AbstractLoadPlanBasedEntityLoader.load(AbstractLoadPlanBasedEntityLoader.java:186)
    at org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:4126)
    at org.hibernate.event.internal.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:503)
    at org.hibernate.event.internal.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:468)
    at org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:213)
    at org.hibernate.event.internal.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:275)
    at org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:151)
    at org.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java:1070)
    at org.hibernate.internal.SessionImpl.internalLoad(SessionImpl.java:989)
    at org.hibernate.type.EntityType.resolveIdentifier(EntityType.java:716)
    at org.hibernate.type.EntityType.resolve(EntityType.java:502)
    at org.hibernate.engine.internal.TwoPhaseLoad.doInitializeEntity(TwoPhaseLoad.java:170)
    at org.hibernate.engine.internal.TwoPhaseLoad.initializeEntity(TwoPhaseLoad.java:144)
    at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.performTwoPhaseLoad(AbstractRowReader.java:244)
    at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.finishUp(AbstractRowReader.java:215)
    at org.hibernate.loader.plan.exec.process.internal.ResultSetProcessorImpl.extractResults(ResultSetProcessorImpl.java:140)
    at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:138)
    at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:102)
    at org.hibernate.loader.entity.plan.AbstractLoadPlanBasedEntityLoader.load(AbstractLoadPlanBasedEntityLoader.java:186)
    at org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:4126)
    at org.hibernate.event.internal.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:503)
    at org.hibernate.event.internal.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:468)
    at org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:213)
    at org.hibernate.event.internal.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:275)
    at org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:151)
    at org.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java:1070)
    at org.hibernate.internal.SessionImpl.access00(SessionImpl.java:176)
    at org.hibernate.internal.SessionImpl$IdentifierLoadAccessImpl.load(SessionImpl.java:2551)
    at org.hibernate.internal.SessionImpl$SimpleNaturalIdLoadAccessImpl.load(SessionImpl.java:2794)

这里有什么建议吗?

所以,这似乎发生了,因为在此过程中的某个地方,当涉及到缓存时,Hibernate 开始以不同的方式处理在相同事务中创建的对象和在不同事务中创建的对象。

因此,如果您有一个单元测试,其中您在创建只读对象的事务中进行一些设置,即使您只写入一次它们,如果它们是在与测试。

解决方案是使用具有 Propagation.REQUIRES_NEW 的事务管理器在中央方法中创建一次性只读对象。你不能为此使用注释,顺便说一句