OPENJPA-134 : When we're performing eager fetches and we can detect that we're

already fetching the owning side of a bidi relation, cut off eager selecting 
and loading when we come across the back-ptr to the owner again.



git-svn-id: https://svn.apache.org/repos/asf/incubator/openjpa/trunk@527565 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
A. Abram White 2007-04-11 16:21:13 +00:00
parent 0806fd5b44
commit b4a3a77840
17 changed files with 462 additions and 57 deletions

View File

@ -233,14 +233,6 @@ public class DelegatingJDBCFetchConfiguration
} }
} }
public JDBCFetchConfiguration traverseJDBC(FieldMetaData fm) {
try {
return getJDBCDelegate().traverseJDBC(fm);
} catch (RuntimeException re) {
throw translate(re);
}
}
public int getIsolation() { public int getIsolation() {
try { try {
return getJDBCDelegate().getIsolation(); return getJDBCDelegate().getIsolation();
@ -257,4 +249,12 @@ public class DelegatingJDBCFetchConfiguration
throw translate(re); throw translate(re);
} }
} }
public JDBCFetchConfiguration traverseJDBC(FieldMetaData fm) {
try {
return getJDBCDelegate().traverseJDBC(fm);
} catch (RuntimeException re) {
throw translate(re);
}
}
} }

View File

@ -166,11 +166,6 @@ public interface JDBCFetchConfiguration
*/ */
public JDBCFetchConfiguration clearJoins(); public JDBCFetchConfiguration clearJoins();
/**
* Convenience method to cast traversal to store-specific type.
*/
public JDBCFetchConfiguration traverseJDBC(FieldMetaData fm);
/** /**
* <p>The isolation level for queries issued to the database. This overrides * <p>The isolation level for queries issued to the database. This overrides
* the persistence-unit-wide <code>openjpa.jdbc.TransactionIsolation</code> * the persistence-unit-wide <code>openjpa.jdbc.TransactionIsolation</code>
@ -204,4 +199,9 @@ public interface JDBCFetchConfiguration
* @since 0.9.7 * @since 0.9.7
*/ */
public JDBCFetchConfiguration setIsolation(int level); public JDBCFetchConfiguration setIsolation(int level);
/**
* Convenience method to cast traversal to store-specific type.
*/
public JDBCFetchConfiguration traverseJDBC(FieldMetaData fm);
} }

View File

@ -303,25 +303,6 @@ public class JDBCFetchConfigurationImpl
return this; return this;
} }
public JDBCFetchConfiguration traverseJDBC(FieldMetaData fm) {
return (JDBCFetchConfiguration) traverse(fm);
}
/**
* Access JDBC configuration information. May return null if not a
* JDBC back-end (possible to get a JDBCFetchConfiguration on non-JDBC
* back end in remote client).
*/
private JDBCConfiguration getJDBCConfiguration() {
StoreContext ctx = getContext();
if (ctx == null)
return null;
OpenJPAConfiguration conf = ctx.getConfiguration();
if (!(conf instanceof JDBCConfiguration))
return null;
return (JDBCConfiguration) conf;
}
public int getIsolation() { public int getIsolation() {
return _state.isolationLevel; return _state.isolationLevel;
} }
@ -342,4 +323,23 @@ public class JDBCFetchConfigurationImpl
_state.isolationLevel = level; _state.isolationLevel = level;
return this; return this;
} }
public JDBCFetchConfiguration traverseJDBC(FieldMetaData fm) {
return (JDBCFetchConfiguration) traverse(fm);
}
/**
* Access JDBC configuration information. May return null if not a
* JDBC back-end (possible to get a JDBCFetchConfiguration on non-JDBC
* back end in remote client).
*/
private JDBCConfiguration getJDBCConfiguration() {
StoreContext ctx = getContext();
if (ctx == null)
return null;
OpenJPAConfiguration conf = ctx.getConfiguration();
if (!(conf instanceof JDBCConfiguration))
return null;
return (JDBCConfiguration) conf;
}
} }

View File

