mirror of https://github.com/apache/openjpa.git
OPENJPA-610 Test cases to identify use cases where behavior differs because of DataCache
git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@659669 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
93e15d846c
commit
534778acf8
|
@ -0,0 +1,89 @@
|
|||
package org.apache.openjpa.persistence.cache.common.apps;
|
||||
|
||||
import javax.persistence.*;
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A persistent entity that is owned by unidirectional single-valued
|
||||
* relationship.
|
||||
* A unidirectional relationship has only one owning side and the other side
|
||||
* called as owned side is this receiver.
|
||||
* Given the following relationship between Entity A and Entity B:
|
||||
* Entity A refers a single instance of Entity B
|
||||
* Entity B does not refer Entity A (owner)
|
||||
* Entity A is called owner and Entity B is called owned with respect
|
||||
* to the above relationship.
|
||||
*
|
||||
* Used to test identical application behavior with or without DataCache.
|
||||
*
|
||||
* @see BidirectionalOne2OneOwned
|
||||
* @see TestDataCacheBehavesIdentical
|
||||
* @see Section 2.1.8.3 of JPA Specification Version 1.0
|
||||
*
|
||||
* @author Pinaki Poddar
|
||||
*
|
||||
*/
|
||||
|
||||
@Entity
|
||||
public class BidirectionalOne2OneOwned {
|
||||
@Id
|
||||
private long id;
|
||||
|
||||
private String name;
|
||||
|
||||
@OneToOne(mappedBy="owned")
|
||||
private BidirectionalOne2OneOwner owner;
|
||||
|
||||
@Version
|
||||
private int version;
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public BidirectionalOne2OneOwner getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
public void setOwner(BidirectionalOne2OneOwner owner) {
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return this.getClass().getSimpleName() + ":" + id + ":" + name;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.apache.openjpa.persistence.cache.common.apps;
|
||||
|
||||
import javax.persistence.*;
|
||||
|
||||
/**
|
||||
* A persistent entity that owns bidirectional single-valued relationship.
|
||||
* A bidirectional relationship has only an owning side, which is this receiver.
|
||||
* Given the following relationship between Entity A and Entity B:
|
||||
* Entity A refers to a single instance of Entity B
|
||||
* Entity B refers to a single instance of Entity A
|
||||
* If Entity B qualifies its relation to the Entity A with mappedBy
|
||||
* annotation qualifier then Entity B is called owned and Entity A is called
|
||||
* owner with respect to the above relationship.
|
||||
*
|
||||
* Used to test identical application behavior with or without DataCache.
|
||||
*
|
||||
* @see BidirectionalOne2OneOwned
|
||||
*
|
||||
* @author Pinaki Poddar
|
||||
*
|
||||
*/
|
||||
@Entity
|
||||
public class BidirectionalOne2OneOwner {
|
||||
@Id
|
||||
private long id;
|
||||
|
||||
private String name;
|
||||
|
||||
@OneToOne
|
||||
private BidirectionalOne2OneOwned owned;
|
||||
|
||||
@Version
|
||||
private int version;
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public BidirectionalOne2OneOwned getOwned() {
|
||||
return owned;
|
||||
}
|
||||
|
||||
public void setOwned(BidirectionalOne2OneOwned owned) {
|
||||
this.owned = owned;
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return this.getClass().getSimpleName() + ":" + id + ":" + name;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.apache.openjpa.persistence.cache.common.apps;
|
||||
|
||||
import javax.persistence.*;
|
||||
/**
|
||||
* A persistent entity that is owned by unidirectional single-valued
|
||||
* relationship.
|
||||
* A unidirectional relationship has only one owning side and the other side
|
||||
* called as owned side is this receiver.
|
||||
* Given the following relationship between Entity A and Entity B:
|
||||
* Entity A refers a single instance of Entity B
|
||||
* Entity B does not refer Entity A (owner)
|
||||
* Entity A is called owner and Entity B is called owned with respect
|
||||
* to the above relationship.
|
||||
*
|
||||
* Used to test identical application behavior with or without DataCache.
|
||||
*
|
||||
* @see UnidirectionalOne2OneOwned
|
||||
* @see TestDataCacheBehavesIdentical
|
||||
* @see Section 2.1.8.3 of JPA Specification Version 1.0
|
||||
*
|
||||
* @author Pinaki Poddar
|
||||
*
|
||||
*/
|
||||
|
||||
@Entity
|
||||
public class UnidirectionalOne2OneOwned {
|
||||
@Id
|
||||
private long id;
|
||||
|
||||
private String name;
|
||||
|
||||
@Version
|
||||
private int version;
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return this.getClass().getSimpleName() + ":" + id + ":" + name;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.apache.openjpa.persistence.cache.common.apps;
|
||||
|
||||
import javax.persistence.*;
|
||||
|
||||
/**
|
||||
* A persistent entity that owns unidirectional single-valued relationship.
|
||||
* A unidirectional relationship has only one owning side, which is this
|
||||
* receiver.
|
||||
* Given the following relationship between Entity A and Entity B:
|
||||
* Entity A refers a single instance of Entity B
|
||||
* Entity B does not refer Entity A (owner)
|
||||
* Entity A is called owner and Entity B is called owned with respect
|
||||
* to the above relationship.
|
||||
*
|
||||
* Used to test identical application behavior with or without DataCache.
|
||||
*
|
||||
* @see UnidirectionalOne2OneOwned
|
||||
* @see TestDataCacheBehavesIdentical
|
||||
* @see Section 2.1.8.3 of JPA Specification Version 1.0
|
||||
*
|
||||
* @author Pinaki Poddar
|
||||
*
|
||||
*/
|
||||
@Entity
|
||||
public class UnidirectionalOne2OneOwner {
|
||||
@Id
|
||||
private long id;
|
||||
|
||||
private String name;
|
||||
|
||||
@OneToOne
|
||||
private UnidirectionalOne2OneOwned owned;
|
||||
|
||||
@Version
|
||||
private int version;
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public UnidirectionalOne2OneOwned getOwned() {
|
||||
return owned;
|
||||
}
|
||||
|
||||
public void setOwned(UnidirectionalOne2OneOwned owned) {
|
||||
this.owned = owned;
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return this.getClass().getSimpleName() + ":" + id + ":" + name;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,444 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.openjpa.persistence.datacache;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.EntityNotFoundException;
|
||||
import javax.persistence.LockModeType;
|
||||
|
||||
import org.apache.openjpa.persistence.EntityManagerImpl;
|
||||
import org.apache.openjpa.persistence.OpenJPAEntityManagerFactorySPI;
|
||||
import org.apache.openjpa.persistence.OpenJPAEntityManagerSPI;
|
||||
import org.apache.openjpa.persistence.StoreCache;
|
||||
import org.apache.openjpa.persistence.StoreCacheImpl;
|
||||
import org.apache.openjpa.persistence.cache.common.apps.BidirectionalOne2OneOwned;
|
||||
import org.apache.openjpa.persistence.cache.common.apps.BidirectionalOne2OneOwner;
|
||||
import org.apache.openjpa.persistence.common.utils.AbstractTestCase;
|
||||
import org.apache.openjpa.persistence.exception.PObject;
|
||||
|
||||
/**
|
||||
* Tests various application behavior with or without DataCache.
|
||||
* Ideally, an application should behave identically irrespective of the
|
||||
* DataCache. However, purpose of this test is to identify specific scenarios
|
||||
* where this ideal is violated. The test case also demonstrates, wherever
|
||||
* possible, what extra step an application may take to ensure that its
|
||||
* behavior with or without DataCache remains identical.
|
||||
*
|
||||
* So far following use cases are found to demonstrate behavioral differences:
|
||||
* 1. Inconsistent bidirectional relation
|
||||
* 2. Refresh
|
||||
*
|
||||
* @author Pinaki Poddar
|
||||
*
|
||||
*/
|
||||
public class TestDataCacheBehavesIdentical extends AbstractTestCase {
|
||||
private static OpenJPAEntityManagerFactorySPI emfWithDataCache;
|
||||
private static OpenJPAEntityManagerFactorySPI emfWithoutDataCache;
|
||||
|
||||
private static final boolean WITH_DATACACHE = true;
|
||||
private static final boolean CONSISTENT = true;
|
||||
private static final boolean DIRTY = true;
|
||||
private static final LockModeType NOLOCK = null;
|
||||
|
||||
private static final String MARKER_DATACACHE = "in DataCache";
|
||||
private static final String MARKER_DATABASE = "in Database";
|
||||
private static final String MARKER_CACHE = "in Object Cache";
|
||||
private static final String MARKER_DIRTY_CACHE = "in Object Cache (dirty)";
|
||||
private static long ID_COUNTER = System.currentTimeMillis();
|
||||
|
||||
|
||||
/**
|
||||
* Sets up two EntityManagerFactory: one with DataCache another without.
|
||||
*/
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
if (emfWithDataCache == null) {
|
||||
emfWithDataCache = createEMF(
|
||||
"openjpa.jdbc.SynchronizeMappings", "buildSchema",
|
||||
"openjpa.RuntimeUnenhancedClasses", "unsupported",
|
||||
"openjpa.DataCache", "true",
|
||||
"openjpa.RemoteCommitProvider", "sjvm",
|
||||
"openjpa.jdbc.UpdateManager", "constraint",
|
||||
PObject.class,
|
||||
BidirectionalOne2OneOwner.class,
|
||||
BidirectionalOne2OneOwned.class, CLEAR_TABLES);
|
||||
emfWithoutDataCache = createEMF(
|
||||
"openjpa.RuntimeUnenhancedClasses", "unsupported",
|
||||
"openjpa.DataCache", "false",
|
||||
"openjpa.jdbc.UpdateManager", "constraint",
|
||||
PObject.class,
|
||||
BidirectionalOne2OneOwned.class,
|
||||
BidirectionalOne2OneOwner.class, CLEAR_TABLES);
|
||||
|
||||
assertNotNull(emfWithDataCache);
|
||||
assertNotNull(emfWithoutDataCache);
|
||||
|
||||
// StoreCache is, by design, always non-null
|
||||
assertNotNull(emfWithDataCache.getStoreCache());
|
||||
assertNotNull(emfWithoutDataCache.getStoreCache());
|
||||
|
||||
// however, following distinguishes whether DataCache is active
|
||||
assertTrue(isDataCacheActive(emfWithDataCache));
|
||||
assertFalse(isDataCacheActive(emfWithoutDataCache));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Affirms via internal structures if the given factory is configured with
|
||||
* active DataCache. Because, even when DataCache is configured to be
|
||||
* false, a no-op StoreCache is instantiated by design.
|
||||
*/
|
||||
boolean isDataCacheActive(OpenJPAEntityManagerFactorySPI emf) {
|
||||
return ((StoreCacheImpl) emf.getStoreCache()).getDelegate() != null
|
||||
&& emf.getConfiguration()
|
||||
.getDataCacheManagerInstance()
|
||||
.getSystemDataCache() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create one-to-one bidirectional relation (may or may not be consistent)
|
||||
* between two pairs of instances. Creates four instances Owner1, Owned1,
|
||||
* Owner2, Owned2. The first instance has the given id. The id of the other
|
||||
* instances monotonically increase by 1. The relationship is set either
|
||||
* consistently or inconsistently. Consistent relation is when Owner1 points
|
||||
* to Owned1 and Owned1 points back to Owner1. Inconsistent relation is when
|
||||
* Owner1 points to Owned1 but Owned1 points to Owner2 instead of Owner1.
|
||||
*
|
||||
*
|
||||
* @param em
|
||||
* the entity manager to persist the instances
|
||||
* @param id
|
||||
* the identifier of the first owner instance. The identifier for
|
||||
* the other instances are sequential in order of creation.
|
||||
* @param consistent
|
||||
* if true sets the relationship as consistent.
|
||||
*/
|
||||
public void createBidirectionalRelation(EntityManager em, long id,
|
||||
boolean consistent) {
|
||||
BidirectionalOne2OneOwner owner1 = new BidirectionalOne2OneOwner();
|
||||
BidirectionalOne2OneOwned owned1 = new BidirectionalOne2OneOwned();
|
||||
BidirectionalOne2OneOwner owner2 = new BidirectionalOne2OneOwner();
|
||||
BidirectionalOne2OneOwned owned2 = new BidirectionalOne2OneOwned();
|
||||
|
||||
owner1.setId(id++);
|
||||
owned1.setId(id++);
|
||||
owner2.setId(id++);
|
||||
owned2.setId(id++);
|
||||
|
||||
owner1.setName("Owner1");
|
||||
owned1.setName("Owned1");
|
||||
owned2.setName("Owned2");
|
||||
owner2.setName("Owner2");
|
||||
|
||||
owner1.setOwned(owned1);
|
||||
owner2.setOwned(owned2);
|
||||
|
||||
if (consistent) {
|
||||
owned1.setOwner(owner1);
|
||||
owned2.setOwner(owner2);
|
||||
} else {
|
||||
owned1.setOwner(owner2);
|
||||
owned2.setOwner(owner1);
|
||||
}
|
||||
|
||||
em.getTransaction().begin();
|
||||
em.persist(owner1);
|
||||
em.persist(owned1);
|
||||
em.persist(owner2);
|
||||
em.persist(owned2);
|
||||
em.getTransaction().commit();
|
||||
em.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that bidirectionally related objects can be persisted
|
||||
* and later retrieved in a different transaction.
|
||||
*
|
||||
* Creates interrelated set of four instances.
|
||||
* Establish their relation either consistently or inconsistently based
|
||||
* on the given flag.
|
||||
* Persist them and then clear the context.
|
||||
* Fetch the instances in memory again by their identifiers.
|
||||
* Compare the interrelations between the fetched instances with the
|
||||
* relations of the original instances (which can be consistent or
|
||||
* inconsistent).
|
||||
*
|
||||
* The mapping specification is such that the bidirectional relation is
|
||||
* stored in database by a single foreign key. Hence database relation
|
||||
* is always consistent. Hence the instances retrieved from database are
|
||||
* always consistently related irrespective of whether they were created
|
||||
* with consistent or inconsistent relation.
|
||||
* However, when the instances are retrieved from the data cache, data cache
|
||||
* will preserve the in-memory relations even when they are inconsistent.
|
||||
*
|
||||
* @param useDataCache
|
||||
* use DataCache
|
||||
* @param consistent
|
||||
* assume that the relationship were created as consistent.
|
||||
*/
|
||||
public void verifyBidirectionalRelation(boolean useDataCache,
|
||||
boolean createConsistent, boolean expectConsistent) {
|
||||
EntityManager em = (useDataCache)
|
||||
? emfWithDataCache.createEntityManager()
|
||||
: emfWithoutDataCache.createEntityManager();
|
||||
|
||||
long id = ID_COUNTER++;
|
||||
ID_COUNTER += 4;
|
||||
createBidirectionalRelation(em, id, createConsistent);
|
||||
|
||||
|
||||
BidirectionalOne2OneOwner owner1 = em.find(BidirectionalOne2OneOwner.class, id);
|
||||
BidirectionalOne2OneOwned owned1 = em.find(BidirectionalOne2OneOwned.class, id + 1);
|
||||
BidirectionalOne2OneOwner owner2 = em.find(BidirectionalOne2OneOwner.class, id + 2);
|
||||
BidirectionalOne2OneOwned owned2 = em.find(BidirectionalOne2OneOwned.class, id + 3);
|
||||
|
||||
assertNotNull(owner1);
|
||||
assertNotNull(owner2);
|
||||
assertNotNull(owned1);
|
||||
assertNotNull(owned2);
|
||||
|
||||
assertEquals(owner1, expectConsistent
|
||||
? owner1.getOwned().getOwner()
|
||||
: owner2.getOwned().getOwner());
|
||||
assertEquals(owner2, expectConsistent
|
||||
? owner2.getOwned().getOwner()
|
||||
: owner1.getOwned().getOwner());
|
||||
|
||||
|
||||
assertEquals(owned1, owner1.getOwned());
|
||||
assertEquals(expectConsistent ? owner1 : owner2, owned1.getOwner());
|
||||
assertEquals(owned2, owner2.getOwned());
|
||||
assertEquals(expectConsistent ? owner2 : owner1, owned2.getOwner());
|
||||
}
|
||||
|
||||
public void testConsitentBidirectionalRelationIsPreservedWithDataCache() {
|
||||
verifyBidirectionalRelation(WITH_DATACACHE, CONSISTENT, CONSISTENT);
|
||||
}
|
||||
|
||||
public void testConsitentBidirectionalRelationIsPreservedWithoutDataCache() {
|
||||
verifyBidirectionalRelation(!WITH_DATACACHE, CONSISTENT, CONSISTENT);
|
||||
}
|
||||
|
||||
public void testInconsitentBidirectionalRelationIsPreservedWithDataCache() {
|
||||
verifyBidirectionalRelation(WITH_DATACACHE, !CONSISTENT, !CONSISTENT);
|
||||
}
|
||||
|
||||
public void testInconsitentBidirectionalRelationIsNotPreservedWithoutDataCache() {
|
||||
verifyBidirectionalRelation(!WITH_DATACACHE, !CONSISTENT, CONSISTENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that refresh() may fetch state from either the data cache or the
|
||||
* database based on different conditions.
|
||||
* The conditions that impact are
|
||||
* a) whether current lock is stronger than NONE
|
||||
* b) whether the instance being refreshed is dirty
|
||||
*
|
||||
* An instance is created with data cache marker and persisted.
|
||||
* A native SQL is used to update the database record with database marker.
|
||||
* The in-memory instance is not aware of this out-of-band update.
|
||||
* Then the in-memory instance is refreshed. The marker of the refreshed
|
||||
* instance tells whether the instance is refreshed from the data cache
|
||||
* of the database.
|
||||
*
|
||||
* @param useDataCache flags if data cache is active. if not, then surely
|
||||
* refresh always fetch state from the database.
|
||||
*
|
||||
* @param datacache the marker for the copy of the data cached instance
|
||||
* @param database the marker for the database record
|
||||
* @param lock lock to be used
|
||||
*/
|
||||
public void verifyRefresh(boolean useDataCache, LockModeType lock, boolean makeDirtyBeforeRefresh) {
|
||||
OpenJPAEntityManagerFactorySPI emf = (useDataCache)
|
||||
? emfWithDataCache : emfWithoutDataCache;
|
||||
|
||||
OpenJPAEntityManagerSPI em = emf.createEntityManager();
|
||||
|
||||
em.getTransaction().begin();
|
||||
PObject pc = new PObject();
|
||||
pc.setName(useDataCache ? MARKER_DATACACHE : MARKER_CACHE);
|
||||
em.persist(pc);
|
||||
em.getTransaction().commit();
|
||||
|
||||
Object oid = pc.getId();
|
||||
StoreCache dataCache = emf.getStoreCache();
|
||||
assertEquals(useDataCache, dataCache.contains(PObject.class, oid));
|
||||
|
||||
// Modify the record in the database in a separate transaction using
|
||||
// native SQL so that the in-memory instance is not altered
|
||||
em.getTransaction().begin();
|
||||
String sql = "UPDATE POBJECT SET NAME='"+ MARKER_DATABASE +"' WHERE id="+oid;
|
||||
em.createNativeQuery(sql).executeUpdate();
|
||||
em.getTransaction().commit();
|
||||
|
||||
assertEquals(useDataCache ? MARKER_DATACACHE : MARKER_CACHE, pc.getName());
|
||||
|
||||
em.getTransaction().begin();
|
||||
if (makeDirtyBeforeRefresh) {
|
||||
pc.setName(MARKER_DIRTY_CACHE);
|
||||
}
|
||||
assertEquals(makeDirtyBeforeRefresh, em.isDirty(pc));
|
||||
|
||||
if (lock != null) {
|
||||
((EntityManagerImpl)em).getFetchPlan().setReadLockMode(lock);
|
||||
}
|
||||
em.refresh(pc);
|
||||
|
||||
String expected = getExpectedMarker(useDataCache, lock, makeDirtyBeforeRefresh);
|
||||
assertEquals(expected, pc.getName());
|
||||
em.getTransaction().commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* The expected marker i.e. where the state is refreshed from depends on
|
||||
* a) whether DataCache is active
|
||||
* b) whether current Lock is stronger than NOLOCK
|
||||
* c) whether the object to be refreshed is dirty
|
||||
*
|
||||
* The following truth table enumerates the possibilities
|
||||
*
|
||||
* Use Cache? Lock? Dirty? Target
|
||||
* Y Y Y Database
|
||||
* Y N Y Data Cache
|
||||
* Y Y N Data Cache
|
||||
* Y N N Data Cache
|
||||
*
|
||||
* N Y Y Database
|
||||
* N N Y Database
|
||||
* N Y N Object Cache
|
||||
* N N N Object Cache
|
||||
*
|
||||
* @param datacache the marker for
|
||||
* @param database
|
||||
* @param useDataCache
|
||||
* @param lock
|
||||
* @param makeDirtyBeforeRefresh
|
||||
* @return
|
||||
*/
|
||||
String getExpectedMarker(boolean useDataCache, LockModeType lock, boolean makeDirtyBeforeRefresh) {
|
||||
if (useDataCache) {
|
||||
return (lock != null && makeDirtyBeforeRefresh)
|
||||
? MARKER_DATABASE : MARKER_DATACACHE;
|
||||
} else {
|
||||
return (makeDirtyBeforeRefresh) ? MARKER_DATABASE : MARKER_CACHE;
|
||||
}
|
||||
}
|
||||
|
||||
public void testDirtyRefreshWithNoLockHitsDataCache() {
|
||||
verifyRefresh(WITH_DATACACHE, NOLOCK, DIRTY);
|
||||
}
|
||||
|
||||
public void testCleanRefreshWithNoLockHitsDataCache() {
|
||||
verifyRefresh(WITH_DATACACHE, NOLOCK, !DIRTY);
|
||||
}
|
||||
|
||||
public void testDirtyRefreshWithReadLockHitsDatabase() {
|
||||
verifyRefresh(WITH_DATACACHE, LockModeType.READ, DIRTY);
|
||||
}
|
||||
|
||||
public void testCleanRefreshWithReadLockHitsDataCache() {
|
||||
verifyRefresh(WITH_DATACACHE, LockModeType.READ, !DIRTY);
|
||||
}
|
||||
|
||||
public void testDirtyRefreshWithWriteLockHitsDatabase() {
|
||||
verifyRefresh(WITH_DATACACHE, LockModeType.WRITE, DIRTY);
|
||||
}
|
||||
|
||||
public void testCleanRefreshWithWriteLockHitsDatabase() {
|
||||
verifyRefresh(WITH_DATACACHE, LockModeType.WRITE, !DIRTY);
|
||||
}
|
||||
|
||||
public void testDirtyRefreshWithoutDataCacheAlwaysHitsDatabase() {
|
||||
verifyRefresh(!WITH_DATACACHE, NOLOCK, DIRTY);
|
||||
verifyRefresh(!WITH_DATACACHE, LockModeType.READ, DIRTY);
|
||||
verifyRefresh(!WITH_DATACACHE, LockModeType.WRITE, DIRTY);
|
||||
}
|
||||
|
||||
public void testCleanRefreshWithoutDataCacheNeverHitsDatabase() {
|
||||
verifyRefresh(!WITH_DATACACHE, NOLOCK, !DIRTY);
|
||||
verifyRefresh(!WITH_DATACACHE, LockModeType.READ, !DIRTY);
|
||||
verifyRefresh(!WITH_DATACACHE, LockModeType.WRITE, !DIRTY);
|
||||
}
|
||||
|
||||
|
||||
public void verifyDeleteDetectionOnRefresh(boolean useDataCache, LockModeType lock) {
|
||||
OpenJPAEntityManagerFactorySPI emf = (useDataCache)
|
||||
? emfWithDataCache : emfWithoutDataCache;
|
||||
|
||||
OpenJPAEntityManagerSPI em = emf.createEntityManager();
|
||||
|
||||
em.getTransaction().begin();
|
||||
PObject pc = new PObject();
|
||||
pc.setName(useDataCache ? MARKER_DATACACHE : MARKER_CACHE);
|
||||
em.persist(pc);
|
||||
em.getTransaction().commit();
|
||||
|
||||
Object oid = pc.getId();
|
||||
StoreCache dataCache = emf.getStoreCache();
|
||||
assertEquals(useDataCache, dataCache.contains(PObject.class, oid));
|
||||
|
||||
// delete the record in the database in a separate transaction using
|
||||
// native SQL so that the in-memory instance is not altered
|
||||
em.getTransaction().begin();
|
||||
String sql = "DELETE FROM POBJECT WHERE id="+oid;
|
||||
em.createNativeQuery(sql).executeUpdate();
|
||||
em.getTransaction().commit();
|
||||
|
||||
// the object cache does not know that the record was deleted
|
||||
assertTrue(em.contains(pc));
|
||||
// nor does the data cache
|
||||
assertEquals(useDataCache, dataCache.contains(PObject.class, oid));
|
||||
|
||||
assertEquals(useDataCache ? MARKER_DATACACHE : MARKER_CACHE, pc.getName());
|
||||
|
||||
em.getTransaction().begin();
|
||||
if (lock != null)
|
||||
em.getFetchPlan().setReadLockMode(lock);
|
||||
try {
|
||||
em.refresh(pc);
|
||||
if (lock == null) {
|
||||
assertEquals(useDataCache ? MARKER_DATACACHE : MARKER_CACHE, pc.getName());
|
||||
} else {
|
||||
fail("expected EntityNotFoundException for PObject:" + oid);
|
||||
}
|
||||
} catch (EntityNotFoundException ex) {
|
||||
if (lock != null) {
|
||||
// we are good
|
||||
}
|
||||
} finally {
|
||||
em.getTransaction().rollback();
|
||||
}
|
||||
}
|
||||
|
||||
public void testDeleteIsDetectedOnRefreshWithLockWithActiveDataCache() {
|
||||
verifyDeleteDetectionOnRefresh(WITH_DATACACHE, LockModeType.READ);
|
||||
verifyDeleteDetectionOnRefresh(WITH_DATACACHE, LockModeType.WRITE);
|
||||
}
|
||||
|
||||
public void testDeleteIsNotDetectedOnRefreshWithNoLockWithActiveDataCache() {
|
||||
verifyDeleteDetectionOnRefresh(WITH_DATACACHE, NOLOCK);
|
||||
}
|
||||
|
||||
public void testDeleteIsDetectedOnRefreshAlwaysWithoutDataCache() {
|
||||
verifyDeleteDetectionOnRefresh(!WITH_DATACACHE, NOLOCK);
|
||||
verifyDeleteDetectionOnRefresh(!WITH_DATACACHE, LockModeType.READ);
|
||||
verifyDeleteDetectionOnRefresh(!WITH_DATACACHE, LockModeType.WRITE);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue