OPENJPA-245. Backported r646082 to trunk.

git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@666888 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Patrick Linskey 2008-06-11 23:14:24 +00:00
parent 981c023e0e
commit b589da8070
5 changed files with 167 additions and 52 deletions

View File

@ -118,7 +118,10 @@ public class PCEnhancer {
public static final int ENHANCE_INTERFACE = 2 << 1;
public static final int ENHANCE_PC = 2 << 2;
private static final String PRE = "pc";
public static final String PRE = "pc";
public static final String ISDETACHEDSTATEDEFINITIVE = PRE
+ "isDetachedStateDefinitive";
private static final Class PCTYPE = PersistenceCapable.class;
private static final String SM = PRE + "StateManager";
private static final Class SMTYPE = StateManager.class;
@ -2999,12 +3002,27 @@ public class PCEnhancer {
*/
private void addIsDetachedMethod()
throws NoSuchMethodException {
// public boolean pcIsDetached ()
// public boolean pcIsDetached()
BCMethod method = _pc.declareMethod(PRE + "IsDetached",
Boolean.class, null);
method.makePublic();
Code code = method.getCode(true);
writeIsDetachedMethod(code);
boolean needsDefinitiveMethod = writeIsDetachedMethod(code);
code.calculateMaxStack();
code.calculateMaxLocals();
if (!needsDefinitiveMethod)
return;
// private boolean pcIsDetachedStateDefinitive()
// return false;
// auxilliary enhancers may change the return value of this method
// if their specs consider detached state definitive
method = _pc.declareMethod(ISDETACHEDSTATEDEFINITIVE, boolean.class,
null);
method.makePrivate();
code = method.getCode(true);
code.constant().setValue(false);
code.ireturn();
code.calculateMaxStack();
code.calculateMaxLocals();
}
@ -3012,14 +3030,17 @@ public class PCEnhancer {
/**
* Creates the body of the pcIsDetached() method to determine if an
* instance is detached.
*
* @return true if we need a pcIsDetachedStateDefinitive method, false
* otherwise
*/
private void writeIsDetachedMethod(Code code)
private boolean writeIsDetachedMethod(Code code)
throws NoSuchMethodException {
// not detachable: return Boolean.FALSE
if (!_meta.isDetachable()) {
code.getstatic().setField(Boolean.class, "FALSE", Boolean.class);
code.areturn();
return;
return false;
}
// if (sm != null)
@ -3067,7 +3088,7 @@ public class PCEnhancer {
ifins.setTarget(target);
notdeser.setTarget(target);
code.areturn();
return;
return false;
}
}
@ -3077,9 +3098,9 @@ public class PCEnhancer {
if (notdeser != null)
notdeser.setTarget(target);
// allow users with version fields to manually construct a "detached"
// instance, so check version before taking into account non-existent
// detached state
// allow users with version or auto-assigned pk fields to manually
// construct a "detached" instance, so check these before taking into
// account non-existent detached state
// consider detached if version is non-default
FieldMetaData version = _meta.getVersionField();
@ -3094,41 +3115,13 @@ public class PCEnhancer {
ifins.setTarget(code.getstatic().setField(Boolean.class, "FALSE",
Boolean.class));
code.areturn();
return;
}
// no detached state: if instance uses detached state and it's not
// synthetic or the instance is not serializable or the state isn't
// transient, must not be detached
if (state == null
&& (!ClassMetaData.SYNTHETIC.equals(_meta.getDetachedState())
|| !Serializable.class.isAssignableFrom(_meta.getDescribedType())
|| !_repos.getConfiguration().getDetachStateInstance().
isDetachedStateTransient())) {
// return Boolean.FALSE
code.getstatic().setField(Boolean.class, "FALSE", Boolean.class);
code.areturn();
return;
}
// no detached state: if instance uses detached state (and must be
// synthetic and transient in serializable instance at this point),
// not detached if state not set to DESERIALIZED
if (state == null) {
// if (pcGetDetachedState () == null) // instead of DESERIALIZED
// return Boolean.FALSE;
loadManagedInstance(code, false);
code.invokevirtual().setMethod(PRE + "GetDetachedState",
Object.class, null);
ifins = code.ifnonnull();
code.getstatic().setField(Boolean.class, "FALSE", Boolean.class);
code.areturn();
ifins.setTarget(code.nop());
return false;
}
// consider detached if auto-genned primary keys are non-default
ifins = null;
JumpInstruction ifins2 = null;
boolean hasAutoAssignedPK = false;
if (state != Boolean.TRUE
&& _meta.getIdentityType() == ClassMetaData.ID_APPLICATION) {
// for each pk field:
@ -3162,13 +3155,65 @@ public class PCEnhancer {
}
}
// give up; we just don't know
target = code.constant().setNull();
// create artificial target to simplify
target = code.nop();
if (ifins != null)
ifins.setTarget(target);
if (ifins2 != null)
ifins2.setTarget(target);
// if has auto-assigned pk and we get to this point, must have default
// value, so must be new instance
if (hasAutoAssignedPK) {
code.getstatic().setField(Boolean.class, "FALSE", Boolean.class);
code.areturn();
return false;
}
// if detached state is not definitive, just give up now and return
// null so that the runtime will perform a DB lookup to determine
// whether we're detached or new
code.aload().setThis();
code.invokespecial().setMethod(ISDETACHEDSTATEDEFINITIVE, boolean.class,
null);
ifins = code.ifne();
code.constant().setNull();
code.areturn();
ifins.setTarget(code.nop());
// no detached state: if instance uses detached state and it's not
// synthetic or the instance is not serializable or the state isn't
// transient, must not be detached
if (state == null
&& (!ClassMetaData.SYNTHETIC.equals(_meta.getDetachedState())
|| !Serializable.class.isAssignableFrom(_meta.getDescribedType())
|| !_repos.getConfiguration().getDetachStateInstance().
isDetachedStateTransient())) {
// return Boolean.FALSE
code.getstatic().setField(Boolean.class, "FALSE", Boolean.class);
code.areturn();
return true;
}
// no detached state: if instance uses detached state (and must be
// synthetic and transient in serializable instance at this point),
// not detached if state not set to DESERIALIZED
if (state == null) {
// if (pcGetDetachedState () == null) // instead of DESERIALIZED
// return Boolean.FALSE;
loadManagedInstance(code, false);
code.invokevirtual().setMethod(PRE + "GetDetachedState",
Object.class, null);
ifins = code.ifnonnull();
code.getstatic().setField(Boolean.class, "FALSE", Boolean.class);
code.areturn();
ifins.setTarget(code.nop());
}
// give up; we just don't know
code.constant().setNull();
code.areturn();
return true;
}
/**

View File

@ -69,19 +69,12 @@ class VersionAttachStrategy
public Object attach(AttachManager manager, Object toAttach,
ClassMetaData meta, PersistenceCapable into, OpenJPAStateManager owner,
ValueMetaData ownerMeta, boolean explicit) {
// VersionAttachStrategy is invoked in the case where no more
// intelligent strategy could be found; let's be more lenient
// about new vs. detached record determination.
if (into == null)
into = findFromDatabase(manager, toAttach);
BrokerImpl broker = manager.getBroker();
PersistenceCapable pc = ImplHelper.toPersistenceCapable(toAttach,
meta.getRepository().getConfiguration());
boolean embedded = ownerMeta != null && ownerMeta.isEmbeddedPC();
boolean isNew = !broker.isDetached(pc) && into == null;
boolean isNew = !broker.isDetached(pc);
Object version = null;
StateManagerImpl sm;

View File

@ -116,7 +116,9 @@ not-managed: The given instance "{0}" is not managed by this context.
trans-not-managed: This broker is not configured to use managed transactions.
bad-detached-op: You cannot perform operation {0} on detached object "{1}". \
This operation only applies to managed objects.
persist-detached: Attempt to persist detached object "{0}".
persist-detached: Attempt to persist detached object "{0}". If this is a new \
instance, make sure any versino and/or auto-generated primary key fields are \
null/default when persisting.
null-value: The field "{0}" of instance "{1}" contained a null value; \
the metadata for this field specifies that nulls are illegal.
change-identity: Attempt to change a primary key field of an instance that \
@ -396,4 +398,4 @@ cant-serialize-pessimistic-broker: Serialization not allowed for brokers with \
an active datastore (pessimistic) transaction.
cant-serialize-connected-broker: Serialization not allowed for brokers with \
an active connection to the database.
no-interface-metadata: No metadata was found for managed interface {0}.
no-interface-metadata: No metadata was found for managed interface {0}.

View File

@ -0,0 +1,72 @@
/*
* 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.detachment;
import javax.persistence.EntityManager;
import junit.textui.TestRunner;
import org.apache.openjpa.persistence.test.SingleEMFTestCase;
/**
* Test that manually constructing instances with existing primary key values
* and attaching them works.
*
* @author Abe White
*/
public class TestAttachConstructedCopy
extends SingleEMFTestCase {
public void setUp() {
setUp(Record.class);
}
public void testAttachConstructedCopyWithGeneratedPKAndNoVersion() {
Record record = new Record();
record.setContent("orig");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
em.persist(record);
em.getTransaction().commit();
em.close();
int id = record.getId();
Record copy = new Record();
copy.setId(id);
copy.setContent("new");
em = emf.createEntityManager();
em.getTransaction().begin();
record = em.merge(copy);
assertTrue(record != copy);
assertEquals("new", record.getContent());
em.getTransaction().commit();
em.close();
em = emf.createEntityManager();
record = em.find(Record.class, id);
assertEquals("new", record.getContent());
em.close();
}
public static void main(String[] args) {
TestRunner.run(TestAttachConstructedCopy.class);
}
}

View File

@ -195,7 +195,10 @@ attached instance's corresponding fields to null.
<para>
If the instance has a <literal>Version</literal> field,
OpenJPA will consider the object detached if the version field has a non-default
value, and new otherwise.
value, and new otherwise. Similarly, if the instance has
<literal>GeneratedValue</literal> primary key fields, OpenJPA will consider the
object detached if any of these fields have non-default values, and new
otherwise.
</para>
<para>
When attaching null fields in these cases, OpenJPA cannot distinguish between a