Java tutorial
/***************************************************************** * 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.cayenne.access; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import org.apache.cayenne.CayenneRuntimeException; import org.apache.cayenne.DataChannel; import org.apache.cayenne.DataChannelFilter; import org.apache.cayenne.DataChannelFilterChain; import org.apache.cayenne.DataChannelSyncCallbackAction; import org.apache.cayenne.ObjectContext; import org.apache.cayenne.QueryResponse; import org.apache.cayenne.access.jdbc.BatchQueryBuilderFactory; import org.apache.cayenne.cache.NestedQueryCache; import org.apache.cayenne.cache.QueryCache; import org.apache.cayenne.configuration.Constants; import org.apache.cayenne.configuration.ObjectContextFactory; import org.apache.cayenne.di.BeforeScopeEnd; import org.apache.cayenne.di.Inject; import org.apache.cayenne.event.EventManager; import org.apache.cayenne.graph.CompoundDiff; import org.apache.cayenne.graph.GraphDiff; import org.apache.cayenne.log.JdbcEventLogger; import org.apache.cayenne.map.DataMap; import org.apache.cayenne.map.EntityResolver; import org.apache.cayenne.map.EntitySorter; import org.apache.cayenne.query.Query; import org.apache.cayenne.query.QueryChain; import org.apache.cayenne.util.ToStringBuilder; import org.apache.commons.collections.Transformer; /** * DataDomain performs query routing functions in Cayenne. DataDomain creates single data * source abstraction hiding multiple physical data sources from the user. When a child * DataContext sends a query to the DataDomain, it is transparently routed to an * appropriate DataNode. */ public class DataDomain implements QueryEngine, DataChannel { public static final String SHARED_CACHE_ENABLED_PROPERTY = "cayenne.DataDomain.sharedCache"; public static final boolean SHARED_CACHE_ENABLED_DEFAULT = true; public static final String VALIDATING_OBJECTS_ON_COMMIT_PROPERTY = "cayenne.DataDomain.validatingObjectsOnCommit"; public static final boolean VALIDATING_OBJECTS_ON_COMMIT_DEFAULT = true; public static final String USING_EXTERNAL_TRANSACTIONS_PROPERTY = "cayenne.DataDomain.usingExternalTransactions"; public static final boolean USING_EXTERNAL_TRANSACTIONS_DEFAULT = false; /** * @since 3.1 */ @Inject protected JdbcEventLogger jdbcEventLogger; /** * @since 3.1 */ protected int maxIdQualifierSize; /** * @since 3.1 */ protected List<DataChannelFilter> filters; protected Map<String, DataNode> nodes; protected Map<String, DataNode> nodesByDataMapName; protected DataNode defaultNode; protected Map<String, String> properties; protected EntityResolver entityResolver; protected DataRowStore sharedSnapshotCache; protected TransactionDelegate transactionDelegate; protected String name; protected QueryCache queryCache; // these are initialized from properties... protected boolean sharedCacheEnabled; protected boolean validatingObjectsOnCommit; protected boolean usingExternalTransactions; /** * @since 1.2 */ protected EventManager eventManager; /** * @since 1.2 */ protected EntitySorter entitySorter; protected boolean stopped; /** * Factory for creating QueryBuilders. Might be null, then default one will be used. * Server-only. * * @deprecated since 3.1 BatchQueryBuilderFactory is injected into JdbcAdapter. */ @Deprecated private BatchQueryBuilderFactory queryBuilderFactory; /** * Creates a DataDomain and assigns it a name. */ public DataDomain(String name) { init(name); resetProperties(); } /** * Creates new DataDomain. * * @param name DataDomain name. Domain can be located using its name in the * Configuration object. * @param properties A Map containing domain configuration properties. */ public DataDomain(String name, Map properties) { init(name); initWithProperties(properties); } private void init(String name) { this.filters = new CopyOnWriteArrayList<DataChannelFilter>(); this.nodesByDataMapName = new ConcurrentHashMap<String, DataNode>(); this.nodes = new ConcurrentHashMap<String, DataNode>(); // properties are read-only, so no need for concurrent map, or any specific map // for that matter this.properties = Collections.EMPTY_MAP; setName(name); } /** * Checks that Domain is not stopped. Throws DomainStoppedException otherwise. * * @since 3.0 */ protected void checkStopped() throws DomainStoppedException { if (stopped) { throw new DomainStoppedException( "Domain " + name + " was shutdown and can no longer be used to access the database"); } } /** * @since 3.1 */ public EntitySorter getEntitySorter() { return entitySorter; } /** * @since 3.1 */ public void setEntitySorter(EntitySorter entitySorter) { this.entitySorter = entitySorter; } /** * @since 1.1 */ protected void resetProperties() { properties = Collections.EMPTY_MAP; sharedCacheEnabled = SHARED_CACHE_ENABLED_DEFAULT; validatingObjectsOnCommit = VALIDATING_OBJECTS_ON_COMMIT_DEFAULT; usingExternalTransactions = USING_EXTERNAL_TRANSACTIONS_DEFAULT; } /** * Reinitializes domain state with a new set of properties. * * @since 1.1 */ public void initWithProperties(Map<String, String> properties) { // clone properties to ensure that it is read-only internally properties = properties != null ? new HashMap<String, String>(properties) : Collections.EMPTY_MAP; String sharedCacheEnabled = properties.get(SHARED_CACHE_ENABLED_PROPERTY); String validatingObjectsOnCommit = properties.get(VALIDATING_OBJECTS_ON_COMMIT_PROPERTY); String usingExternalTransactions = properties.get(USING_EXTERNAL_TRANSACTIONS_PROPERTY); // init ivars from properties this.sharedCacheEnabled = (sharedCacheEnabled != null) ? "true".equalsIgnoreCase(sharedCacheEnabled) : SHARED_CACHE_ENABLED_DEFAULT; this.validatingObjectsOnCommit = (validatingObjectsOnCommit != null) ? "true".equalsIgnoreCase(validatingObjectsOnCommit) : VALIDATING_OBJECTS_ON_COMMIT_DEFAULT; this.usingExternalTransactions = (usingExternalTransactions != null) ? "true".equalsIgnoreCase(usingExternalTransactions) : USING_EXTERNAL_TRANSACTIONS_DEFAULT; this.properties = properties; } /** * Returns EventManager used by this DataDomain. * * @since 1.2 */ public EventManager getEventManager() { return eventManager; } /** * Sets EventManager used by this DataDomain. * * @since 1.2 */ public void setEventManager(EventManager eventManager) { this.eventManager = eventManager; if (sharedSnapshotCache != null) { sharedSnapshotCache.setEventManager(eventManager); } } /** * Returns "name" property value. */ public String getName() { return name; } /** * Sets "name" property to a new value. */ public synchronized void setName(String name) { this.name = name; if (sharedSnapshotCache != null) { this.sharedSnapshotCache.setName(name); } } /** * Returns <code>true</code> if DataContexts produced by this DataDomain are using * shared DataRowStore. Returns <code>false</code> if each DataContext would work with * its own DataRowStore. Note that this setting can be overwritten per DataContext. * See {@link #createDataContext(boolean)}. */ public boolean isSharedCacheEnabled() { return sharedCacheEnabled; } public void setSharedCacheEnabled(boolean sharedCacheEnabled) { this.sharedCacheEnabled = sharedCacheEnabled; } /** * Returns whether child DataContexts default behavior is to perform object validation * before commit is executed. * * @since 1.1 */ public boolean isValidatingObjectsOnCommit() { return validatingObjectsOnCommit; } /** * Sets the property defining whether child DataContexts should perform object * validation before commit is executed. * * @since 1.1 */ public void setValidatingObjectsOnCommit(boolean flag) { this.validatingObjectsOnCommit = flag; } /** * Returns whether this DataDomain should internally commit all transactions, or let * container do that. * * @since 1.1 */ public boolean isUsingExternalTransactions() { return usingExternalTransactions; } /** * Sets a property defining whether this DataDomain should internally commit all * transactions, or let container do that. * * @since 1.1 */ public void setUsingExternalTransactions(boolean flag) { this.usingExternalTransactions = flag; } /** * @since 1.1 * @return a Map of properties for this DataDomain. */ public Map<String, String> getProperties() { return properties; } /** * @since 1.1 * @return TransactionDelegate associated with this DataDomain, or null if no delegate * exist. */ public TransactionDelegate getTransactionDelegate() { return transactionDelegate; } /** * Initializes TransactionDelegate used by all DataContexts associated with this * DataDomain. * * @since 1.1 */ public void setTransactionDelegate(TransactionDelegate transactionDelegate) { this.transactionDelegate = transactionDelegate; } /** * Returns snapshots cache for this DataDomain, lazily initializing it on the first * call if 'sharedCacheEnabled' flag is true. */ public DataRowStore getSharedSnapshotCache() { if (sharedSnapshotCache == null && sharedCacheEnabled) { this.sharedSnapshotCache = nonNullSharedSnapshotCache(); } return sharedSnapshotCache; } /** * Returns a guaranteed non-null shared snapshot cache regardless of the * 'sharedCacheEnabled' flag setting. */ synchronized DataRowStore nonNullSharedSnapshotCache() { if (sharedSnapshotCache == null) { this.sharedSnapshotCache = new DataRowStore(name, properties, eventManager); } return sharedSnapshotCache; } /** * Shuts down the previous cache instance, sets cache to the new DataSowStore instance * and updates two properties of the new DataSowStore: name and eventManager. */ public synchronized void setSharedSnapshotCache(DataRowStore snapshotCache) { if (this.sharedSnapshotCache != snapshotCache) { if (this.sharedSnapshotCache != null) { this.sharedSnapshotCache.shutdown(); } this.sharedSnapshotCache = snapshotCache; if (snapshotCache != null) { snapshotCache.setEventManager(getEventManager()); snapshotCache.setName(getName()); } } } /** * Registers new DataMap with this domain. * * @deprecated since 3.1 use a more consistently named {@link #addDataMap(DataMap)}. */ @Deprecated public void addMap(DataMap map) { addDataMap(map); } public void addDataMap(DataMap dataMap) { getEntityResolver().addDataMap(dataMap); refreshEntitySorter(); } /** * Returns DataMap matching <code>name</code> parameter. * * @deprecated since 3.1 use a more consistently named {@link #getDataMap(String)}. */ public DataMap getMap(String mapName) { return getEntityResolver().getDataMap(mapName); } /** * @since 3.1 */ public DataMap getDataMap(String mapName) { return getEntityResolver().getDataMap(mapName); } /** * Removes named DataMap from this DataDomain and any underlying DataNodes that * include it. * * @deprecated since 3.1 use a more consistently named {@link #removeDataMap(String)}. */ @Deprecated public void removeMap(String mapName) { removeDataMap(mapName); } /** * Removes named DataMap from this DataDomain and any underlying DataNodes that * include it. * * @since 3.1 */ public void removeDataMap(String mapName) { DataMap map = getDataMap(mapName); if (map == null) { return; } // remove from data nodes for (DataNode node : nodes.values()) { node.removeDataMap(mapName); } nodesByDataMapName.remove(mapName); // remove from EntityResolver getEntityResolver().removeDataMap(map); refreshEntitySorter(); } /** * Removes a DataNode from DataDomain. Any maps previously associated with this node * within domain will still be kept around, however they wan't be mapped to any node. */ public void removeDataNode(String nodeName) { DataNode removed = nodes.remove(nodeName); if (removed != null) { removed.setEntityResolver(null); Iterator<DataNode> it = nodesByDataMapName.values().iterator(); while (it.hasNext()) { if (it.next() == removed) { it.remove(); } } } } /** * Returns a collection of registered DataMaps. */ public Collection<DataMap> getDataMaps() { return getEntityResolver().getDataMaps(); } /** * Returns an unmodifiable collection of DataNodes associated with this domain. */ public Collection<DataNode> getDataNodes() { return Collections.unmodifiableCollection(nodes.values()); } /** * Closes all data nodes, removes them from the list of available nodes. * * @deprecated since 3.1 unused and unneeded */ @Deprecated public void reset() { nodes.clear(); nodesByDataMapName.clear(); if (entityResolver != null) { entityResolver.clearCache(); entityResolver = null; } } /** * Clears the list of internal DataMaps. * * @deprecated since 3.1 unused and unneeded */ @Deprecated public void clearDataMaps() { getEntityResolver().setDataMaps(Collections.EMPTY_LIST); } /** * Adds new DataNode. */ public void addNode(DataNode node) { // add node to name->node map nodes.put(node.getName(), node); node.setEntityResolver(getEntityResolver()); // add node to "ent name->node" map for (DataMap map : node.getDataMaps()) { addDataMap(map); nodesByDataMapName.put(map.getName(), node); } } /** * Creates and returns a new DataContext. If this DataDomain is configured to use * shared cache, returned DataContext will use shared cache as well. Otherwise a new * instance of DataRowStore will be used as its local cache. * * @deprecated since 3.1 as context creation is done via {@link ObjectContextFactory} * and injection. */ @Deprecated public DataContext createDataContext() { return createDataContext(isSharedCacheEnabled()); } /** * Creates a new DataContext. * * @param useSharedCache determines whether resulting DataContext should use shared * vs. local cache. This setting overrides default behavior configured for * this DataDomain via {@link #SHARED_CACHE_ENABLED_PROPERTY}. * @since 1.1 * @deprecated since 3.1 as context creation is done via {@link ObjectContextFactory} * and injection. */ @Deprecated public DataContext createDataContext(boolean useSharedCache) { // for new dataRowStores use the same name for all stores // it makes it easier to track the event subject DataRowStore snapshotCache = (useSharedCache) ? nonNullSharedSnapshotCache() : new DataRowStore(name, properties, eventManager); DataContext context = new DataContext(this, new ObjectStore(snapshotCache)); if (queryCache != null) { context.setQueryCache(new NestedQueryCache(queryCache)); } context.setValidatingObjectsOnCommit(isValidatingObjectsOnCommit()); return context; } /** * Creates and returns a new inactive transaction. Returned transaction is bound to * the current execution thread. * <p> * If there is a TransactionDelegate, adds the delegate to the newly created * Transaction. Behavior of the returned Transaction depends on * "usingInternalTransactions" property setting. * </p> * * @since 1.1 */ public Transaction createTransaction() { if (isUsingExternalTransactions()) { Transaction transaction = Transaction.externalTransaction(getTransactionDelegate()); transaction.setJdbcEventLogger(jdbcEventLogger); return transaction; } else { Transaction transaction = Transaction.internalTransaction(getTransactionDelegate()); transaction.setJdbcEventLogger(jdbcEventLogger); return transaction; } } /** * Returns registered DataNode whose name matches <code>name</code> parameter. * * @deprecated since 3.1, use a more consistently named {@link #getDataNode(String)}. */ @Deprecated public DataNode getNode(String nodeName) { return getDataNode(nodeName); } /** * Returns registered DataNode whose name matches <code>name</code> parameter. * * @since 3.1 */ public DataNode getDataNode(String nodeName) { return nodes.get(nodeName); } /** * Updates internal index of DataNodes stored by the entity name. * * @deprecated since 3.1 - unneeded and unused. */ @Deprecated public void reindexNodes() { nodesByDataMapName.clear(); for (DataNode node : getDataNodes()) { for (DataMap map : node.getDataMaps()) { addDataMap(map); nodesByDataMapName.put(map.getName(), node); } } } /** * Returns a DataNode that should handle queries for all entities in a DataMap. * * @since 1.1 */ public DataNode lookupDataNode(DataMap map) { DataNode node = nodesByDataMapName.get(map.getName()); if (node == null) { // see if one of the node states has changed, and the map is now linked... for (DataNode n : getDataNodes()) { for (DataMap m : n.getDataMaps()) { if (m == map) { nodesByDataMapName.put(map.getName(), n); node = n; break; } } if (node != null) { break; } } if (node == null) { if (defaultNode != null) { nodesByDataMapName.put(map.getName(), defaultNode); node = defaultNode; } else { throw new CayenneRuntimeException("No DataNode configured for DataMap '" + map.getName() + "' and no default DataNode set"); } } } return node; } /** * Sets EntityResolver. If not set explicitly, DataDomain creates a default * EntityResolver internally on demand. * * @since 1.1 */ public void setEntityResolver(EntityResolver entityResolver) { this.entityResolver = entityResolver; } // creates default entity resolver if there is none set yet private synchronized void createEntityResolver() { if (entityResolver == null) { // entity resolver will be self-indexing as we add all our maps // to it as they are added to the DataDomain entityResolver = new EntityResolver(); } } /** * Shutdowns all owned data nodes and marks this domain as stopped. */ @BeforeScopeEnd public void shutdown() { if (!stopped) { stopped = true; if (sharedSnapshotCache != null) { sharedSnapshotCache.shutdown(); } // deprecated - noop code for backwards compatibility as DataNode shutdown is // no longer needed for (DataNode node : getDataNodes()) { node.shutdown(); } } } /** * Routes queries to appropriate DataNodes for execution. */ public void performQueries(final Collection<? extends Query> queries, final OperationObserver callback) { runInTransaction(new Transformer() { public Object transform(Object input) { new DataDomainLegacyQueryAction(DataDomain.this, new QueryChain(queries), callback).execute(); return null; } }); } // ****** DataChannel methods: /** * Runs query returning generic QueryResponse. * * @since 1.2 */ public QueryResponse onQuery(final ObjectContext originatingContext, final Query query) { checkStopped(); return new DataDomainQueryFilterChain().onQuery(originatingContext, query); } QueryResponse onQueryNoFilters(final ObjectContext originatingContext, final Query query) { // transaction note: // we don't wrap this code in transaction to reduce transaction scope to // just the DB operation for better performance ... query action will // start a transaction itself when and if needed return new DataDomainQueryAction(originatingContext, DataDomain.this, query).execute(); } /** * Returns an EntityResolver that stores mapping information for this domain. */ public EntityResolver getEntityResolver() { if (entityResolver == null) { createEntityResolver(); } return entityResolver; } /** * Only handles commit-type synchronization, ignoring any other type. * * @since 1.2 */ public GraphDiff onSync(final ObjectContext originatingContext, final GraphDiff changes, int syncType) { checkStopped(); return new DataDomainSyncFilterChain().onSync(originatingContext, changes, syncType); } GraphDiff onSyncNoFilters(final ObjectContext originatingContext, final GraphDiff changes, int syncType) { DataChannelSyncCallbackAction callbackAction = DataChannelSyncCallbackAction.getCallbackAction( getEntityResolver().getCallbackRegistry(), originatingContext.getGraphManager(), changes, syncType); callbackAction.applyPreCommit(); GraphDiff result; switch (syncType) { case DataChannel.ROLLBACK_CASCADE_SYNC: result = onSyncRollback(originatingContext); break; // "cascade" and "no_cascade" are the same from the DataDomain // perspective, // including transaction handling logic case DataChannel.FLUSH_NOCASCADE_SYNC: case DataChannel.FLUSH_CASCADE_SYNC: result = (GraphDiff) runInTransaction(new Transformer() { public Object transform(Object input) { return onSyncFlush(originatingContext, changes); } }); break; default: throw new CayenneRuntimeException("Invalid synchronization type: " + syncType); } callbackAction.applyPostCommit(); return result; } GraphDiff onSyncRollback(ObjectContext originatingContext) { // if there is a transaction in progress, roll it back Transaction transaction = Transaction.getThreadTransaction(); if (transaction != null) { transaction.setRollbackOnly(); } return new CompoundDiff(); } GraphDiff onSyncFlush(ObjectContext originatingContext, GraphDiff childChanges) { if (!(originatingContext instanceof DataContext)) { throw new CayenneRuntimeException( "No support for committing ObjectContexts that are not DataContexts yet. " + "Unsupported context: " + originatingContext); } DataDomainFlushAction action = new DataDomainFlushAction(this); action.setJdbcEventLogger(jdbcEventLogger); return action.flush((DataContext) originatingContext, childChanges); } /** * Executes Transformer.transform() method in a transaction. Transaction policy is to * check for the thread transaction, and use it if one exists. If it doesn't, a new * transaction is created, with a scope limited to this method. */ // WARNING: (andrus) if we ever decide to make this method protected or public, we // need to change the signature to avoid API dependency on commons-collections Object runInTransaction(Transformer operation) { // user or container-managed or nested transaction if (Transaction.getThreadTransaction() != null) { return operation.transform(null); } // Cayenne-managed transaction Transaction transaction = createTransaction(); Transaction.bindThreadTransaction(transaction); try { // implicit begin.. Object result = operation.transform(null); transaction.commit(); return result; } catch (Exception ex) { transaction.setRollbackOnly(); // must rethrow if (ex instanceof CayenneRuntimeException) { throw (CayenneRuntimeException) ex; } else { throw new CayenneRuntimeException(ex); } } finally { Transaction.bindThreadTransaction(null); if (transaction.getStatus() == Transaction.STATUS_MARKED_ROLLEDBACK) { try { transaction.rollback(); } catch (Exception rollbackEx) { // although we don't expect an exception here, print the stack, as // there have been some Cayenne bugs already (CAY-557) that were // masked by this 'catch' clause. jdbcEventLogger.logQueryError(rollbackEx); } } } } @Override public String toString() { return new ToStringBuilder(this).append("name", name).toString(); } /** * Returns shared {@link QueryCache} used by this DataDomain. * * @since 3.0 */ public QueryCache getQueryCache() { return queryCache; } public void setQueryCache(QueryCache queryCache) { this.queryCache = queryCache; } /** * Sets factory for creating QueryBuilders * * @deprecated since 3.1 BatchQueryBuilderFactory is injected into JdbcAdapter. */ @Deprecated public void setQueryBuilderFactory(BatchQueryBuilderFactory queryBuilderFactory) { this.queryBuilderFactory = queryBuilderFactory; } /** * @return factory for creating QueryBuilders. Might be null * @deprecated since 3.1 BatchQueryBuilderFactory is injected into JdbcAdapter. */ @Deprecated public BatchQueryBuilderFactory getQueryBuilderFactory() { return queryBuilderFactory; } /** * @since 3.1 */ JdbcEventLogger getJdbcEventLogger() { return jdbcEventLogger; } void refreshEntitySorter() { if (entitySorter != null) { entitySorter.setEntityResolver(getEntityResolver()); } } /** * Returns an unmodifiable list of filters registered with this DataDomain. * <p> * Filter ordering note: filters are applied in reverse order of their occurrence in * the filter list. I.e. the last filter in the list called first in the chain. * * @since 3.1 */ public List<DataChannelFilter> getFilters() { return Collections.unmodifiableList(filters); } /** * Adds a new filter, calling its 'init' method. * * @since 3.1 */ public void addFilter(DataChannelFilter filter) { filter.init(this); filters.add(filter); } /** * Removes a filter from the filter chain. * * @since 3.1 */ public void removeFilter(DataChannelFilter filter) { filters.remove(filter); } abstract class DataDomainFilterChain implements DataChannelFilterChain { private int i; DataDomainFilterChain() { i = filters != null ? filters.size() : 0; } DataChannelFilter nextFilter() { // filters are ordered innermost to outermost i--; return i >= 0 ? filters.get(i) : null; } } final class DataDomainQueryFilterChain extends DataDomainFilterChain { public QueryResponse onQuery(ObjectContext originatingContext, Query query) { DataChannelFilter filter = nextFilter(); return (filter != null) ? filter.onQuery(originatingContext, query, this) : onQueryNoFilters(originatingContext, query); } public GraphDiff onSync(ObjectContext originatingContext, GraphDiff changes, int syncType) { throw new UnsupportedOperationException("It is illegal to call 'onSync' inside 'onQuery' chain"); } } final class DataDomainSyncFilterChain extends DataDomainFilterChain { public GraphDiff onSync(final ObjectContext originatingContext, final GraphDiff changes, int syncType) { DataChannelFilter filter = nextFilter(); return (filter != null) ? filter.onSync(originatingContext, changes, syncType, this) : onSyncNoFilters(originatingContext, changes, syncType); } public QueryResponse onQuery(ObjectContext originatingContext, Query query) { throw new UnsupportedOperationException("It is illegal to call 'onQuery' inside 'onSync' chain"); } } /** * An optional DataNode that is used for DataMaps that are not linked to a DataNode * explicitly. * * @since 3.1 */ public DataNode getDefaultNode() { return defaultNode; } /** * @since 3.1 */ public void setDefaultNode(DataNode defaultNode) { this.defaultNode = defaultNode; } /** * Returns a maximum number of object IDs to match in a single query for queries that * select objects based on collection of ObjectIds. This affects queries generated by * Cayenne when processing paginated queries and DISJOINT_BY_ID prefetches and is * intended to address database limitations on the size of SQL statements as well as * to cap memory use in Cayenne when generating such queries. The default is 10000. It * can be changed either by calling {@link #setMaxIdQualifierSize(int)} or changing * the value for property {@link Constants#SERVER_MAX_ID_QUALIFIER_SIZE_PROPERTY}. * * @since 3.1 */ public int getMaxIdQualifierSize() { return maxIdQualifierSize; } /** * @since 3.1 */ public void setMaxIdQualifierSize(int maxIdQualifierSize) { this.maxIdQualifierSize = maxIdQualifierSize; } }