Q:

Can't write readonly object after hibernate upgrade

I'm upgrading to Hibernate 4.3.8 and I'm running into a strange issue in unit tests.
I have a User class that reference a Language 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{

}

So, after upgrading, I'm starting to see this exception as the user class is loaded like this:
    User user = (User) getSessionFactory().getCurrentSession()
    .bySimpleNaturalId(User.class)
    .load(email);

Exception:
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.access$2000(SessionImpl.java:176)
    at org.hibernate.internal.SessionImpl$IdentifierLoadAccessImpl.load(SessionImpl.java:2551)
    at org.hibernate.internal.SessionImpl$SimpleNaturalIdLoadAccessImpl.load(SessionImpl.java:2794)

Any suggestions here?

A:

So, this seems to occur because somewhere along the way, when it comes to caching, Hibernate started treating objects that are created in the same transactions differently from objects that are created in different transactions.
So, if you have a unit test where you do some setup within a transaction creating readonly objects, even though you are only writing to them once, they will throw an exception if they are created in the same transaction as the test.
The solution is to have your one time readonly objects created in a central method using a transactionmanager with Propagation.REQUIRES_NEW. You cannot use annotations for this, BTW

