mirror of https://github.com/apache/openjpa.git
OPENJPA-1097 Detachment processing of our proxied mutable types (Date, Timestamp, etc) needs to be consistent as EM.clear() was not unproxying JavaTypes.OBJECT fields but detach()/detachAll() were. Thanks to Rick Curtis for the DetachManager changes.
git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@919696 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
bb538f49f9
commit
b9a230567e
|
@ -476,8 +476,7 @@ public class DetachManager
|
|||
_fullFM.reproxy(detSM);
|
||||
_fullFM.setStateManager(null);
|
||||
} else {
|
||||
InstanceDetachFieldManager fm = new InstanceDetachFieldManager
|
||||
(detachedPC, detSM);
|
||||
InstanceDetachFieldManager fm = new InstanceDetachFieldManager(detachedPC, detSM);
|
||||
fm.setStateManager(sm);
|
||||
fm.detachFields(fields);
|
||||
}
|
||||
|
@ -548,28 +547,28 @@ public class DetachManager
|
|||
* Unproxies second class object fields.
|
||||
*/
|
||||
public void reproxy(DetachedStateManager dsm) {
|
||||
FieldMetaData[] fmds = sm.getMetaData().getFields();
|
||||
for (int i = 0; i < fmds.length; i++) {
|
||||
switch (fmds[i].getDeclaredTypeCode()) {
|
||||
for (FieldMetaData fmd : sm.getMetaData().getProxyFields()) {
|
||||
switch (fmd.getDeclaredTypeCode()) {
|
||||
case JavaTypes.COLLECTION:
|
||||
case JavaTypes.MAP:
|
||||
// lrs proxies not detached
|
||||
if (fmds[i].isLRS()) {
|
||||
if (fmd.isLRS()) {
|
||||
objval = null;
|
||||
sm.replaceField(getDetachedPersistenceCapable(),
|
||||
this, i);
|
||||
sm.replaceField(getDetachedPersistenceCapable(), this, fmd.getIndex());
|
||||
break;
|
||||
}
|
||||
// no break
|
||||
case JavaTypes.CALENDAR:
|
||||
case JavaTypes.DATE:
|
||||
case JavaTypes.OBJECT:
|
||||
sm.provideField(getDetachedPersistenceCapable(), this, i);
|
||||
sm.provideField(getDetachedPersistenceCapable(), this, fmd.getIndex());
|
||||
if (objval instanceof Proxy) {
|
||||
Proxy proxy = (Proxy) objval;
|
||||
if (proxy.getChangeTracker() != null)
|
||||
proxy.getChangeTracker().stopTracking();
|
||||
proxy.setOwner(dsm, (dsm == null) ? -1 : i);
|
||||
proxy.setOwner(dsm, (dsm == null) ? -1 : fmd.getIndex());
|
||||
objval = proxy.copy(proxy);
|
||||
sm.replaceField(getDetachedPersistenceCapable(), this, fmd.getIndex());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -711,8 +710,10 @@ public class DetachManager
|
|||
* Set the owner of the field's proxy to the detached state manager.
|
||||
*/
|
||||
private Object reproxy(Object obj, int field) {
|
||||
if (obj != null && _detSM != null && obj instanceof Proxy)
|
||||
if (obj != null && _detSM != null && obj instanceof Proxy){
|
||||
((Proxy) obj).setOwner(_detSM, field);
|
||||
return ((Proxy) obj).copy(obj);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* 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.detach;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.sql.Date;
|
||||
import java.sql.Time;
|
||||
import java.sql.Timestamp;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.Temporal;
|
||||
import javax.persistence.TemporalType;
|
||||
|
||||
@Entity
|
||||
@Table(name="Entity20_detach")
|
||||
public class Entity20 implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Id
|
||||
private Integer id;
|
||||
|
||||
@Column(name = "sqldate" )
|
||||
@Temporal(TemporalType.DATE)
|
||||
private Date sqlDate;
|
||||
|
||||
@Column(name = "sqltime")
|
||||
@Temporal(TemporalType.TIME)
|
||||
private Time sqlTime;
|
||||
|
||||
@Column(name = "sqltimestamp")
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
private Timestamp sqlTimestamp;
|
||||
|
||||
private String name;
|
||||
|
||||
public Entity20() {
|
||||
}
|
||||
|
||||
public Entity20(int id) {
|
||||
this.id = new Integer(id);
|
||||
this.name = this.id.toString();
|
||||
Long time = System.currentTimeMillis();
|
||||
this.sqlTime = new Time(time);
|
||||
this.sqlDate = new Date(time);
|
||||
this.sqlTimestamp = new Timestamp(time);
|
||||
}
|
||||
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setDate(Date d) {
|
||||
sqlDate = d;
|
||||
}
|
||||
|
||||
public Date getDate() {
|
||||
return sqlDate;
|
||||
}
|
||||
|
||||
public void setTime(Time t) {
|
||||
sqlTime = t;
|
||||
}
|
||||
|
||||
public Time getTime() {
|
||||
return sqlTime;
|
||||
}
|
||||
|
||||
public void setTimestamp(Timestamp t) {
|
||||
sqlTimestamp = t;
|
||||
}
|
||||
|
||||
public Timestamp getTimestamp() {
|
||||
return sqlTimestamp;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,262 @@
|
|||
/*
|
||||
* 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.detach;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.apache.openjpa.conf.Compatibility;
|
||||
import org.apache.openjpa.lib.log.Log;
|
||||
import org.apache.openjpa.persistence.OpenJPAEntityManager;
|
||||
import org.apache.openjpa.persistence.test.SingleEMFTestCase;
|
||||
|
||||
public class TestDetachNoProxy extends SingleEMFTestCase {
|
||||
|
||||
private static final int numEntities = 3;
|
||||
private static final String PROXY = new String("$proxy");
|
||||
private Log log;
|
||||
|
||||
public void setUp() {
|
||||
setUp(DROP_TABLES, Entity20.class);
|
||||
log = emf.getConfiguration().getLog("test");
|
||||
|
||||
// check and set Compatibility values to new 2.0 values
|
||||
Compatibility compat = emf.getConfiguration().getCompatibilityInstance();
|
||||
assertNotNull(compat);
|
||||
if (log.isTraceEnabled()) {
|
||||
log.info("Before set, FlushBeforeDetach=" + compat.getFlushBeforeDetach());
|
||||
log.info("Before set, CopyOnDetach=" + compat.getCopyOnDetach());
|
||||
log.info("Before set, CascadeWithDetach=" + compat.getCascadeWithDetach());
|
||||
}
|
||||
compat.setFlushBeforeDetach(false);
|
||||
compat.setCopyOnDetach(false);
|
||||
compat.setCascadeWithDetach(false);
|
||||
if (log.isTraceEnabled()) {
|
||||
log.info("After set, FlushBeforeDetach=" + compat.getFlushBeforeDetach());
|
||||
log.info("After set, CopyOnDetach=" + compat.getCopyOnDetach());
|
||||
log.info("After set, CascadeWithDetach=" + compat.getCascadeWithDetach());
|
||||
}
|
||||
createEntities(numEntities);
|
||||
}
|
||||
|
||||
private void createEntities(int count) {
|
||||
Entity20 e20 = null;
|
||||
OpenJPAEntityManager em = emf.createEntityManager();
|
||||
em.getTransaction().begin();
|
||||
for (int i=0; i<count; i++) {
|
||||
e20 = new Entity20(i);
|
||||
em.persist(e20);
|
||||
}
|
||||
em.getTransaction().commit();
|
||||
em.close();
|
||||
}
|
||||
|
||||
/*
|
||||
* Verify that an in-place detached entity does not use the proxy classes.
|
||||
*/
|
||||
public void testDetach() {
|
||||
if (log.isTraceEnabled())
|
||||
log.info("***** testDetach() *****");
|
||||
Integer id = new Integer(0);
|
||||
OpenJPAEntityManager em = emf.createEntityManager();
|
||||
|
||||
em.clear();
|
||||
Entity20 e20 = em.find(Entity20.class, id);
|
||||
if (log.isTraceEnabled())
|
||||
log.trace("** after find");
|
||||
assertTrue(em.contains(e20));
|
||||
verifySerializable(e20, true);
|
||||
|
||||
// new openjpa-2.0.0 behavior, where detach() doesn't return updated entity, but does it in-place
|
||||
em.detach(e20);
|
||||
if (log.isTraceEnabled())
|
||||
log.trace("** after detach");
|
||||
// in-place updated entity should not have any proxy classes and should be detached
|
||||
assertTrue(em.isDetached(e20));
|
||||
verifySerializable(e20, false);
|
||||
|
||||
em.close();
|
||||
}
|
||||
|
||||
/*
|
||||
* Verify that a detachCopy() returned entity does not contain any proxy classes.
|
||||
*/
|
||||
public void testDetachCopy() {
|
||||
if (log.isTraceEnabled())
|
||||
log.info("***** testDetachCopy() *****");
|
||||
Integer id = new Integer(0);
|
||||
OpenJPAEntityManager em = emf.createEntityManager();
|
||||
em.clear();
|
||||
|
||||
Entity20 e20 = em.find(Entity20.class, id);
|
||||
if (log.isTraceEnabled())
|
||||
log.trace("** after find");
|
||||
assertTrue(em.contains(e20));
|
||||
verifySerializable(e20, true);
|
||||
|
||||
// Test new detachCopy() method added in 2.0.0
|
||||
Entity20 e20copy = em.detachCopy(e20);
|
||||
if (log.isTraceEnabled())
|
||||
log.trace("** after detachCopy");
|
||||
assertTrue(em.isDetached(e20copy));
|
||||
verifySerializable(e20copy, false);
|
||||
|
||||
em.close();
|
||||
}
|
||||
|
||||
/*
|
||||
* Verify that in-place detachAll entities do not use the proxy classes.
|
||||
*/
|
||||
public void testDetachAll() {
|
||||
if (log.isTraceEnabled())
|
||||
log.info("***** testDetachAll() *****");
|
||||
OpenJPAEntityManager em = emf.createEntityManager();
|
||||
em.clear();
|
||||
|
||||
ArrayList<Entity20> e20List = new ArrayList<Entity20>(numEntities);
|
||||
for (int i=0; i<numEntities; i++) {
|
||||
Entity20 e20 = em.find(Entity20.class, new Integer(i));
|
||||
e20List.add(e20);
|
||||
if (log.isTraceEnabled())
|
||||
log.trace("** after find Entity20(" + i + ")");
|
||||
assertTrue(em.contains(e20));
|
||||
verifySerializable(e20, true);
|
||||
}
|
||||
|
||||
// new openjpa-2.0.0 behavior, where detachAll() updates entities in-place
|
||||
// ArrayList<Entity20> e20ListCopy = new ArrayList<Entity20>(em.detachAll(e20List));
|
||||
// em.detachAll(e20List); // for some reason calling with Collection causes a NPE, so use Object[] instead
|
||||
em.detachAll(e20List.get(0), e20List.get(1), e20List.get(2));
|
||||
for (int i=0; i<numEntities; i++) {
|
||||
if (log.isTraceEnabled())
|
||||
log.trace("** after EM.clear() verify Entity20(" + i + ")");
|
||||
Entity20 e20 = e20List.get(i);
|
||||
// entity should not have any proxy classes and should be detached
|
||||
assertTrue(em.isDetached(e20));
|
||||
verifySerializable(e20, false);
|
||||
}
|
||||
|
||||
em.close();
|
||||
}
|
||||
|
||||
/*
|
||||
* Verify that after EM.clear() in-place detached entities do not contain any proxy classes.
|
||||
*/
|
||||
public void testClear() {
|
||||
if (log.isTraceEnabled())
|
||||
log.info("***** testClear() *****");
|
||||
OpenJPAEntityManager em = emf.createEntityManager();
|
||||
em.clear();
|
||||
|
||||
ArrayList<Entity20> e20List = new ArrayList<Entity20>(numEntities);
|
||||
for (int i=0; i<numEntities; i++) {
|
||||
Entity20 e20 = em.find(Entity20.class, new Integer(i));
|
||||
e20List.add(e20);
|
||||
if (log.isTraceEnabled())
|
||||
log.trace("** after find Entity20(" + i + ")");
|
||||
assertTrue(em.contains(e20));
|
||||
verifySerializable(e20, true);
|
||||
}
|
||||
|
||||
em.clear();
|
||||
for (int i=0; i<numEntities; i++) {
|
||||
if (log.isTraceEnabled())
|
||||
log.trace("** after EM.clear() verify Entity20(" + i + ")");
|
||||
Entity20 e20 = e20List.get(i);
|
||||
assertTrue(em.isDetached(e20));
|
||||
verifySerializable(e20, false);
|
||||
}
|
||||
|
||||
em.close();
|
||||
}
|
||||
|
||||
|
||||
private void verifySerializable(Entity20 e20, boolean usesProxy) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
ObjectOutputStream oos = null;
|
||||
byte[] e20bytes = null;
|
||||
|
||||
if (log.isTraceEnabled())
|
||||
log.trace("verifySerializable() - before serialize");
|
||||
verifyEntities(e20, usesProxy);
|
||||
|
||||
// first serialize
|
||||
try {
|
||||
oos = new ObjectOutputStream(baos);
|
||||
oos.writeObject(e20);
|
||||
e20bytes = baos.toByteArray();
|
||||
} catch (IOException e) {
|
||||
fail(e.toString());
|
||||
} finally {
|
||||
try {
|
||||
if (oos != null)
|
||||
oos.close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
|
||||
// then deserialize
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(e20bytes);
|
||||
ObjectInputStream ois = null;
|
||||
Entity20 e20new = null;
|
||||
try {
|
||||
ois = new ObjectInputStream(bais);
|
||||
e20new = (Entity20) ois.readObject();
|
||||
if (log.isTraceEnabled())
|
||||
log.trace("verifySerializable() - after deserialize");
|
||||
verifyEntities(e20new, false);
|
||||
} catch (IOException e) {
|
||||
fail(e.toString());
|
||||
} catch (ClassNotFoundException e) {
|
||||
fail(e.toString());
|
||||
} finally {
|
||||
try {
|
||||
if (ois != null)
|
||||
ois.close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void verifyEntities(Entity20 e20, boolean usesProxy) {
|
||||
if (log.isTraceEnabled()) {
|
||||
printClassNames(e20);
|
||||
log.trace("asserting expected proxy usage");
|
||||
}
|
||||
assertTrue("Expected sqlDate endsWith($proxy) to return " + usesProxy,
|
||||
usesProxy == e20.getDate().getClass().getCanonicalName().endsWith(PROXY));
|
||||
assertTrue("Expected sqlTime endsWith($proxy) to return " + usesProxy,
|
||||
usesProxy == e20.getTime().getClass().getCanonicalName().endsWith(PROXY));
|
||||
assertTrue("Expected sqlTimestamp endsWith($proxy) to return " + usesProxy,
|
||||
usesProxy == e20.getTimestamp().getClass().getCanonicalName().endsWith(PROXY));
|
||||
|
||||
}
|
||||
|
||||
private void printClassNames(Entity20 e20) {
|
||||
log.info("sqlDate = " + e20.getDate().getClass().getCanonicalName());
|
||||
log.info("sqlTime = " + e20.getTime().getClass().getCanonicalName());
|
||||
log.info("sqlTimestamp = " + e20.getTimestamp().getClass().getCanonicalName());
|
||||
}
|
||||
|
||||
}
|
|
@ -124,10 +124,12 @@ public class TestProxyCollection extends SingleEMFTestCase {
|
|||
modified.modify(modifier);
|
||||
em.merge(modified);
|
||||
em.getTransaction().commit();
|
||||
|
||||
// this was unproxied by EM.clear() in create() below
|
||||
// assertProxyCollection(root.getNodes(), true);
|
||||
assertNotProxyCollection(root.getNodes());
|
||||
|
||||
em.clear();
|
||||
|
||||
assertProxyCollection(root.getNodes(), false);
|
||||
|
||||
verify(root, modifier);
|
||||
}
|
||||
|
||||
|
@ -145,7 +147,11 @@ public class TestProxyCollection extends SingleEMFTestCase {
|
|||
em.getTransaction().begin();
|
||||
em.persist(root);
|
||||
em.getTransaction().commit();
|
||||
// OPENJPA-1097 Fixed behavior so entities will use the proxy classes until EM.clear() is called
|
||||
assertProxyCollection(root.getNodes(), true);
|
||||
em.clear();
|
||||
// OPENJPA-1097 All proxies are removed after EM.clear()
|
||||
assertNotProxyCollection(root.getNodes());
|
||||
|
||||
return root;
|
||||
}
|
||||
|
@ -181,4 +187,12 @@ public class TestProxyCollection extends SingleEMFTestCase {
|
|||
assertFalse(tracker.isTracking());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the given object is NOT a proxy collection.
|
||||
*/
|
||||
void assertNotProxyCollection(Object o) {
|
||||
assertFalse(o instanceof ProxyCollection);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue