| /* |
| * 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 java.sql.Connection; |
| import java.sql.PreparedStatement; |
| import java.sql.ResultSet; |
| import java.sql.SQLException; |
| import java.sql.Statement; |
| import java.util.ArrayList; |
| import java.util.BitSet; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| import javax.sql.DataSource; |
| |
| import org.apache.openjpa.datacache.QueryCache; |
| import org.apache.openjpa.datacache.QueryCacheStoreQuery; |
| import org.apache.openjpa.enhance.PersistenceCapable; |
| import org.apache.openjpa.event.OrphanedKeyAction; |
| import org.apache.openjpa.jdbc.conf.JDBCConfiguration; |
| import org.apache.openjpa.jdbc.meta.ClassMapping; |
| import org.apache.openjpa.jdbc.meta.Discriminator; |
| import org.apache.openjpa.jdbc.meta.FieldMapping; |
| import org.apache.openjpa.jdbc.meta.ValueMapping; |
| import org.apache.openjpa.jdbc.meta.strats.SuperclassDiscriminatorStrategy; |
| import org.apache.openjpa.jdbc.sql.DBDictionary; |
| import org.apache.openjpa.jdbc.sql.JoinSyntaxes; |
| import org.apache.openjpa.jdbc.sql.Joins; |
| import org.apache.openjpa.jdbc.sql.Result; |
| import org.apache.openjpa.jdbc.sql.SQLExceptions; |
| import org.apache.openjpa.jdbc.sql.SQLFactory; |
| import org.apache.openjpa.jdbc.sql.Select; |
| import org.apache.openjpa.jdbc.sql.SelectExecutor; |
| import org.apache.openjpa.jdbc.sql.Union; |
| import org.apache.openjpa.kernel.BrokerImpl; |
| import org.apache.openjpa.kernel.FetchConfiguration; |
| import org.apache.openjpa.kernel.FinderCache; |
| import org.apache.openjpa.kernel.LockManager; |
| import org.apache.openjpa.kernel.OpenJPAStateManager; |
| import org.apache.openjpa.kernel.PCState; |
| import org.apache.openjpa.kernel.QueryLanguages; |
| import org.apache.openjpa.kernel.Seq; |
| import org.apache.openjpa.kernel.StoreContext; |
| import org.apache.openjpa.kernel.StoreManager; |
| import org.apache.openjpa.kernel.StoreQuery; |
| import org.apache.openjpa.kernel.exps.ExpressionParser; |
| import org.apache.openjpa.lib.jdbc.DelegatingConnection; |
| import org.apache.openjpa.lib.jdbc.DelegatingPreparedStatement; |
| import org.apache.openjpa.lib.jdbc.DelegatingStatement; |
| import org.apache.openjpa.lib.log.Log; |
| import org.apache.openjpa.lib.rop.MergedResultObjectProvider; |
| import org.apache.openjpa.lib.rop.ResultObjectProvider; |
| import org.apache.openjpa.lib.util.Localizer; |
| import org.apache.openjpa.meta.ClassMetaData; |
| import org.apache.openjpa.meta.FieldMetaData; |
| import org.apache.openjpa.meta.JavaTypes; |
| import org.apache.openjpa.meta.ValueStrategies; |
| import org.apache.openjpa.util.ApplicationIds; |
| import org.apache.openjpa.util.Exceptions; |
| import org.apache.openjpa.util.Id; |
| import org.apache.openjpa.util.ImplHelper; |
| import org.apache.openjpa.util.InvalidStateException; |
| import org.apache.openjpa.util.OpenJPAId; |
| import org.apache.openjpa.util.StoreException; |
| import org.apache.openjpa.util.UserException; |
| |
| /** |
| * StoreManager plugin that uses JDBC to store persistent data in a |
| * relational data store. |
| * |
| * @author Abe White |
| */ |
| public class JDBCStoreManager implements StoreManager, JDBCStore { |
| |
| private static final Localizer _loc = Localizer.forPackage |
| (JDBCStoreManager.class); |
| |
| private StoreContext _ctx = null; |
| private JDBCConfiguration _conf = null; |
| private DBDictionary _dict = null; |
| private SQLFactory _sql = null; |
| private JDBCLockManager _lm = null; |
| private DataSource _ds = null; |
| private RefCountConnection _conn = null; |
| private boolean _active = false; |
| private Log _log = null; |
| |
| // track the pending statements so we can cancel them |
| private List<Statement> _stmnts = Collections.synchronizedList(new ArrayList<Statement>()); |
| |
| // pool statements so that we can try to reuse rather than recreate |
| private List<CancelPreparedStatement> _cancelPreparedStatementsPool = new ArrayList<>(); |
| private List<CancelStatement> _cancelStatementPool = new ArrayList<>(); |
| |
| @Override |
| public StoreContext getContext() { |
| return _ctx; |
| } |
| |
| @Override |
| public void setContext(StoreContext ctx) { |
| setContext(ctx, (JDBCConfiguration) ctx.getConfiguration()); |
| } |
| |
| public void setContext(StoreContext ctx, JDBCConfiguration conf) { |
| _ctx = ctx; |
| _conf = conf; |
| _dict = _conf.getDBDictionaryInstance(); |
| _sql = _conf.getSQLFactoryInstance(); |
| _log = _conf.getLog(JDBCConfiguration.LOG_DIAG); |
| |
| LockManager lm = ctx.getLockManager(); |
| if (lm instanceof JDBCLockManager) |
| _lm = (JDBCLockManager) lm; |
| |
| _ds = getDataSource(ctx); |
| |
| if (_conf.getUpdateManagerInstance().orderDirty()) |
| ctx.setOrderDirtyObjects(true); |
| } |
| |
| private boolean useConnectionFactory2(StoreContext ctx) { |
| return (!ctx.isManaged() && _conf.isConnectionFactoryModeManaged()); |
| } |
| |
| private DataSource getDataSource(StoreContext ctx) { |
| DataSource ds; |
| |
| if(useConnectionFactory2(ctx)) { |
| ds = _conf.getDataSource2(ctx); |
| } |
| else { |
| ds = _conf.getDataSource(ctx); |
| } |
| return ds; |
| } |
| |
| @Override |
| public JDBCConfiguration getConfiguration() { |
| return _conf; |
| } |
| |
| @Override |
| public DBDictionary getDBDictionary() { |
| return _dict; |
| } |
| |
| @Override |
| public SQLFactory getSQLFactory() { |
| return _sql; |
| } |
| |
| @Override |
| public JDBCLockManager getLockManager() { |
| return _lm; |
| } |
| |
| @Override |
| public JDBCFetchConfiguration getFetchConfiguration() { |
| return (JDBCFetchConfiguration) _ctx.getFetchConfiguration(); |
| } |
| |
| @Override |
| public void beginOptimistic() { |
| } |
| |
| @Override |
| public void rollbackOptimistic() { |
| } |
| |
| @Override |
| public void begin() { |
| _active = true; |
| try { |
| if ((!_ctx.isManaged() || !_conf.isConnectionFactoryModeManaged()) |
| && _conn.getAutoCommit()) |
| _conn.setAutoCommit(false); |
| } catch (SQLException se) { |
| _active = false; |
| throw SQLExceptions.getStore(se, _dict); |
| } |
| } |
| |
| @Override |
| public void commit() { |
| try { |
| if (!_ctx.isManaged() || !_conf.isConnectionFactoryModeManaged()) |
| _conn.commit(); |
| } catch (SQLException se) { |
| try { |
| _conn.rollback(); |
| } catch (SQLException se2) { |
| } |
| throw SQLExceptions.getStore(se, _dict); |
| } finally { |
| _active = false; |
| } |
| } |
| |
| @Override |
| public void rollback() { |
| // already rolled back ourselves? |
| if (!_active) |
| return; |
| |
| try { |
| if (_conn != null |
| && (!_ctx.isManaged() || !_conf |
| .isConnectionFactoryModeManaged())) |
| _conn.rollback(); |
| } catch (SQLException se) { |
| throw SQLExceptions.getStore(se, _dict); |
| } finally { |
| _active = false; |
| } |
| } |
| |
| @Override |
| public void retainConnection() { |
| connect(false); |
| _conn.setRetain(true); |
| } |
| |
| @Override |
| public void releaseConnection() { |
| if (_conn != null) |
| _conn.setRetain(false); |
| } |
| |
| @Override |
| public Object getClientConnection() { |
| return new ClientConnection(getConnection()); |
| } |
| |
| @Override |
| public Connection getConnection() { |
| connect(true); |
| return _conn; |
| } |
| |
| protected DataSource getDataSource() { |
| return _ds; |
| } |
| |
| @Override |
| public boolean exists(OpenJPAStateManager sm, Object context) { |
| // add where conditions on base class to avoid joins if subclass |
| // doesn't use oid as identifier |
| ClassMapping mapping = (ClassMapping) sm.getMetaData(); |
| return exists(mapping, sm.getObjectId(), context); |
| } |
| |
| @Override |
| public boolean isCached(List<Object> oids, BitSet edata) { |
| // JDBCStoreManager doesn't store oids in memory. |
| return false; |
| } |
| |
| private boolean exists(ClassMapping mapping, Object oid, Object context) { |
| // add where conditions on base class to avoid joins if subclass |
| // doesn't use oid as identifier |
| Select sel = _sql.newSelect(); |
| while (mapping.getJoinablePCSuperclassMapping() != null) |
| mapping = mapping.getJoinablePCSuperclassMapping(); |
| |
| sel.wherePrimaryKey(oid, mapping, this); |
| if (_log.isTraceEnabled()) { |
| _log.trace("exists: oid="+oid+" "+mapping.getDescribedType()); |
| } |
| try { |
| return sel.getCount(this) != 0; |
| } catch (SQLException se) { |
| throw SQLExceptions.getStore(se, _dict); |
| } |
| } |
| |
| @Override |
| public boolean syncVersion(OpenJPAStateManager sm, Object context) { |
| ClassMapping mapping = (ClassMapping) sm.getMetaData(); |
| try { |
| return mapping.getVersion().checkVersion(sm, this, true); |
| } catch (SQLException se) { |
| throw SQLExceptions.getStore(se, _dict, getReadLockLevel()); |
| } |
| } |
| |
| private int getReadLockLevel() { |
| JDBCFetchConfiguration fetch = getFetchConfiguration(); |
| if (fetch != null) { |
| return fetch.getReadLockLevel(); |
| } |
| return -1; |
| } |
| |
| @Override |
| public int compareVersion(OpenJPAStateManager state, Object v1, Object v2) { |
| ClassMapping mapping = (ClassMapping) state.getMetaData(); |
| return mapping.getVersion().compareVersion(v1, v2); |
| } |
| |
| @Override |
| public boolean initialize(OpenJPAStateManager sm, PCState state, |
| FetchConfiguration fetch, Object context) { |
| ConnectionInfo info = (ConnectionInfo) context; |
| try { |
| return initializeState(sm, state, (JDBCFetchConfiguration) fetch, |
| info); |
| } catch (ClassNotFoundException cnfe) { |
| throw new UserException(cnfe); |
| } catch (SQLException se) { |
| throw SQLExceptions.getStore(se, Exceptions.toString(sm.getPersistenceCapable()), |
| _dict, fetch.getReadLockLevel()); |
| } |
| } |
| |
| /** |
| * Initialize a newly-loaded instance. |
| */ |
| protected boolean initializeState(OpenJPAStateManager sm, PCState state, |
| JDBCFetchConfiguration fetch, ConnectionInfo info) |
| throws ClassNotFoundException, SQLException { |
| Object oid = sm.getObjectId(); |
| ClassMapping mapping = (ClassMapping) sm.getMetaData(); |
| Result res = null; |
| try { |
| if (info != null && info.result != null) { |
| res = info.result; |
| info.sm = sm; |
| if (info.mapping == null) |
| info.mapping = mapping; |
| mapping = info.mapping; |
| } else if (oid instanceof OpenJPAId |
| && !((OpenJPAId) oid).hasSubclasses()) { |
| Boolean custom = customLoad(sm, mapping, state, fetch); |
| if (custom != null) |
| return custom; |
| res = getInitializeStateResult(sm, mapping, fetch, |
| Select.SUBS_EXACT); |
| if (res == null && !selectPrimaryKey(sm, mapping, fetch)) |
| return false; |
| if (isEmptyResult(res)) |
| return false; |
| } else { |
| ClassMapping[] mappings = mapping. |
| getIndependentAssignableMappings(); |
| if (mappings.length == 1) { |
| mapping = mappings[0]; |
| Boolean custom = customLoad(sm, mapping, state, fetch); |
| if (custom != null) |
| return custom; |
| res = getInitializeStateResult(sm, mapping, fetch, |
| Select.SUBS_ANY_JOINABLE); |
| if (res == null && !selectPrimaryKey(sm, mapping, fetch)) |
| return false; |
| } else |
| res = getInitializeStateUnionResult(sm, mapping, mappings, |
| fetch); |
| if (isEmptyResult(res)) |
| return false; |
| } |
| |
| // figure out what type of object this is; the state manager |
| // only guarantees to provide a base class |
| Class<?> type; |
| if ((type = getType(res, mapping)) == null) { |
| if (res.getBaseMapping() != null) |
| mapping = res.getBaseMapping(); |
| res.startDataRequest(mapping.getDiscriminator()); |
| try { |
| type = mapping.getDiscriminator().getClass(this, mapping, |
| res); |
| } finally { |
| res.endDataRequest(); |
| } |
| } |
| |
| // initialize the state manager; this may change the mapping |
| // and the object id instance if the type as determined |
| // from the indicator is a subclass of expected type |
| sm.initialize(type, state); |
| |
| if (info != null && info.result != null) { |
| FieldMapping mappedByFieldMapping = info.result. |
| getMappedByFieldMapping(); |
| Object mappedByObject = info.result.getMappedByValue(); |
| if (mappedByFieldMapping != null && mappedByObject != null) |
| if (mappedByObject instanceof OpenJPAId && |
| mapping.getExtraFieldDataIndex(mappedByFieldMapping.getIndex()) != -1) { |
| // The inverse relation can not be set since |
| // we are eagerly loading this sm for |
| // a sm owner that is still in the process of |
| // initializing itself. |
| // Remember owner oid by setIntermediate(). |
| // The inverse relation is set later by |
| // setInverseRelation() when the sm owner is fully |
| // initialized. |
| int index = mappedByFieldMapping.getIndex(); |
| if (sm.getLoaded().get(index)) { |
| sm.setImplData(index, mappedByObject); |
| } else { |
| sm.setIntermediate(index, mappedByObject); |
| } |
| } else { |
| setMappedBy(sm, mappedByFieldMapping, mappedByObject); |
| } |
| } |
| // load the selected mappings into the given state manager |
| if (res != null) { |
| // re-get the mapping in case the instance was a subclass |
| mapping = (ClassMapping) sm.getMetaData(); |
| load(mapping, sm, fetch, res); |
| getVersion(mapping, sm, res); |
| setInverseRelation(sm, mapping, res); |
| } |
| return true; |
| } finally { |
| if (res != null && (info == null || res != info.result)) |
| res.close(); |
| } |
| } |
| |
| private void setInverseRelation(OpenJPAStateManager owner, |
| ClassMapping mapping, Result res) { |
| FieldMapping[] fms = mapping.getFieldMappings(); |
| |
| // At this point, the owner is fully initialized. |
| // Check if the owner has eagerly loaded ToMany relations. |
| for (int i = 0; i < fms.length; i++) { |
| if (res.getEager(fms[i]) != null) { |
| if (!fms[i].getElement().isTypePC()) { |
| continue; |
| } |
| Object coll = owner.fetchObject(fms[i].getIndex()); |
| if (coll instanceof Map) |
| coll = ((Map<?,?>)coll).values(); |
| if (coll instanceof Collection<?> && |
| ((Collection<?>) coll).size() > 0) { |
| // Found eagerly loaded collection. |
| // Publisher (1) <==> (M) Magazine |
| // publisher has a EAGER OneToMany relation |
| // magazine has a EAGER or LAZY ManyToOne publisher |
| // For each member (Magazine) in the collection, |
| // set its inverse relation (Publisher). |
| for (Iterator<?> itr = ((Collection<?>) coll).iterator(); |
| itr.hasNext();) { |
| PersistenceCapable pc = (PersistenceCapable) itr.next(); |
| if (pc == null) { |
| continue; |
| } |
| OpenJPAStateManager sm = (OpenJPAStateManager) pc.pcGetStateManager(); |
| ClassMapping cm = |
| (ClassMapping) _conf.getMetaDataRepositoryInstance().getCachedMetaData(pc.getClass()); |
| FieldMapping[] fmd = cm.getFieldMappings(); |
| for (int j = 0; j < fmd.length; j++) { |
| // don't check the oids for basic fields. |
| if (fmd[j].isTypePC()) { |
| Object oid = sm.getIntermediate(fmd[j].getIndex()); |
| // if oid was setIntermediate() previously and it is the same as the owner,generate |
| // then set the inverse relation |
| if (oid != null && oid.equals(owner.getObjectId())) { |
| sm.storeObject(fmd[j].getIndex(), owner.getPersistenceCapable()); |
| break; |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| protected void setMappedBy(OpenJPAStateManager sm, |
| FieldMapping mappedByFieldMapping, Object mappedByObject) { |
| ClassMapping mapping = (ClassMapping) sm.getMetaData(); |
| FieldMapping[] fms = mapping.getFieldMappings(); |
| for (int i = 0; i < fms.length; i++) { |
| if (fms[i] == mappedByFieldMapping) { |
| sm.storeObject(fms[i].getIndex(), mappedByObject); |
| return; |
| } |
| } |
| } |
| |
| /** |
| * This method is to provide override for non-JDBC or JDBC-like |
| * implementation of getting version from the result set. |
| */ |
| protected void getVersion(ClassMapping mapping, OpenJPAStateManager sm, |
| Result res) throws SQLException { |
| mapping.getVersion().afterLoad(sm, this); |
| } |
| |
| /** |
| * This method is to provide override for non-JDBC or JDBC-like |
| * implementation of checking whether the result set is empty or not. |
| */ |
| protected boolean isEmptyResult(Result res) throws SQLException { |
| if (res != null && !res.next()) |
| return true; |
| return false; |
| } |
| |
| /** |
| * This method is to provide override for non-JDBC or JDBC-like |
| * implementation of getting type from the result set. |
| */ |
| protected Class<?> getType(Result res, ClassMapping mapping){ |
| if (res == null) |
| return mapping.getDescribedType(); |
| return null; |
| } |
| |
| /** |
| * Allow the mapping to custom load data. Return null if the mapping |
| * does not use custom loading. |
| */ |
| private Boolean customLoad(OpenJPAStateManager sm, ClassMapping mapping, |
| PCState state, JDBCFetchConfiguration fetch) |
| throws ClassNotFoundException, SQLException { |
| // check to see if the mapping takes care of initialization |
| if (!mapping.customLoad(sm, this, state, fetch)) |
| return null; |
| if (sm.getManagedInstance() != null) { |
| mapping.getVersion().afterLoad(sm, this); |
| return Boolean.TRUE; |
| } |
| return Boolean.FALSE; |
| } |
| |
| /** |
| * Select the data for the given instance and return the result. Return |
| * null if there is no data in the current fetch groups to select. |
| */ |
| private Result getInitializeStateResult(OpenJPAStateManager sm, |
| ClassMapping mapping, JDBCFetchConfiguration fetch, int subs) |
| throws SQLException { |
| FinderQueryImpl fq = getFinder(mapping, fetch); |
| if (fq != null) |
| return fq.execute(sm, this, fetch); |
| Select sel = _sql.newSelect(); |
| if (!select(sel, mapping, subs, sm, null, fetch, |
| EagerFetchModes.EAGER_JOIN, true, false)) |
| return null; |
| sel.wherePrimaryKey(sm.getObjectId(), mapping, this); |
| sel.setExpectedResultCount(1, false); |
| if (_log.isTraceEnabled()) { |
| _log.trace("getInitializeStateResult: oid="+sm.getObjectId()+" "+mapping.getDescribedType()); |
| } |
| Result result = sel.execute(this, fetch); |
| cacheFinder(mapping, sel, fetch); |
| return result; |
| } |
| |
| /** |
| * Select a union of the data for the given instance from possible concrete |
| * mappings and return the result. |
| */ |
| private Result getInitializeStateUnionResult(final OpenJPAStateManager sm, |
| ClassMapping mapping, final ClassMapping[] mappings, |
| final JDBCFetchConfiguration fetch) throws SQLException { |
| FinderQueryImpl fq = getFinder(mapping, fetch); |
| if (fq != null) |
| return fq.execute(sm, this, fetch); |
| final JDBCStoreManager store = this; |
| final int eager = Math.min(fetch.getEagerFetchMode(), |
| EagerFetchModes.EAGER_JOIN); |
| |
| Union union = _sql.newUnion(mappings.length); |
| union.setExpectedResultCount(1, false); |
| if (fetch.getSubclassFetchMode(mapping) != EagerFetchModes.EAGER_JOIN) |
| union.abortUnion(); |
| union.select(new Union.Selector() { |
| @Override |
| public void select(Select sel, int i) { |
| sel.select(mappings[i], Select.SUBS_ANY_JOINABLE, store, fetch, |
| eager); |
| sel.wherePrimaryKey(sm.getObjectId(), mappings[i], store); |
| } |
| }); |
| Result result = union.execute(this, fetch); |
| cacheFinder(mapping, union, fetch); |
| return result; |
| } |
| |
| /** |
| * Select primary key data to make sure the given instance exists, locking |
| * if needed. |
| */ |
| private boolean selectPrimaryKey(OpenJPAStateManager sm, |
| ClassMapping mapping, JDBCFetchConfiguration fetch) |
| throws SQLException { |
| // select pks from base class record to ensure it exists and lock |
| // it if needed |
| ClassMapping base = mapping; |
| while (base.getJoinablePCSuperclassMapping() != null) |
| base = base.getJoinablePCSuperclassMapping(); |
| |
| Select sel = _sql.newSelect(); |
| sel.select(base.getPrimaryKeyColumns()); |
| sel.wherePrimaryKey(sm.getObjectId(), base, this); |
| if (_log.isTraceEnabled()) { |
| _log.trace("selectPrimaryKey: oid="+sm.getObjectId()+" "+mapping.getDescribedType()); |
| } |
| Result exists = sel.execute(this, fetch); |
| try { |
| if (isEmptyResult(exists)) |
| return false; |
| |
| // record locked? |
| if (_active && _lm != null && exists.isLocking()) |
| _lm.loadedForUpdate(sm); |
| return true; |
| } finally { |
| exists.close(); |
| } |
| } |
| |
| @Override |
| public boolean load(OpenJPAStateManager sm, BitSet fields, |
| FetchConfiguration fetch, int lockLevel, Object context) { |
| JDBCFetchConfiguration jfetch = (JDBCFetchConfiguration) fetch; |
| |
| // get a connection, or reuse current one |
| ConnectionInfo info = (ConnectionInfo) context; |
| Result res = null; |
| if (info != null) { |
| // if initialize() fails to load required fields, then this method |
| // is called; make sure not to try to use the given result if it's |
| // the same one we just failed to completely initialize() with |
| if (info.sm != sm) |
| res = info.result; |
| info.sm = null; |
| } |
| try { |
| // if there's an existing result, load all we can from it |
| ClassMapping mapping = (ClassMapping) sm.getMetaData(); |
| if (res != null) { |
| load(mapping, sm, jfetch, res); |
| removeLoadedFields(sm, fields); |
| } |
| |
| // if the instance is hollow and there's a customized |
| // get by id method, use it |
| if (sm.getLoaded().length() == 0 |
| && mapping.customLoad(sm, this, null, jfetch)) |
| removeLoadedFields(sm, fields); |
| |
| |
| //### select is kind of a big object, and in some cases we don't |
| //### use it... would it be worth it to have a small shell select |
| //### object that only creates a real select when actually used? |
| //### Delayed proxy specific optimization: If the only fields that |
| //### need to be loaded are delayed proxies, building the select is |
| //### not necessary. |
| |
| if (!isDelayedLoadOnly(sm, fields, mapping)) { |
| Select sel = _sql.newSelect(); |
| if (select(sel, mapping, Select.SUBS_EXACT, sm, fields, jfetch, |
| EagerFetchModes.EAGER_JOIN, true, false)) { |
| sel.wherePrimaryKey(sm.getObjectId(), mapping, this); |
| if (_log.isTraceEnabled()) { |
| _log.trace("load: "+mapping.getDescribedType()+" oid: "+sm.getObjectId()); |
| } |
| res = sel.execute(this, jfetch, lockLevel); |
| try { |
| if (isEmptyResult(res)) |
| return false; |
| load(mapping, sm, jfetch, res); |
| } finally { |
| res.close(); |
| } |
| } |
| } |
| |
| // now allow the fields to load themselves individually too |
| FieldMapping[] fms = mapping.getFieldMappings(); |
| for (int i = 0; i < fms.length; i++) |
| if (fields.get(i) && (!sm.getLoaded().get(i) || sm.isDelayed(i))) { |
| if (_log.isTraceEnabled()) { |
| _log.trace("load field: '"+ fms[i].getName() + "' for oid="+sm.getObjectId() |
| +" "+mapping.getDescribedType()); |
| } |
| fms[i].load(sm, this, jfetch.traverseJDBC(fms[i])); |
| } |
| mapping.getVersion().afterLoad(sm, this); |
| return true; |
| } catch (ClassNotFoundException cnfe) { |
| throw new StoreException(cnfe); |
| } catch (SQLException se) { |
| throw SQLExceptions.getStore(se, _dict, lockLevel); |
| } |
| } |
| |
| private boolean isDelayedLoadOnly(OpenJPAStateManager sm, BitSet fields, ClassMapping mapping) { |
| if (!sm.getContext().getConfiguration().getProxyManagerInstance().getDelayCollectionLoading() |
| || fields.isEmpty()) { |
| return false; |
| } |
| FieldMapping[] fms = mapping.getFieldMappings(); |
| for (int i = 0; i < fms.length; i++) { |
| if (fields.get(i)) { |
| if (!(fms[i].isDelayCapable() && (!sm.getLoaded().get(i) || sm.isDelayed(i)))) { |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Return a list formed by removing all loaded fields from the given one. |
| */ |
| private void removeLoadedFields(OpenJPAStateManager sm, BitSet fields) { |
| for (int i = 0, len = fields.length(); i < len; i++) |
| if (fields.get(i) && sm.getLoaded().get(i)) |
| fields.clear(i); |
| } |
| |
| @Override |
| public Collection loadAll(Collection sms, PCState state, int load, |
| FetchConfiguration fetch, Object context) { |
| return ImplHelper.loadAll(sms, this, state, load, fetch, context); |
| } |
| |
| @Override |
| public void beforeStateChange(OpenJPAStateManager sm, PCState fromState, |
| PCState toState) { |
| } |
| |
| @Override |
| public Collection flush(Collection sms) { |
| try { |
| if (_conn != null && _conn.getInnermostDelegate().isReadOnly()) |
| _conn.setReadOnly(false); |
| } catch (SQLException e) { |
| } |
| return _conf.getUpdateManagerInstance().flush(sms, this); |
| } |
| |
| @Override |
| public boolean cancelAll() { |
| // note that this method does not lock the context, since |
| // we want to allow a different thread to be able to cancel the |
| // outstanding statement on a different context |
| |
| List<Statement> stmnts; |
| synchronized (_stmnts) { |
| if (_stmnts.isEmpty()) |
| return false; |
| stmnts = new ArrayList<>(_stmnts); |
| } |
| |
| try { |
| for (Iterator<Statement> itr = stmnts.iterator(); itr.hasNext();) |
| ((Statement) itr.next()).cancel(); |
| return true; |
| } catch (SQLException se) { |
| throw SQLExceptions.getStore(se, _dict); |
| } |
| } |
| |
| @Override |
| public boolean assignObjectId(OpenJPAStateManager sm, boolean preFlush) { |
| ClassMetaData meta = sm.getMetaData(); |
| if (meta.getIdentityType() == ClassMetaData.ID_APPLICATION) |
| return ApplicationIds.assign(sm, this, preFlush); |
| |
| // datastore identity |
| Object val = ImplHelper.generateIdentityValue(_ctx, meta, |
| JavaTypes.LONG); |
| if (val == null && meta.getIdentityStrategy() != ValueStrategies.NATIVE) |
| return false; |
| if (val == null) |
| val = getDataStoreIdSequence(meta).next(_ctx, meta); |
| sm.setObjectId(newDataStoreId(val, meta)); |
| return true; |
| } |
| |
| @Override |
| public boolean assignField(OpenJPAStateManager sm, int field, |
| boolean preFlush) { |
| FieldMetaData fmd = sm.getMetaData().getField(field); |
| Object val = ImplHelper.generateFieldValue(_ctx, fmd); |
| if (val == null) |
| return false; |
| sm.store(field, val); |
| return true; |
| } |
| |
| @Override |
| public Class<?> getManagedType(Object oid) { |
| if (oid instanceof Id) |
| return ((Id) oid).getType(); |
| return null; |
| } |
| |
| @Override |
| public Class<?> getDataStoreIdType(ClassMetaData meta) { |
| return Id.class; |
| } |
| |
| @Override |
| public Object copyDataStoreId(Object oid, ClassMetaData meta) { |
| Id id = (Id) oid; |
| return new Id(meta.getDescribedType(), id.getId(), id.hasSubclasses()); |
| } |
| |
| @Override |
| public Object newDataStoreId(Object val, ClassMetaData meta) { |
| return Id.newInstance(meta.getDescribedType(), val); |
| } |
| |
| @Override |
| public Id newDataStoreId(long id, ClassMapping mapping, boolean subs) { |
| return new Id(mapping.getDescribedType(), id, subs); |
| } |
| |
| @Override |
| public ResultObjectProvider executeExtent(ClassMetaData meta, |
| final boolean subclasses, FetchConfiguration fetch) { |
| ClassMapping mapping = (ClassMapping) meta; |
| final ClassMapping[] mappings; |
| if (subclasses) |
| mappings = mapping.getIndependentAssignableMappings(); |
| else |
| mappings = new ClassMapping[] { mapping }; |
| |
| ResultObjectProvider[] rops = null; |
| final JDBCFetchConfiguration jfetch = (JDBCFetchConfiguration) fetch; |
| if (jfetch.getSubclassFetchMode(mapping) != EagerFetchModes.EAGER_JOIN) |
| rops = new ResultObjectProvider[mappings.length]; |
| |
| try { |
| // check for custom loads |
| ResultObjectProvider rop; |
| for (int i = 0; i < mappings.length; i++) { |
| rop = mappings[i].customLoad(this, subclasses, jfetch, 0, |
| Long.MAX_VALUE); |
| if (rop != null) { |
| if (rops == null) |
| rops = new ResultObjectProvider[mappings.length]; |
| rops[i] = rop; |
| } |
| } |
| |
| // if we're selecting independent mappings separately or have |
| // custom loads, do individual selects for each class |
| rop = null; |
| if (rops != null) { |
| for (int i = 0; i < mappings.length; i++) { |
| if (rops[i] != null) |
| continue; |
| |
| Select sel = _sql.newSelect(); |
| sel.setLRS(true); |
| if (_log.isTraceEnabled()) { |
| _log.trace("executeExtent: "+mappings[i].getDescribedType()); |
| sel.logEagerRelations(); |
| } |
| BitSet paged = selectExtent(sel, mappings[i], jfetch, |
| subclasses); |
| if (paged == null) |
| rops[i] = new InstanceResultObjectProvider(sel, |
| mappings[i], this, jfetch); |
| else |
| rops[i] = new PagingResultObjectProvider(sel, |
| mappings[i], this, jfetch, paged, Long.MAX_VALUE); |
| } |
| if (rops.length == 1) |
| return rops[0]; |
| return new MergedResultObjectProvider(rops); |
| } |
| |
| // perform a union on all independent classes |
| Union union = _sql.newUnion(mappings.length); |
| union.setLRS(true); |
| final BitSet[] paged = new BitSet[mappings.length]; |
| union.select(new Union.Selector() { |
| @Override |
| public void select(Select sel, int idx) { |
| paged[idx] = selectExtent(sel, mappings[idx], jfetch, |
| subclasses); |
| } |
| }); |
| |
| // using paging rop if any union element has paged fields |
| for (int i = 0; i < paged.length; i++) { |
| if (paged[i] != null) |
| return new PagingResultObjectProvider(union, mappings, |
| JDBCStoreManager.this, jfetch, paged, Long.MAX_VALUE); |
| } |
| return new InstanceResultObjectProvider(union, mappings[0], this, |
| jfetch); |
| } catch (SQLException se) { |
| throw SQLExceptions.getStore(se, _dict); |
| } |
| } |
| |
| /** |
| * Select the given mapping for use in an extent, returning paged fields. |
| */ |
| private BitSet selectExtent(Select sel, ClassMapping mapping, |
| JDBCFetchConfiguration fetch, boolean subclasses) { |
| int subs = (subclasses) ? Select.SUBS_JOINABLE : Select.SUBS_NONE; |
| // decide between paging and standard iteration |
| BitSet paged = PagingResultObjectProvider.getPagedFields(sel, mapping, |
| this, fetch, EagerFetchModes.EAGER_PARALLEL, |
| Long.MAX_VALUE); |
| if (paged == null) |
| sel.selectIdentifier(mapping, subs, this, fetch, |
| EagerFetchModes.EAGER_PARALLEL); |
| else |
| sel.selectIdentifier(mapping, subs, this, fetch, |
| EagerFetchModes.EAGER_JOIN); |
| return paged; |
| } |
| |
| private StoreQuery newStoreQuery(String language) { |
| ExpressionParser ep = QueryLanguages.parserForLanguage(language); |
| if (ep != null) { |
| return new JDBCStoreQuery(this, ep); |
| } |
| if (QueryLanguages.LANG_SQL.equals(language)) { |
| return new SQLStoreQuery(this); |
| } |
| if (QueryLanguages.LANG_PREPARED_SQL.equals(language)) { |
| return new PreparedSQLStoreQuery(this); |
| } |
| if (QueryLanguages.LANG_STORED_PROC.equals(language)) { |
| return new StoredProcedureQuery(this); |
| } |
| return null; |
| } |
| |
| @Override |
| public StoreQuery newQuery(String language) { |
| StoreQuery sq = newStoreQuery(language); |
| if (sq == null || QueryLanguages.parserForLanguage(language) == null) { |
| return sq; |
| } |
| |
| QueryCache queryCache = _ctx.getConfiguration().getDataCacheManagerInstance().getSystemQueryCache(); |
| if (queryCache == null) { |
| return sq; |
| } |
| |
| return new QueryCacheStoreQuery(sq, queryCache); |
| } |
| |
| @Override |
| public FetchConfiguration newFetchConfiguration() { |
| return new JDBCFetchConfigurationImpl(); |
| } |
| |
| @Override |
| public Seq getDataStoreIdSequence(ClassMetaData meta) { |
| if (meta.getIdentityStrategy() == ValueStrategies.NATIVE |
| || meta.getIdentityStrategy() == ValueStrategies.NONE) |
| return _conf.getSequenceInstance(); |
| return null; |
| } |
| |
| @Override |
| public Seq getValueSequence(FieldMetaData fmd) { |
| return null; |
| } |
| |
| @Override |
| public void close() { |
| if (_conn != null) |
| _conn.free(); |
| } |
| |
| ///////////// |
| // Utilities |
| ///////////// |
| |
| /** |
| * Connect to the db. |
| */ |
| private void connect(boolean ref) { |
| _ctx.lock(); |
| try { |
| // connect if the connection is currently null, or if |
| // the connection has been closed out from under us |
| if (_conn == null) |
| _conn = connectInternal(); |
| if (ref) |
| _conn.ref(); |
| } catch (SQLException se) { |
| throw SQLExceptions.getStore(se, _dict); |
| } finally { |
| _ctx.unlock(); |
| } |
| } |
| |
| /** |
| * Connect to the database. This method is separated out so that it |
| * can be overridden. |
| */ |
| protected RefCountConnection connectInternal() throws SQLException { |
| return new RefCountConnection(_ds.getConnection()); |
| } |
| |
| @Override |
| public Connection getNewConnection() { |
| try { |
| return _ds.getConnection(); |
| } catch (SQLException e) { |
| throw SQLExceptions.getStore(e, _dict); |
| } |
| } |
| |
| /** |
| * Find the object with the given oid. |
| */ |
| @Override |
| public Object find(Object oid, ValueMapping vm, |
| JDBCFetchConfiguration fetch) { |
| if (oid == null) |
| return null; |
| if (_log.isTraceEnabled()) { |
| ClassMapping declaredTypeMapping = vm.getDeclaredTypeMapping(); |
| Class<?> describedType = (declaredTypeMapping != null) ? declaredTypeMapping.getDescribedType() : null; |
| _log.trace("find: oid="+oid+", describedType="+describedType); |
| } |
| Object pc = _ctx.find(oid, fetch, null, null, 0); |
| if (pc == null && vm != null) { |
| OrphanedKeyAction action = _conf.getOrphanedKeyActionInstance(); |
| pc = action.orphan(oid, null, vm); |
| } |
| return pc; |
| } |
| |
| /** |
| * Load the object in the current row of the given result. |
| */ |
| public Object load(ClassMapping mapping, JDBCFetchConfiguration fetch, |
| BitSet exclude, Result result) throws SQLException { |
| if (!mapping.isMapped()) |
| throw new InvalidStateException(_loc.get("virtual-mapping", |
| mapping)); |
| |
| // get the object id for the row; base class selects pk columns |
| ClassMapping base = mapping; |
| while (base.getJoinablePCSuperclassMapping() != null) |
| base = base.getJoinablePCSuperclassMapping(); |
| Object oid = base.getObjectId(this, result, null, true, null); |
| if (oid == null) |
| return null; |
| |
| ConnectionInfo info = new ConnectionInfo(); |
| info.result = result; |
| info.mapping = mapping; |
| |
| // if inverse relation is known, exclude loading during find |
| exclude = excludeInverseRelation(mapping, info, exclude); |
| return _ctx.find(oid, fetch, exclude, info, 0); |
| } |
| |
| private BitSet excludeInverseRelation(ClassMapping mapping, |
| ConnectionInfo info, BitSet exclude) { |
| FieldMapping inverse = info.result.getMappedByFieldMapping(); |
| if (inverse != null) { |
| FieldMapping[] fms = mapping.getDefinedFieldMappings(); |
| if (exclude == null) |
| exclude = new BitSet(fms.length); |
| for (int i = 0; i < fms.length; i++) { |
| if (fms[i] == inverse) { |
| exclude.set(fms[i].getIndex()); |
| break; |
| } |
| } |
| } |
| return exclude; |
| } |
| |
| /** |
| * Load the given state manager with data from the result set. Only |
| * mappings originally selected will be loaded. |
| */ |
| private void load(ClassMapping mapping, OpenJPAStateManager sm, |
| JDBCFetchConfiguration fetch, Result res) throws SQLException { |
| FieldMapping eagerToMany = load(mapping, sm, fetch, res, null); |
| if (eagerToMany != null) { |
| if (_log.isTraceEnabled()) { |
| _log.trace("Loading eager toMany: "+eagerToMany.getName()+" for "+mapping); |
| } |
| eagerToMany.loadEagerJoin(sm, this, fetch.traverseJDBC(eagerToMany), |
| res); |
| } |
| if (_active && _lm != null && res.isLocking()) |
| _lm.loadedForUpdate(sm); |
| } |
| |
| /** |
| * Load the fields of the given mapping. Return any to-many eager field |
| * without loading it. |
| */ |
| private FieldMapping load(ClassMapping mapping, OpenJPAStateManager sm, |
| JDBCFetchConfiguration fetch, Result res, FieldMapping eagerToMany) |
| throws SQLException { |
| if (mapping.customLoad(sm, this, fetch, res)) |
| return eagerToMany; |
| |
| // load superclass data; base class loads version |
| ClassMapping parent = mapping.getJoinablePCSuperclassMapping(); |
| if (parent != null) |
| eagerToMany = load(parent, sm, fetch, res, eagerToMany); |
| else if (sm.getVersion() == null) |
| mapping.getVersion().load(sm, this, res); |
| |
| // load unloaded fields |
| FieldMapping[] fms = mapping.getDefinedFieldMappings(); |
| Object eres, processed; |
| for (int i = 0; i < fms.length; i++) { |
| if (fms[i].isPrimaryKey() || sm.getLoaded().get(fms[i].getIndex())) |
| continue; |
| |
| // check for eager result, and if not present do standard load |
| eres = res.getEager(fms[i]); |
| res.startDataRequest(fms[i]); |
| try { |
| if (eres == res) { |
| if (eagerToMany == null && fms[i].isEagerSelectToMany()) |
| eagerToMany = fms[i]; |
| else |
| fms[i].loadEagerJoin(sm, this, |
| fetch.traverseJDBC(fms[i]), res); |
| } else if (eres != null) { |
| processed = fms[i].loadEagerParallel(sm, this, |
| fetch.traverseJDBC(fms[i]), eres); |
| if (processed != eres) |
| res.putEager(fms[i], processed); |
| } else { |
| fms[i].load(sm, this, fetch.traverseJDBC(fms[i]), res); |
| } |
| } finally { |
| res.endDataRequest(); |
| } |
| } |
| return eagerToMany; |
| } |
| |
| /** |
| * For implementation use only. |
| * Return a select for the proper mappings. Return null if no select is |
| * needed. The method is designed to be complementary to the load methods. |
| * |
| * @param sel select to build on |
| * @param mapping the mapping for the base type to select for |
| * @param subs whether the select might include subclasses of the |
| * given mapping |
| * @param sm state manager if an instance is being loaded or |
| * initialized, else null |
| * @param fields if a state manager is being loaded, the set of |
| * fields that must be loaded in order, else null |
| * @param fetch the fetch configuration; used if no specific fields |
| * must be loaded, and used when selecting relations |
| * @param eager eager fetch mode to use |
| * @param ident whether to select primary key columns as distinct |
| * identifiers |
| * @param outer whether we're outer-joining to this type |
| * @return true if the select is required, false otherwise |
| */ |
| public boolean select(Select sel, ClassMapping mapping, int subs, |
| OpenJPAStateManager sm, BitSet fields, JDBCFetchConfiguration fetch, |
| int eager, boolean ident, boolean outer) { |
| // add class conditions so that they're cloned for any batched selects |
| boolean joinedSupers = false; |
| if(needClassCondition(mapping, subs, sm)) { |
| joinedSupers = getJoinedSupers(sel, mapping, subs, outer); |
| } |
| |
| // create all our eager selects so that those fields are reserved |
| // and cannot be reused during the actual eager select process, |
| // preventing infinite recursion |
| eager = Math.min(eager, fetch.getEagerFetchMode()); |
| FieldMapping eagerToMany = createEagerSelects(sel, mapping, sm, fields, |
| fetch, eager); |
| |
| // select all base class mappings; do this after batching so that |
| // the joins needed by these selects don't get in the WHERE clause |
| // of the batched selects |
| int seld = selectBaseMappings(sel, mapping, mapping, sm, fields, |
| fetch, eager, eagerToMany, ident, joinedSupers); |
| |
| // select eager to-many relations last because during load they |
| // advance the result set and could exhaust it, so no other mappings |
| // can load afterwords |
| if (eagerToMany != null) |
| eagerToMany.selectEagerJoin(sel, sm, this, |
| fetch.traverseJDBC(eagerToMany), eager); |
| |
| // optionally select subclass mappings |
| if (subs == Select.SUBS_JOINABLE || subs == Select.SUBS_ANY_JOINABLE) |
| selectSubclassMappings(sel, mapping, sm, fetch); |
| if (sm != null) |
| sel.setDistinct(false); |
| return seld > 0; |
| } |
| |
| private boolean getJoinedSupers(Select sel, ClassMapping mapping, int subs, boolean outer) { |
| loadSubclasses(mapping); |
| Joins joins = (outer) ? sel.newOuterJoins() : null; |
| boolean includeSubs = false; |
| if (subs == Select.SUBS_JOINABLE || subs == Select.SUBS_ANY_JOINABLE) { |
| includeSubs = true; |
| } |
| return mapping.getDiscriminator().addClassConditions(sel, includeSubs, joins); |
| } |
| |
| private boolean needClassCondition(ClassMapping mapping, int subs, |
| OpenJPAStateManager sm) { |
| boolean retVal = false; |
| if(sm == null || sm.getPCState() == PCState.TRANSIENT) { |
| if(subs == Select.SUBS_JOINABLE || subs == Select.SUBS_NONE) { |
| retVal = true; |
| } |
| else { |
| if (mapping.getDiscriminator() != null |
| && SuperclassDiscriminatorStrategy.class.isInstance(mapping.getDiscriminator().getStrategy()) |
| && mapping.getMappingRepository().getConfiguration().getCompatibilityInstance() |
| .getSuperclassDiscriminatorStrategyByDefault()) { |
| retVal = true; |
| } |
| } |
| } |
| return retVal; |
| } |
| |
| /** |
| * Mark the fields of this mapping as reserved so that eager fetches can't |
| * get into infinite recursive situations. |
| */ |
| private FieldMapping createEagerSelects(Select sel, ClassMapping mapping, |
| OpenJPAStateManager sm, BitSet fields, JDBCFetchConfiguration fetch, |
| int eager) { |
| if (mapping == null || eager == EagerFetchModes.EAGER_NONE) |
| return null; |
| |
| FieldMapping eagerToMany = createEagerSelects(sel, |
| mapping.getJoinablePCSuperclassMapping(), sm, fields, fetch, eager); |
| |
| FieldMapping[] fms = mapping.getDefinedFieldMappings(); |
| boolean inEagerJoin = sel.hasEagerJoin(false); |
| int sels; |
| int jtype; |
| int mode; |
| for (int i = 0; i < fms.length; i++) { |
| mode = fms[i].getEagerFetchMode(); |
| if (mode == EagerFetchModes.EAGER_NONE) |
| continue; |
| if (!requiresSelect(fms[i], sm, fields, fetch)) |
| continue; |
| |
| // try to select with join first |
| jtype = (fms[i].getNullValue() == FieldMetaData.NULL_EXCEPTION) |
| ? Select.EAGER_INNER : Select.EAGER_OUTER; |
| if (mode != EagerFetchModes.EAGER_PARALLEL && !fms[i].isEagerSelectToMany() |
| && fms[i].supportsSelect(sel, jtype, sm, this, fetch) > 0 |
| && sel.eagerClone(fms[i], jtype, false, 1) != null) |
| continue; |
| |
| boolean hasJoin = fetch.hasJoin(fms[i].getFullName(false)); |
| |
| // if the field declares a preferred select mode of join or does not |
| // have a preferred mode and we're doing a by-id lookup, try |
| // to use a to-many join also. currently we limit eager |
| // outer joins to non-LRS, non-ranged selects that don't already |
| // have an eager to-many join |
| if ((hasJoin || mode == EagerFetchModes.EAGER_JOIN |
| || (mode == FetchConfiguration.DEFAULT && sm != null)) |
| && fms[i].isEagerSelectToMany() |
| && !inEagerJoin |
| && !sel.hasEagerJoin(true) |
| && (!sel.getAutoDistinct() || (!sel.isLRS() |
| && sel.getStartIndex() == 0 |
| && sel.getEndIndex() == Long.MAX_VALUE)) |
| && fms[i].supportsSelect(sel, jtype, sm, this, fetch) > 0) { |
| if (sel.eagerClone(fms[i], jtype, true, 1) != null) |
| eagerToMany = fms[i]; |
| else |
| continue; |
| } |
| |
| // finally, try parallel |
| if (eager == EagerFetchModes.EAGER_PARALLEL |
| && (sels = fms[i].supportsSelect(sel, Select.EAGER_PARALLEL, sm, |
| this, fetch)) != 0) |
| sel.eagerClone(fms[i], Select.EAGER_PARALLEL, |
| fms[i].isEagerSelectToMany(), sels); |
| } |
| return eagerToMany; |
| } |
| |
| /** |
| * Determine if the given field needs to be selected. |
| */ |
| private static boolean requiresSelect(FieldMapping fm, |
| OpenJPAStateManager sm, BitSet fields, JDBCFetchConfiguration fetch) { |
| if (fields != null) |
| return fields.get(fm.getIndex()); |
| if (sm != null && sm.getPCState() != PCState.TRANSIENT |
| && sm.getLoaded().get(fm.getIndex())) |
| return false; |
| return fetch.requiresFetch(fm) == FetchConfiguration.FETCH_LOAD; |
| } |
| |
| /** |
| * Select the field mappings of the given class and all its superclasses. |
| * |
| * @param sel the select to use |
| * @param mapping the most-derived type to select for |
| * @param orig the original mapping type selected |
| * @param sm the instance being selected for, or null if none |
| * @param fields the fields to load |
| * @param fetch fetch configuration to use for loading relations |
| * @param eager the eager fetch mode to use |
| * @param joined whether the class has already been joined down to |
| * its base class |
| * @return > 0 if the select is required, 0 if data was |
| * selected but is not required, and < 0 if nothing was selected |
| */ |
| private int selectBaseMappings(Select sel, ClassMapping mapping, |
| ClassMapping orig, OpenJPAStateManager sm, BitSet fields, |
| JDBCFetchConfiguration fetch, int eager, FieldMapping eagerToMany, |
| boolean ident, boolean joined) { |
| ClassMapping parent = mapping.getJoinablePCSuperclassMapping(); |
| if (parent == null && !mapping.isMapped()) |
| throw new InvalidStateException(_loc.get("virtual-mapping", mapping. |
| getDescribedType())); |
| |
| int seld = -1; |
| int pseld = -1; |
| |
| // base class selects pks, etc |
| if (parent == null) { |
| // if no instance, select pks |
| if (sm == null) { |
| if (ident) |
| sel.selectIdentifier(mapping.getPrimaryKeyColumns()); |
| else |
| sel.select(mapping.getPrimaryKeyColumns()); |
| seld = 1; |
| } |
| |
| // if no instance or not initialized and not exact oid, select type |
| if ((sm == null || (sm.getPCState() == PCState.TRANSIENT |
| && (!(sm.getObjectId() instanceof OpenJPAId) |
| || ((OpenJPAId) sm.getObjectId()).hasSubclasses()))) |
| && mapping.getDiscriminator().select(sel, orig)) |
| seld = 1; |
| |
| // if no instance or no version, select version |
| if ((sm == null || sm.getVersion() == null) |
| && mapping.getVersion().select(sel, orig)) |
| seld = 1; |
| } else { |
| // recurse on parent |
| pseld = selectBaseMappings(sel, parent, orig, sm, fields, |
| fetch, eager, eagerToMany, ident, joined); |
| } |
| |
| // select the mappings in the given fields set, or based on fetch |
| // configuration if no fields given |
| FieldMapping[] fms = mapping.getDefinedFieldMappings(); |
| SelectExecutor esel; |
| int fseld; |
| for (int i = 0; i < fms.length; i++) { |
| // skip eager to-many select; we do that separately in calling |
| // method |
| if (fms[i] == eagerToMany) |
| continue; |
| |
| // check for eager select |
| esel = sel.getEager(fms[i]); |
| if (esel != null) { |
| if (esel == sel) |
| fms[i].selectEagerJoin(sel, sm, this, |
| fetch.traverseJDBC(fms[i]), eager); |
| else |
| fms[i].selectEagerParallel(esel, sm, this, |
| fetch.traverseJDBC(fms[i]), eager); |
| seld = Math.max(0, seld); |
| } else if (requiresSelect(fms[i], sm, fields, fetch)) { |
| fseld = fms[i].select(sel, sm, this, |
| fetch.traverseJDBC(fms[i]), eager); |
| seld = Math.max(fseld, seld); |
| } else if (optSelect(fms[i], sel, sm, fetch)) { |
| fseld = fms[i].select(sel, sm, this, |
| fetch.traverseJDBC(fms[i]), EagerFetchModes.EAGER_NONE); |
| |
| // don't upgrade seld to > 0 based on these fields, since |
| // they're not in the calculated field set |
| if (fseld >= 0 && seld < 0) |
| seld = 0; |
| } |
| } |
| |
| // in certain circumstances force join to superclass table to avoid |
| // SQL generation error. |
| if ( eagerToMany != null && pseld < 0 && !joined |
| && parent != null ) { |
| FieldMapping[] pfms = parent.getDefinedFieldMappings(); |
| for (int i = 0; i < pfms.length; i++) { |
| if (pfms[i] == eagerToMany ) { |
| pseld = 0; |
| break; |
| } |
| } |
| } |
| |
| // join to parent table if the parent / any ancestors have selected |
| // anything |
| if (!joined && pseld >= 0 && parent.getTable() != mapping.getTable()) |
| sel.where(mapping.joinSuperclass(sel.newJoins(), false)); |
| |
| // return the highest value |
| return Math.max(pseld, seld); |
| } |
| |
| /** |
| * 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) { |
| boolean dfg = |
| fetch.getIgnoreDfgForFkSelect() || |
| !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; |
| } |
| |
| /** |
| * Select field mappings that match the given fetch configuration for |
| * subclasses of the given type. |
| * |
| * @param sel the select to use |
| * @param mapping the type whose subclasses to select |
| * @param sm the instance being selected for, or null if none |
| * @param fetch the fetch configuration |
| */ |
| private void selectSubclassMappings(Select sel, ClassMapping mapping, |
| OpenJPAStateManager sm, JDBCFetchConfiguration fetch) { |
| loadSubclasses(mapping); |
| ClassMapping[] subMappings = mapping.getJoinablePCSubclassMappings(); |
| if (subMappings.length == 0) |
| return; |
| |
| // select all subclass mappings that match the fetch configuration |
| // and whose table is in the list of those selected so far; this |
| // way we select the max possible without selecting any tables that |
| // aren't present in all possible query matches; a special case |
| // is made for mappings that use 2-part selects that aren't |
| // explicitly *not* in the default so that they can get their |
| // primary table data |
| FieldMapping[] fms; |
| boolean joined; |
| boolean canJoin = _dict.joinSyntax != JoinSyntaxes.SYNTAX_TRADITIONAL |
| && fetch.getSubclassFetchMode(mapping) != EagerFetchModes.EAGER_NONE; |
| for (int i = 0; i < subMappings.length; i++) { |
| if (!subMappings[i].supportsEagerSelect(sel, sm, this, mapping, |
| fetch)) |
| continue; |
| |
| // initialize so that if we can't join, we pretend we already have |
| joined = !canJoin; |
| fms = subMappings[i].getDefinedFieldMappings(); |
| for (int j = 0; j < fms.length; j++) { |
| // make sure in one of configured fetch groups |
| if (fetch.requiresFetch(fms[j]) != FetchConfiguration.FETCH_LOAD |
| && ((!fms[j].isInDefaultFetchGroup() |
| && fms[j].isDefaultFetchGroupExplicit()) |
| || fms[j].supportsSelect(sel, Select.TYPE_TWO_PART, sm, this, |
| fetch) <= 0)) |
| continue; |
| |
| // if we can join to the subclass, do so; much better chance |
| // that the field will be able to select itself without joins |
| if (!joined) { |
| // mark joined whether or not we join, so we don't have to |
| // test conditions again for this subclass |
| joined = true; |
| sel.where(joinSubclass(sel, mapping, subMappings[i], null)); |
| } |
| |
| // if can select with tables already selected, do it |
| if (fms[j].supportsSelect(sel, Select.TYPE_JOINLESS, sm, this, fetch) > 0) |
| fms[j].select(sel, null, this, fetch.traverseJDBC(fms[j]), EagerFetchModes.EAGER_NONE); |
| } |
| } |
| } |
| |
| /** |
| * Helper method to join from class to its subclass. Recursive to allow |
| * for multiple hops, starting from the base class. |
| */ |
| private static Joins joinSubclass(Select sel, ClassMapping base, |
| ClassMapping sub, Joins joins) { |
| if (sub == base || sub.getTable() == base.getTable() |
| || sel.isSelected(sub.getTable())) |
| return null; |
| |
| // recurse first so we go least->most derived |
| ClassMapping sup = sub.getJoinablePCSuperclassMapping(); |
| joins = joinSubclass(sel, base, sup, joins); |
| if (joins == null) |
| joins = sel.newJoins(); |
| return sub.joinSuperclass(joins, true); |
| } |
| |
| /** |
| * Makes sure all subclasses of the given type are loaded in the JVM. |
| * This is usually done automatically. |
| */ |
| @Override |
| public void loadSubclasses(ClassMapping mapping) { |
| Discriminator dsc = mapping.getDiscriminator(); |
| if (dsc.getSubclassesLoaded()) |
| return; |
| |
| // if the subclass list is set, no need to load subs |
| if (mapping.getRepository().getPersistentTypeNames(false, |
| _ctx.getClassLoader()) != null) { |
| dsc.setSubclassesLoaded(true); |
| return; |
| } |
| |
| try { |
| dsc.loadSubclasses(this); |
| } catch (ClassNotFoundException cnfe) { |
| throw new StoreException(cnfe); |
| } catch (SQLException se) { |
| throw SQLExceptions.getStore(se, _dict); |
| } |
| } |
| |
| /** |
| * Make the statement a candidate for cancellation. |
| */ |
| private void beforeExecuteStatement(Statement stmnt) { |
| _stmnts.add(stmnt); |
| } |
| |
| /** |
| * Remove the statement from the cancellable set. |
| */ |
| private void afterExecuteStatement(Statement stmnt) { |
| _stmnts.remove(stmnt); |
| } |
| |
| FinderQueryImpl getFinder(ClassMapping mapping, FetchConfiguration fetch) { |
| FinderCache cache = getFinderCache(); |
| return cache == null |
| ? null : (FinderQueryImpl)cache.get(mapping, fetch); |
| } |
| |
| boolean cacheFinder(ClassMapping mapping, SelectExecutor select, |
| FetchConfiguration fetch) { |
| FinderCache cache = getFinderCache(); |
| return cache != null && cache.cache(mapping, select, fetch) != null; |
| } |
| |
| FinderCache getFinderCache() { |
| return (((BrokerImpl)getContext()).getCacheFinderQuery()) |
| ? getConfiguration().getFinderCacheInstance() : null; |
| } |
| |
| /** |
| * Connection returned to client code. Makes sure its wrapped connection ref count is decremented on finalize. |
| */ |
| public static class ClientConnection extends |
| DelegatingConnection { |
| |
| private boolean _closed = false; |
| |
| public ClientConnection(Connection conn) { |
| super(conn); |
| } |
| |
| @Override |
| public void close() throws SQLException { |
| _closed = true; |
| super.close(); |
| } |
| |
| @Override |
| protected void finalize() throws SQLException { |
| if (!_closed) |
| close(); |
| } |
| } |
| |
| /** |
| * Connection wrapper that keeps an internal ref count so that it knows |
| * when to really close. |
| */ |
| protected class RefCountConnection extends DelegatingConnection { |
| |
| private boolean _retain = false; |
| private int _refs = 0; |
| private boolean _freed = false; |
| |
| public RefCountConnection(Connection conn) { |
| super(conn); |
| } |
| |
| public boolean getRetain() { |
| return _retain; |
| } |
| |
| public void setRetain(boolean retain) { |
| if (_retain && !retain && _refs <= 0) |
| free(); |
| _retain = retain; |
| } |
| |
| public void ref() { |
| // don't have to lock; called from connect(), which is locked |
| _refs++; |
| } |
| |
| @Override |
| public void close() throws SQLException { |
| // lock at broker level to avoid deadlocks |
| _ctx.lock(); |
| try { |
| _refs--; |
| if (_refs <= 0 && !_retain) |
| free(); |
| } finally { |
| _ctx.unlock(); |
| } |
| } |
| |
| /* |
| * Non-thread-safe method. Ensure protection in the call path... |
| */ |
| public void free() { |
| // ensure that we do not close the underlying connection |
| // multiple times; this could happen if someone (e.g., an |
| // Extent) holds a RefConnection, and then closes it (e.g., in |
| // the finalizer) after the StoreManager has already been closed. |
| if (_freed) |
| return; |
| |
| try { |
| getDelegate().close(); |
| } catch (SQLException se) { |
| } |
| _freed = true; |
| _conn = null; |
| } |
| |
| @Override |
| protected Statement createStatement(boolean wrap) throws SQLException { |
| return getCancelStatement(super.createStatement(false), |
| RefCountConnection.this); |
| } |
| |
| @Override |
| protected Statement createStatement(int rsType, int rsConcur, |
| boolean wrap) throws SQLException { |
| return getCancelStatement(super.createStatement(rsType, rsConcur, |
| false), RefCountConnection.this); |
| |
| } |
| |
| @Override |
| protected PreparedStatement prepareStatement(String sql, boolean wrap) |
| throws SQLException { |
| return getCancelPreparedStatement(super.prepareStatement(sql, |
| false), RefCountConnection.this); |
| } |
| |
| @Override |
| protected PreparedStatement prepareStatement(String sql, int rsType, |
| int rsConcur, boolean wrap) throws SQLException { |
| return getCancelPreparedStatement(super.prepareStatement(sql, |
| rsType, rsConcur, false), RefCountConnection.this); |
| } |
| } |
| |
| private PreparedStatement getCancelPreparedStatement(PreparedStatement stmnt, Connection conn) { |
| synchronized (_cancelPreparedStatementsPool) { |
| if (!_cancelPreparedStatementsPool.isEmpty()) { |
| CancelPreparedStatement res = _cancelPreparedStatementsPool.remove(0); |
| res.initialize(stmnt, conn); |
| return res; |
| } |
| } |
| return new CancelPreparedStatement(stmnt, conn); |
| } |
| |
| private Statement getCancelStatement(Statement stmnt, Connection conn) { |
| synchronized (_cancelStatementPool) { |
| if (!_cancelStatementPool.isEmpty()) { |
| CancelStatement res = _cancelStatementPool.remove(0); |
| res.initialize(stmnt, conn); |
| return res; |
| } |
| } |
| |
| return new CancelStatement(stmnt, conn); |
| } |
| |
| /** |
| * Statement type that adds and removes itself from the set of active |
| * statements so that it can be canceled. |
| */ |
| private class CancelStatement extends DelegatingStatement { |
| |
| public CancelStatement(Statement stmnt, Connection conn) { |
| super(stmnt, conn); |
| } |
| |
| @Override |
| public int executeUpdate(String sql) throws SQLException { |
| beforeExecuteStatement(this); |
| try { |
| return super.executeUpdate(sql); |
| } finally { |
| afterExecuteStatement(this); |
| } |
| } |
| |
| @Override |
| protected ResultSet executeQuery(String sql, boolean wrap) |
| throws SQLException { |
| beforeExecuteStatement(this); |
| try { |
| return super.executeQuery(sql, wrap); |
| } finally { |
| afterExecuteStatement(this); |
| } |
| } |
| |
| @Override |
| public boolean execute(String sql) throws SQLException { |
| beforeExecuteStatement(this); |
| try { |
| return super.execute(sql); |
| } finally { |
| afterExecuteStatement(this); |
| } |
| } |
| |
| @Override |
| public int executeUpdate(String sql, int i) throws SQLException { |
| beforeExecuteStatement(this); |
| try { |
| return super.executeUpdate(sql, i); |
| } finally { |
| afterExecuteStatement(this); |
| } |
| } |
| |
| @Override |
| public int executeUpdate(String sql, int[] ia) throws SQLException { |
| beforeExecuteStatement(this); |
| try { |
| return super.executeUpdate(sql, ia); |
| } finally { |
| afterExecuteStatement(this); |
| } |
| } |
| |
| @Override |
| public int executeUpdate(String sql, String[] sa) throws SQLException { |
| beforeExecuteStatement(this); |
| try { |
| return super.executeUpdate(sql, sa); |
| } finally { |
| afterExecuteStatement(this); |
| } |
| } |
| |
| @Override |
| public boolean execute(String sql, int i) throws SQLException { |
| beforeExecuteStatement(this); |
| try { |
| return super.execute(sql, i); |
| } finally { |
| afterExecuteStatement(this); |
| } |
| } |
| |
| @Override |
| public boolean execute(String sql, int[] ia) throws SQLException { |
| beforeExecuteStatement(this); |
| try { |
| return super.execute(sql, ia); |
| } finally { |
| afterExecuteStatement(this); |
| } |
| } |
| |
| @Override |
| public boolean execute(String sql, String[] sa) throws SQLException { |
| beforeExecuteStatement(this); |
| try { |
| return super.execute(sql, sa); |
| } finally { |
| afterExecuteStatement(this); |
| } |
| } |
| |
| @Override |
| public void close() throws SQLException { |
| super.close(); |
| synchronized (_cancelStatementPool) { |
| _cancelStatementPool.add(this); |
| } |
| } |
| } |
| |
| /** |
| * Statement type that adds and removes itself from the set of active |
| * statements so that it can be canceled. |
| */ |
| private class CancelPreparedStatement extends |
| DelegatingPreparedStatement { |
| |
| public CancelPreparedStatement(PreparedStatement stmnt, |
| Connection conn) { |
| super(stmnt, conn); |
| } |
| |
| @Override |
| public int executeUpdate() throws SQLException { |
| beforeExecuteStatement(this); |
| try { |
| return super.executeUpdate(); |
| } finally { |
| afterExecuteStatement(this); |
| } |
| } |
| |
| @Override |
| protected ResultSet executeQuery(boolean wrap) throws SQLException { |
| beforeExecuteStatement(this); |
| try { |
| return super.executeQuery(wrap); |
| } finally { |
| afterExecuteStatement(this); |
| } |
| } |
| |
| @Override |
| public int[] executeBatch() throws SQLException { |
| beforeExecuteStatement(this); |
| try { |
| return super.executeBatch(); |
| } finally { |
| afterExecuteStatement(this); |
| } |
| } |
| |
| @Override |
| public boolean execute() throws SQLException { |
| beforeExecuteStatement(this); |
| try { |
| return super.execute(); |
| } finally { |
| afterExecuteStatement(this); |
| } |
| } |
| |
| @Override |
| public boolean execute(String s) throws SQLException { |
| beforeExecuteStatement(this); |
| try { |
| return super.execute(s); |
| } finally { |
| afterExecuteStatement(this); |
| } |
| } |
| |
| @Override |
| public int executeUpdate(String s) throws SQLException { |
| beforeExecuteStatement(this); |
| try { |
| return super.executeUpdate(s); |
| } finally { |
| afterExecuteStatement(this); |
| } |
| } |
| |
| @Override |
| public int executeUpdate(String s, int i) throws SQLException { |
| beforeExecuteStatement(this); |
| try { |
| return super.executeUpdate(s, i); |
| } finally { |
| afterExecuteStatement(this); |
| } |
| } |
| |
| @Override |
| public int executeUpdate(String s, int[] ia) throws SQLException { |
| beforeExecuteStatement(this); |
| try { |
| return super.executeUpdate(s, ia); |
| } finally { |
| afterExecuteStatement(this); |
| } |
| } |
| |
| @Override |
| public int executeUpdate(String s, String[] sa) throws SQLException { |
| beforeExecuteStatement(this); |
| try { |
| return super.executeUpdate(s, sa); |
| } finally { |
| afterExecuteStatement(this); |
| } |
| } |
| |
| @Override |
| public boolean execute(String s, int i) throws SQLException { |
| beforeExecuteStatement(this); |
| try { |
| return super.execute(s, i); |
| } finally { |
| afterExecuteStatement(this); |
| } |
| } |
| |
| @Override |
| public boolean execute(String s, int[] ia) throws SQLException { |
| beforeExecuteStatement(this); |
| try { |
| return super.execute(s, ia); |
| } finally { |
| afterExecuteStatement(this); |
| } |
| } |
| |
| @Override |
| public boolean execute(String s, String[] sa) throws SQLException { |
| beforeExecuteStatement(this); |
| try { |
| return super.execute(s, sa); |
| } finally { |
| afterExecuteStatement(this); |
| } |
| } |
| |
| @Override |
| public void close() throws SQLException { |
| super.close(); |
| synchronized (_cancelPreparedStatementsPool) { |
| _cancelPreparedStatementsPool.add(this); |
| } |
| } |
| } |
| } |
| |