@ -756,8 +756,8 @@ public class JDBCStoreManager
public Object load(ClassMapping mapping, JDBCFetchConfiguration fetch, public Object load(ClassMapping mapping, JDBCFetchConfiguration fetch,
BitSet exclude, Result result) throws SQLException { BitSet exclude, Result result) throws SQLException {
if (!mapping.isMapped()) if (!mapping.isMapped())
throw new InvalidStateException(_loc throw new InvalidStateException(_loc.get("virtual-mapping",
.get("virtual-mapping", mapping)); mapping));
// get the object id for the row; base class selects pk columns // get the object id for the row; base class selects pk columns
ClassMapping base = mapping; ClassMapping base = mapping;
@ -972,7 +972,7 @@ public class JDBCStoreManager
if (sm != null && sm.getPCState() != PCState.TRANSIENT if (sm != null && sm.getPCState() != PCState.TRANSIENT
&& sm.getLoaded().get(fm.getIndex())) && sm.getLoaded().get(fm.getIndex()))
return false; return false;
return fetch.requiresFetch(fm); return fetch.requiresFetch(fm) == FetchConfiguration.FETCH_LOAD;
} }
/** /**
@ -1127,7 +1127,7 @@ public class JDBCStoreManager
fms = subMappings[i].getDefinedFieldMappings(); fms = subMappings[i].getDefinedFieldMappings();
for (int j = 0; j < fms.length; j++) { for (int j = 0; j < fms.length; j++) {
// make sure in one of configured fetch groups // make sure in one of configured fetch groups
if (!fetch.requiresFetch(fms[j]) if (fetch.requiresFetch(fms[j]) != FetchConfiguration.FETCH_LOAD
&& ((!fms[j].isInDefaultFetchGroup() && ((!fms[j].isInDefaultFetchGroup()
&& fms[j].isDefaultFetchGroupExplicit()) && fms[j].isDefaultFetchGroupExplicit())
|| fms[j].supportsSelect(sel, sel.TYPE_TWO_PART, sm, this, || fms[j].supportsSelect(sel, sel.TYPE_TWO_PART, sm, this,

View File

@ -27,6 +27,7 @@ import org.apache.openjpa.jdbc.sql.Result;
import org.apache.openjpa.jdbc.sql.SQLBuffer; import org.apache.openjpa.jdbc.sql.SQLBuffer;
import org.apache.openjpa.jdbc.sql.Select; import org.apache.openjpa.jdbc.sql.Select;
import org.apache.openjpa.jdbc.sql.SelectExecutor; import org.apache.openjpa.jdbc.sql.SelectExecutor;
import org.apache.openjpa.kernel.FetchConfiguration;
import org.apache.openjpa.kernel.OpenJPAStateManager; import org.apache.openjpa.kernel.OpenJPAStateManager;
import org.apache.openjpa.kernel.StoreContext; import org.apache.openjpa.kernel.StoreContext;
import org.apache.openjpa.lib.util.Closeable; import org.apache.openjpa.lib.util.Closeable;
@ -90,7 +91,7 @@ public class PagingResultObjectProvider
FieldMapping[] fms = mapping.getDefinedFieldMappings(); FieldMapping[] fms = mapping.getDefinedFieldMappings();
BitSet paged = null; BitSet paged = null;
for (int i = 0; i < fms.length; i++) { for (int i = 0; i < fms.length; i++) {
if (!fetch.requiresFetch(fms[i])) if (fetch.requiresFetch(fms[i]) != FetchConfiguration.FETCH_LOAD)
continue; continue;
if (fms[i].supportsSelect(sel, sel.EAGER_PARALLEL, null, store, if (fms[i].supportsSelect(sel, sel.EAGER_PARALLEL, null, store,

View File

@ -777,7 +777,7 @@ public class BrokerImpl
StateManagerImpl sm = getStateManagerImplById(oid, StateManagerImpl sm = getStateManagerImplById(oid,
(flags & OID_ALLOW_NEW) != 0 || (_flags & FLAG_FLUSHED) != 0); (flags & OID_ALLOW_NEW) != 0 || (_flags & FLAG_FLUSHED) != 0);
if (sm != null) { if (sm != null) {
if (!requiresLoad(sm, true, edata, flags)) if (!requiresLoad(sm, true, fetch, edata, flags))
return call.processReturn(oid, sm); return call.processReturn(oid, sm);
if (!sm.isLoading()) { if (!sm.isLoading()) {
@ -827,7 +827,7 @@ public class BrokerImpl
// initialize a new state manager for the datastore instance // initialize a new state manager for the datastore instance
sm = newStateManagerImpl(oid, (flags & OID_COPY) != 0); sm = newStateManagerImpl(oid, (flags & OID_COPY) != 0);
boolean load = requiresLoad(sm, false, edata, flags); boolean load = requiresLoad(sm, false, fetch, edata, flags);
sm = initialize(sm, load, fetch, edata); sm = initialize(sm, load, fetch, edata);
if (sm == null) { if (sm == null) {
if ((flags & OID_NOVALIDATE) != 0) if ((flags & OID_NOVALIDATE) != 0)
@ -939,7 +939,7 @@ public class BrokerImpl
sm = newStateManagerImpl(oid, (flags & OID_COPY) != 0); sm = newStateManagerImpl(oid, (flags & OID_COPY) != 0);
_loading.put(obj, sm); _loading.put(obj, sm);
if (requiresLoad(sm, initialized, edata, flags)) { if (requiresLoad(sm, initialized, fetch, edata, flags)) {
transState = transState || useTransactionalState(fetch); transState = transState || useTransactionalState(fetch);
if (initialized && !sm.isTransactional() && transState) if (initialized && !sm.isTransactional() && transState)
sm.transactional(); sm.transactional();
@ -977,7 +977,7 @@ public class BrokerImpl
for (Iterator itr = oids.iterator(); itr.hasNext(); idx++) { for (Iterator itr = oids.iterator(); itr.hasNext(); idx++) {
oid = itr.next(); oid = itr.next();
sm = (StateManagerImpl) _loading.get(oid); sm = (StateManagerImpl) _loading.get(oid);
if (sm != null && requiresLoad(sm, true, edata, flags)) { if (sm != null && requiresLoad(sm, true, fetch, edata, flags)) {
try { try {
sm.load(fetch, StateManagerImpl.LOAD_FGS, sm.load(fetch, StateManagerImpl.LOAD_FGS,
exclude, edata, false); exclude, edata, false);
@ -1011,7 +1011,9 @@ public class BrokerImpl
* to the user. * to the user.
*/ */
private boolean requiresLoad(OpenJPAStateManager sm, boolean initialized, private boolean requiresLoad(OpenJPAStateManager sm, boolean initialized,
Object edata, int flags) { FetchConfiguration fetch, Object edata, int flags) {
if (!fetch.requiresLoad())
return false;
if ((flags & OID_NOVALIDATE) == 0) if ((flags & OID_NOVALIDATE) == 0)
return true; return true;
if (edata != null) // take advantage of existing result if (edata != null) // take advantage of existing result

View File

@ -436,7 +436,7 @@ public class DelegatingFetchConfiguration
} }
} }
public boolean requiresFetch(FieldMetaData fmd) { public int requiresFetch(FieldMetaData fmd) {
try { try {
return _fetch.requiresFetch(fmd); return _fetch.requiresFetch(fmd);
} catch (RuntimeException re) { } catch (RuntimeException re) {
@ -444,6 +444,14 @@ public class DelegatingFetchConfiguration
} }
} }
public boolean requiresLoad() {
try {
return _fetch.requiresLoad();
} catch (RuntimeException re) {
throw translate(re);
}
}
public FetchConfiguration traverse(FieldMetaData fmd) { public FetchConfiguration traverse(FieldMetaData fmd) {
try { try {
return _fetch.traverse(fmd); return _fetch.traverse(fmd);

View File

@ -231,9 +231,11 @@ public class DetachManager
StateManagerImpl sm, BitSet idxs) { StateManagerImpl sm, BitSet idxs) {
FetchConfiguration fetch = broker.getFetchConfiguration(); FetchConfiguration fetch = broker.getFetchConfiguration();
FieldMetaData[] fmds = sm.getMetaData().getFields(); FieldMetaData[] fmds = sm.getMetaData().getFields();
for (int i = 0; i < fmds.length; i++) for (int i = 0; i < fmds.length; i++) {
if (fmds[i].isPrimaryKey() || fetch.requiresFetch(fmds[i])) if (fmds[i].isPrimaryKey() || fetch.requiresFetch(fmds[i])
!= FetchConfiguration.FETCH_NONE)
idxs.set(i); idxs.set(i);
}
} }
/** /**

View File

@ -39,6 +39,32 @@ public interface FetchConfiguration
*/ */
public static final int DEFAULT = -99; public static final int DEFAULT = -99;
/**
* Constant indicating that a field does not require fetching.
*
* @see #requiresFetch
*/
public static final int FETCH_NONE = 0;
/**
* Constant indicating that a field requires a fetch and load of fetched
* data.
*
* @see #requiresFetch
*/
public static final int FETCH_LOAD = 1;
/**
* Constant indicating that a reference to the field's value must be
* fetched, but loading data is not necessary. Used when we know the
* data will be loaded anyway, such as when traversing the back-ptr of
* a bidirectional relation where the other side is also being fetched.
*
* @see #requiresFetch
*/
public static final int FETCH_REF = 2;
/** /**
* Return the context assiciated with this configuration; * Return the context assiciated with this configuration;
* may be null if it has not been set or this object has been serialized. * may be null if it has not been set or this object has been serialized.
@ -309,11 +335,19 @@ public interface FetchConfiguration
/** /**
* Affirms if the given field requires to be fetched in the context * Affirms if the given field requires to be fetched in the context
* of current fetch operation. * of current fetch operation. Returns one of {@link #FETCH_NONE},
* {@link #FETCH_LOAD}, {@link FETCH_REF}.
* *
* @since 0.4.1 * @since 0.4.1
*/ */
public boolean requiresFetch(FieldMetaData fm); public int requiresFetch(FieldMetaData fm);
/**
* Return false if we know that the object being fetched with this
* configuration does not require a load, because this configuration came
* from a traversal of a {@link #FETCH_REF} field.
*/
public boolean requiresLoad();
/** /**
* Traverse the given field to generate (possibly) a new configuration * Traverse the given field to generate (possibly) a new configuration

View File

@ -39,6 +39,7 @@ import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.meta.ClassMetaData; import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.meta.FetchGroup; import org.apache.openjpa.meta.FetchGroup;
import org.apache.openjpa.meta.FieldMetaData; import org.apache.openjpa.meta.FieldMetaData;
import org.apache.openjpa.meta.JavaTypes;
import org.apache.openjpa.util.ImplHelper; import org.apache.openjpa.util.ImplHelper;
import org.apache.openjpa.util.InternalException; import org.apache.openjpa.util.InternalException;
import org.apache.openjpa.util.NoTransactionException; import org.apache.openjpa.util.NoTransactionException;
@ -84,6 +85,8 @@ public class FetchConfigurationImpl
private FetchConfigurationImpl _parent; private FetchConfigurationImpl _parent;
private String _fromField; private String _fromField;
private Class _fromType; private Class _fromType;
private String _directRelationOwner;
private boolean _load = true;
private int _availableRecursion; private int _availableRecursion;
private int _availableDepth; private int _availableDepth;
@ -126,6 +129,8 @@ public class FetchConfigurationImpl
clone._parent = _parent; clone._parent = _parent;
clone._fromField = _fromField; clone._fromField = _fromField;
clone._fromType = _fromType; clone._fromType = _fromType;
clone._directRelationOwner = _directRelationOwner;
clone._load = _load;
clone._availableRecursion = _availableRecursion; clone._availableRecursion = _availableRecursion;
clone._availableDepth = _availableDepth; clone._availableDepth = _availableDepth;
clone.copy(this); clone.copy(this);
@ -498,23 +503,32 @@ public class FetchConfigurationImpl
// Traversal // Traversal
///////////// /////////////
public boolean requiresFetch(FieldMetaData fm) { public int requiresFetch(FieldMetaData fm) {
if (!includes(fm)) if (!includes(fm))
return false; return FETCH_NONE;
Class type = getRelationType(fm); Class type = getRelationType(fm);
if (type == null) if (type == null)
return true; return FETCH_LOAD;
if (_availableDepth == 0) if (_availableDepth == 0)
return false; return FETCH_NONE;
// we can skip calculating recursion depth if this is a top-level conf: // we can skip calculating recursion depth if this is a top-level conf:
// the field is in our fetch groups, so can't possibly not select // the field is in our fetch groups, so can't possibly not select
if (_parent == null) if (_parent == null)
return true; return FETCH_LOAD;
int rdepth = getAvailableRecursionDepth(fm, type, false); int rdepth = getAvailableRecursionDepth(fm, type, false);
return rdepth == FetchGroup.DEPTH_INFINITE || rdepth > 0; if (rdepth != FetchGroup.DEPTH_INFINITE && rdepth <= 0)
return FETCH_NONE;
if (StringUtils.equals(_directRelationOwner, fm.getFullName()))
return FETCH_REF;
return FETCH_LOAD;
}
public boolean requiresLoad() {
return _load;
} }
public FetchConfiguration traverse(FieldMetaData fm) { public FetchConfiguration traverse(FieldMetaData fm) {
@ -528,6 +542,15 @@ public class FetchConfigurationImpl
clone._fromField = fm.getFullName(false); clone._fromField = fm.getFullName(false);
clone._fromType = type; clone._fromType = type;
clone._availableRecursion = getAvailableRecursionDepth(fm, type, true); clone._availableRecursion = getAvailableRecursionDepth(fm, type, true);
if (StringUtils.equals(_directRelationOwner, fm.getFullName()))
clone._load = false;
else
clone._load = _load;
FieldMetaData owner = fm.getMappedByMetaData();
if (owner != null && owner.getTypeCode() == JavaTypes.PC)
clone._directRelationOwner = owner.getFullName();
return clone; return clone;
} }

View File

@ -138,7 +138,8 @@ public class PCDataImpl
// fields in configured fetch groups // fields in configured fetch groups
if (!isLoaded(i)) if (!isLoaded(i))
loadIntermediate(sm, fmds[i]); loadIntermediate(sm, fmds[i]);
else if (!sm.getLoaded().get(i) && fetch.requiresFetch(fmds[i])) else if (!sm.getLoaded().get(i) && fetch.requiresFetch(fmds[i])
!= FetchConfiguration.FETCH_NONE)
loadField(sm, fmds[i], fetch, context); loadField(sm, fmds[i], fetch, context);
} }
} }

View File

@ -407,7 +407,8 @@ public class StateManagerImpl
load = !fmds[i].isTransient(); load = !fmds[i].isTransient();
break; break;
case LOAD_FGS: case LOAD_FGS:
load = fetch == null || fetch.requiresFetch(fmds[i]); load = fetch == null || fetch.requiresFetch(fmds[i])
!= FetchConfiguration.FETCH_NONE;
break; break;
default: // LOAD_ALL default: // LOAD_ALL
load = true; load = true;

View File

@ -131,7 +131,8 @@ class VersionAttachStrategy
attachField(manager, toAttach, sm, fmds[i], true); attachField(manager, toAttach, sm, fmds[i], true);
break; break;
case DETACH_FGS: case DETACH_FGS:
if (fetch.requiresFetch(fmds[i])) if (fetch.requiresFetch(fmds[i])
!= FetchConfiguration.FETCH_NONE)
attachField(manager, toAttach, sm, fmds[i], true); attachField(manager, toAttach, sm, fmds[i], true);
break; break;
case DETACH_LOADED: case DETACH_LOADED:

View File

@ -0,0 +1,67 @@
/*
* Copyright 2006 The Apache Software Foundation.
*
* Licensed 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.relations;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
@Entity
public class BidiChild {
@Id
@GeneratedValue
private long id;
private String name;
@ManyToOne(fetch=FetchType.LAZY)
private BidiParent oneToManyParent;
@OneToOne(fetch=FetchType.LAZY)
private BidiParent oneToOneParent;
public long getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public BidiParent getOneToOneParent() {
return oneToOneParent;
}
public void setOneToOneParent(BidiParent parent) {
oneToOneParent = parent;
}
public BidiParent getOneToManyParent() {
return oneToManyParent;
}
public void setOneToManyParent(BidiParent parent) {
oneToManyParent = parent;
}
}

View File

@ -0,0 +1,68 @@
/*
* Copyright 2006 The Apache Software Foundation.
*
* Licensed 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.relations;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.OrderBy;
@Entity
public class BidiParent {
@Id
@GeneratedValue
private long id;
private String name;
@OneToMany(mappedBy="oneToManyParent")
@OrderBy("name ASC")
private List<BidiChild> oneToManyChildren = new ArrayList<BidiChild>();
@OneToOne(fetch=FetchType.LAZY, mappedBy="oneToOneParent")
private BidiChild oneToOneChild;
public long getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public BidiChild getOneToOneChild() {
return oneToOneChild;
}
public void setOneToOneChild(BidiChild child) {
oneToOneChild = child;
}
public List<BidiChild> getOneToManyChildren() {
return oneToManyChildren;
}
}

View File

@ -0,0 +1,196 @@
/*
* Copyright 2006 The Apache Software Foundation.
*
* Licensed 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.relations;
import java.util.List;
import javax.persistence.EntityManager;
import junit.textui.TestRunner;
import org.apache.openjpa.persistence.OpenJPAEntityManager;
import org.apache.openjpa.persistence.OpenJPAQuery;
import org.apache.openjpa.persistence.test.SQLListenerTestCase;
/**
* Test that when both sides of a mapped-by relation are eager fetched,
* traversing from the mapped-by side to the mapping side cuts off -- that
* we don't traverse in a loop back to the mapped-by side in the generated
* SQL.
*
* @author Abe White
*/
public class TestEagerBidiSQL
extends SQLListenerTestCase {
private long id1;
private long id2;
public void setUp() {
setUp(BidiParent.class, BidiChild.class);
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
for (int i = 1; i <= 2; i++) {
BidiParent parent = new BidiParent();
parent.setName("parent" + i);
em.persist(parent);
BidiChild oneOneChild = new BidiChild();
oneOneChild.setName("oneToOneChild" + i);
oneOneChild.setOneToOneParent(parent);
parent.setOneToOneChild(oneOneChild);
em.persist(oneOneChild);
for (int j = 1; j <= 3; j++) {
BidiChild oneManyChild = new BidiChild();
oneManyChild.setName("oneToManyChild" + i + "::" + j);
oneManyChild.setOneToManyParent(parent);
parent.getOneToManyChildren().add(oneManyChild);
em.persist(oneManyChild);
}
if (i == 1)
id1 = parent.getId();
else
id2 = parent.getId();
}
em.getTransaction().commit();
em.close();
}
public void testEagerOwnerOneToManyFind() {
sql.clear();
OpenJPAEntityManager em = emf.createEntityManager();
em.getFetchPlan().addField(BidiParent.class, "oneToManyChildren");
em.getFetchPlan().addField(BidiChild.class, "oneToManyParent");
BidiParent parent = em.find(BidiParent.class, id1);
assertEquals(1, sql.size());
assertNotSQL(".* LEFT OUTER JOIN BidiParent .*");
assertEquals("parent1", parent.getName());
assertEquals(3, parent.getOneToManyChildren().size());
for (int i = 0; i < 3; i++) {
assertEquals("oneToManyChild1::" + (i + 1),
parent.getOneToManyChildren().get(i).getName());
assertEquals(parent,
parent.getOneToManyChildren().get(i).getOneToManyParent());
}
assertEquals(1, sql.size());
em.close();
}
public void testEagerOwnerOneToOneFind() {
sql.clear();
OpenJPAEntityManager em = emf.createEntityManager();
em.getFetchPlan().addField(BidiParent.class, "oneToOneChild");
em.getFetchPlan().addField(BidiChild.class, "oneToOneParent");
BidiParent parent = em.find(BidiParent.class, id1);
assertEquals(1, sql.size());
assertNotSQL(".* LEFT OUTER JOIN BidiParent .*");
assertEquals("parent1", parent.getName());
assertNotNull(parent.getOneToOneChild());
assertEquals("oneToOneChild1", parent.getOneToOneChild().getName());
assertEquals(parent, parent.getOneToOneChild().getOneToOneParent());
assertEquals(1, sql.size());
em.close();
}
public void testEagerOwnerOneToManyQuery() {
sql.clear();
OpenJPAEntityManager em = emf.createEntityManager();
OpenJPAQuery q = em.createQuery("SELECT o FROM BidiParent o "
+ "ORDER BY o.name ASC");
q.getFetchPlan().addField(BidiParent.class, "oneToManyChildren");
q.getFetchPlan().addField(BidiChild.class, "oneToManyParent");
List<BidiParent> res = (List<BidiParent>) q.getResultList();
assertEquals(2, res.size());
assertEquals(sql.toString(), 2, sql.size());
assertNotSQL(".* LEFT OUTER JOIN BidiParent .*");
for (int i = 0; i < res.size(); i++) {
assertEquals("parent" + (i + 1), res.get(i).getName());
assertEquals(3, res.get(i).getOneToManyChildren().size());
for (int j = 0; j < 3; j++) {
assertEquals("oneToManyChild" + (i + 1) + "::" + (j + 1),
res.get(i).getOneToManyChildren().get(j).getName());
assertEquals(res.get(i), res.get(i).getOneToManyChildren().
get(j).getOneToManyParent());
}
}
assertEquals(2, sql.size());
em.close();
}
public void testEagerOwnerOneToOneQuery() {
sql.clear();
OpenJPAEntityManager em = emf.createEntityManager();
OpenJPAQuery q = em.createQuery("SELECT o FROM BidiParent o "
+ "ORDER BY o.name ASC");
q.getFetchPlan().addField(BidiParent.class, "oneToOneChild");
q.getFetchPlan().addField(BidiChild.class, "oneToOneParent");
List<BidiParent> res = (List<BidiParent>) q.getResultList();
assertEquals(2, res.size());
assertEquals(1, sql.size());
assertNotSQL(".* LEFT OUTER JOIN BidiParent .*");
for (int i = 0; i < res.size(); i++) {
assertEquals("parent" + (i + 1), res.get(i).getName());
assertNotNull(res.get(i).getOneToOneChild());
assertEquals("oneToOneChild" + (i + 1),
res.get(i).getOneToOneChild().getName());
assertEquals(res.get(i),
res.get(i).getOneToOneChild().getOneToOneParent());
}
assertEquals(1, sql.size());
em.close();
}
public void testEagerNonOwnerOneToOneQuery() {
sql.clear();
OpenJPAEntityManager em = emf.createEntityManager();
OpenJPAQuery q = em.createQuery("SELECT o FROM BidiParent o "
+ "ORDER BY o.name ASC");
q.getFetchPlan().addField(BidiParent.class, "oneToOneChild");
q.getFetchPlan().addField(BidiChild.class, "oneToManyParent");
List<BidiParent> res = (List<BidiParent>) q.getResultList();
assertEquals(2, res.size());
assertEquals(1, sql.size());
assertSQL(".* LEFT OUTER JOIN BidiParent .*");
for (int i = 0; i < res.size(); i++) {
assertEquals("parent" + (i + 1), res.get(i).getName());
assertNotNull(res.get(i).getOneToOneChild());
assertEquals("oneToOneChild" + (i + 1),
res.get(i).getOneToOneChild().getName());
assertNull(res.get(i).getOneToOneChild().getOneToManyParent());
}
assertEquals(1, sql.size());
em.close();
}
public static void main(String[] args) {
TestRunner.run(TestEagerBidiSQL.class);
}
}

View File

@ -111,7 +111,8 @@ public final class ObjectData
FieldMetaData[] fmds = _meta.getFields(); FieldMetaData[] fmds = _meta.getFields();
for (int i = 0; i < fmds.length; i++) for (int i = 0; i < fmds.length; i++)
if (!sm.getLoaded().get(i) && fetch.requiresFetch(fmds[i])) if (!sm.getLoaded().get(i) && fetch.requiresFetch(fmds[i])
!= FetchConfiguration.FETCH_NONE)
sm.store(i, toLoadable(sm, fmds[i], _data[i], fetch)); sm.store(i, toLoadable(sm, fmds[i], _data[i], fetch));
} }