diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreManager.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreManager.java index bd6a46108..b24525f7a 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreManager.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreManager.java @@ -97,8 +97,7 @@ import org.apache.openjpa.util.UserException; * @author Abe White * @nojavadoc */ -public class JDBCStoreManager - implements StoreManager, JDBCStore { +public class JDBCStoreManager implements StoreManager, JDBCStore { private static final Localizer _loc = Localizer.forPackage (JDBCStoreManager.class); @@ -112,7 +111,8 @@ public class JDBCStoreManager private RefCountConnection _conn = null; private boolean _active = false; private Log _log = null; - + boolean _ignoreDfgForFkSelect = false; + // track the pending statements so we can cancel them private Set _stmnts = Collections.synchronizedSet(new HashSet()); @@ -1404,17 +1404,16 @@ public class JDBCStoreManager } /** - * When selecting fieldes, a special case is made for mappings that use - * 2-part selects that aren't explicitly *not* in the dfg so that they - * can get their primary table data. This method tests for that special - * case as an optimization. + * When selecting fields, a special case is made for mappings that use 2-part selects that aren't explicitly *not* + * in the dfg so that they can get their primary table data. This method tests for that special case as an + * optimization. */ - private boolean optSelect(FieldMapping fm, Select sel, - OpenJPAStateManager sm, JDBCFetchConfiguration fetch) { - return !fm.isInDefaultFetchGroup() - && !fm.isDefaultFetchGroupExplicit() - && (sm == null || sm.getPCState() == PCState.TRANSIENT - || !sm.getLoaded().get(fm.getIndex())) + private boolean optSelect(FieldMapping fm, Select sel, OpenJPAStateManager sm, JDBCFetchConfiguration fetch) { + boolean dfg = + _ignoreDfgForFkSelect || + !fm.isInDefaultFetchGroup() && !fm.isDefaultFetchGroupExplicit(); + + return dfg && (sm == null || sm.getPCState() == PCState.TRANSIENT || !sm.getLoaded().get(fm.getIndex())) && fm.supportsSelect(sel, Select.TYPE_TWO_PART, sm, this, fetch) > 0; } @@ -1552,10 +1551,12 @@ public class JDBCStoreManager ? getConfiguration().getFinderCacheInstance() : null; } + public void setIgnoreDfgForFkSelect(boolean b) { + _ignoreDfgForFkSelect = b; + } /** - * Connection returned to client code. Makes sure its wrapped connection - * ref count is decremented on finalize. + * Connection returned to client code. Makes sure its wrapped connection ref count is decremented on finalize. */ public abstract static class ClientConnection extends DelegatingConnection { diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jdbc/kernel/OptSelectEntity.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jdbc/kernel/OptSelectEntity.java new file mode 100644 index 000000000..b6d39a814 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jdbc/kernel/OptSelectEntity.java @@ -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.jdbc.kernel; + +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.OneToOne; +import javax.persistence.Version; + +@Entity +public class OptSelectEntity { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + int id; + + @Version + int version; + + @OneToOne + OptSelectEntity eagerOneToOne; + + @OneToOne(mappedBy = "eagerOneToOne") + OptSelectEntity eagerOneToOneOwner; + + @OneToOne(fetch = FetchType.LAZY) + OptSelectEntity lazyOneToOne; + + @OneToOne(mappedBy = "lazyOneToOne", fetch = FetchType.LAZY) + OptSelectEntity lazyOneToOneOwner; + + public int getId() { + return id; + } + + public OptSelectEntity getEagerOneToOne() { + return eagerOneToOne; + } + + public void setEagerOneToOne(OptSelectEntity eagerOneToOne) { + this.eagerOneToOne = eagerOneToOne; + } + + public OptSelectEntity getEagerOneToOneOwner() { + return eagerOneToOneOwner; + } + + public void setEagerOneToOneOwner(OptSelectEntity eagerOneToOneOwner) { + this.eagerOneToOneOwner = eagerOneToOneOwner; + } + + public OptSelectEntity getLazyOneToOne() { + return lazyOneToOne; + } + + public void setLazyOneToOne(OptSelectEntity lazyOneToOne) { + this.lazyOneToOne = lazyOneToOne; + } + + public OptSelectEntity getLazyOneToOneOwner() { + return lazyOneToOneOwner; + } + + public void setLazyOneToOneOwner(OptSelectEntity lazyOneToOneOwner) { + this.lazyOneToOneOwner = lazyOneToOneOwner; + } + +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jdbc/kernel/TestJDBCStoreOptSelect.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jdbc/kernel/TestJDBCStoreOptSelect.java new file mode 100644 index 000000000..dabab8adb --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jdbc/kernel/TestJDBCStoreOptSelect.java @@ -0,0 +1,127 @@ +/* + * 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.jdbc.kernel; + +import org.apache.openjpa.enhance.PersistenceCapable; +import org.apache.openjpa.kernel.StateManagerImpl; +import org.apache.openjpa.kernel.StoreManager; +import org.apache.openjpa.meta.ClassMetaData; +import org.apache.openjpa.meta.FieldMetaData; +import org.apache.openjpa.meta.MetaDataRepository; +import org.apache.openjpa.persistence.EntityManagerImpl; +import org.apache.openjpa.persistence.FetchPlan; +import org.apache.openjpa.persistence.OpenJPAEntityManagerSPI; +import org.apache.openjpa.persistence.test.SQLListenerTestCase; + +public class TestJDBCStoreOptSelect extends SQLListenerTestCase { + Object[] props = new Object[] { CLEAR_TABLES, OptSelectEntity.class }; + OptSelectEntity e1, e2; + + @Override + public void setUp() throws Exception { + super.setUp(props); + createData(); + } + + public void test() { + OpenJPAEntityManagerSPI em = emf.createEntityManager(); + StoreManager store = ((EntityManagerImpl) em).getBroker().getStoreManager().getDelegate(); + + FetchPlan fp = getFetchPlan(em); + try { + sql.clear(); + + if (store instanceof JDBCStoreManager == false) { + fail("StoreManager is not an instanceof JDBCStoreManager"); + } + // Set this JDBCStoreManager property so that we will select FKs for fields that are in the DFG, but not + // included in the current select. + ((JDBCStoreManager) store).setIgnoreDfgForFkSelect(true); + + // Remove all relationships + fp.removeField(OptSelectEntity.class, "eagerOneToOne"); + fp.removeField(OptSelectEntity.class, "eagerOneToOneOwner"); + fp.removeField(OptSelectEntity.class, "lazyOneToOne"); + fp.removeField(OptSelectEntity.class, "lazyOneToOneOwner"); + + OptSelectEntity ee1 = em.find(OptSelectEntity.class, e1.getId()); + + // Make sure our sql has no joins + assertEquals(1, sql.size()); + String s = sql.get(0); + assertFalse(s.contains("JOIN") && s.contains("join")); + + // Check to see how many fks(intermediate fields) we selected. + StateManagerImpl smi = ((StateManagerImpl) ((PersistenceCapable) ee1).pcGetStateManager()); + ClassMetaData cmd = + em.getConfiguration().getMetaDataRepositoryInstance().getMetaData(OptSelectEntity.class, null, true); + int fks = 0; + for (FieldMetaData fmd : cmd.getFields()) { + if (smi.getIntermediate(fmd.getIndex()) != null) { + fks++; + } + } + assertEquals(2, fks); + } finally { + if (em.getTransaction().isActive()) { + em.getTransaction().rollback(); + } + if (em.isOpen()) { + em.close(); + } + } + } + + private FetchPlan getFetchPlan(OpenJPAEntityManagerSPI em) { + MetaDataRepository mdr = em.getConfiguration().getMetaDataRepositoryInstance(); + FetchPlan fp = em.pushFetchPlan(); + fp.removeFetchGroups(fp.getFetchGroups()); + for (Class cls : new Class[] { OptSelectEntity.class }) { + ClassMetaData cmd = mdr.getMetaData(cls, null, true); + for (FieldMetaData fmd : cmd.getFields()) { + fp.addField(cls, fmd.getName()); + } + } + return fp; + } + + void createData() { + OpenJPAEntityManagerSPI em = emf.createEntityManager(); + try { + em.getTransaction().begin(); + e1 = new OptSelectEntity(); + e2 = new OptSelectEntity(); + + e1.setEagerOneToOne(e2); + e2.setEagerOneToOneOwner(e2); + + e1.setLazyOneToOne(e2); + e2.setLazyOneToOneOwner(e1); + + em.persistAll(e1, e2); + + em.getTransaction().commit(); + } finally { + if (em.getTransaction().isActive()) + em.getTransaction().rollback(); + if (em.isOpen()) + em.close(); + } + } +}