diff --git a/src/main/java/org/datanucleus/store/rdbms/scostore/JoinListStore.java b/src/main/java/org/datanucleus/store/rdbms/scostore/JoinListStore.java index 69a40ed92..7e71cc8f9 100644 --- a/src/main/java/org/datanucleus/store/rdbms/scostore/JoinListStore.java +++ b/src/main/java/org/datanucleus/store/rdbms/scostore/JoinListStore.java @@ -298,7 +298,7 @@ else if (elementOwner != op.getObject() && op.getReferencedPC() == null) start++; // Execute the statement - sqlControl.executeStatementUpdate(ec, mconn, addStmt, ps, !iter.hasNext()); + sqlControl.executeStatementUpdate(ec, mconn, addStmt, ps, !elemIter.hasNext()); } finally { diff --git a/src/main/java/org/datanucleus/store/rdbms/scostore/JoinMapStore.java b/src/main/java/org/datanucleus/store/rdbms/scostore/JoinMapStore.java index 74e32462c..0aa0c3165 100644 --- a/src/main/java/org/datanucleus/store/rdbms/scostore/JoinMapStore.java +++ b/src/main/java/org/datanucleus/store/rdbms/scostore/JoinMapStore.java @@ -20,12 +20,18 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.AbstractMap; +import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.NoSuchElementException; -import java.util.Set; +import java.util.Optional; import org.datanucleus.ClassLoaderResolver; import org.datanucleus.ExecutionContext; @@ -36,6 +42,9 @@ import org.datanucleus.query.expression.Expression; import org.datanucleus.state.ObjectProvider; import org.datanucleus.store.connection.ManagedConnection; +import org.datanucleus.store.query.QueryManager; +import org.datanucleus.store.rdbms.JDBCUtils; +import org.datanucleus.store.rdbms.SQLController; import org.datanucleus.store.rdbms.exceptions.MappedDatastoreException; import org.datanucleus.store.rdbms.mapping.MappingHelper; import org.datanucleus.store.rdbms.mapping.java.EmbeddedKeyPCMapping; @@ -44,8 +53,7 @@ import org.datanucleus.store.rdbms.mapping.java.SerialisedMapping; import org.datanucleus.store.rdbms.mapping.java.SerialisedPCMapping; import org.datanucleus.store.rdbms.mapping.java.SerialisedReferenceMapping; -import org.datanucleus.store.rdbms.JDBCUtils; -import org.datanucleus.store.rdbms.SQLController; +import org.datanucleus.store.rdbms.mapping.java.SingleFieldMapping; import org.datanucleus.store.rdbms.query.PersistentClassROF; import org.datanucleus.store.rdbms.query.ResultObjectFactory; import org.datanucleus.store.rdbms.query.StatementClassMapping; @@ -74,29 +82,38 @@ */ public class JoinMapStore extends AbstractMapStore { - private String putStmt; - private String updateStmt; - private String removeStmt; - private String clearStmt; + private final String putStmt; + + private final String updateStmt; + + private final String removeStmt; + + private final String clearStmt; + + private static class CachedGetStmt + { + /** JDBC statement to use for retrieving keys of the map (locking). */ + private String stmtLocked; + + /** JDBC statement to use for retrieving keys of the map (not locking). */ + private String stmt; - /** JDBC statement to use for retrieving keys of the map (locking). */ - private volatile String getStmtLocked = null; + private StatementClassMapping mappingDef; - /** JDBC statement to use for retrieving keys of the map (not locking). */ - private String getStmtUnlocked = null; + private StatementParameterMapping mappingParams; + } + + private SetStore keySetStore = null; + + private CollectionStore valueSetStore = null; - private StatementClassMapping getMappingDef = null; - private StatementParameterMapping getMappingParams = null; + private MapEntrySetStore entrySetStore = null; - private SetStore keySetStore = null; - private CollectionStore valueSetStore = null; - private SetStore entrySetStore = null; - /** - * when the element mappings columns can't be part of the primary key - * by datastore limitations like BLOB types. An adapter mapping is used to be a kind of "index" + * when the element mappings columns can't be part of the primary key by datastore limitations like BLOB + * types. An adapter mapping is used to be a kind of "index" */ - protected final JavaTypeMapping adapterMapping; + protected final JavaTypeMapping adapterMapping; /** * Constructor for the backing store of a join map for RDBMS. @@ -124,12 +141,12 @@ public JoinMapStore(MapTable mapTable, ClassLoaderResolver clr) keyCmd = storeMgr.getNucleusContext().getMetaDataManager().getMetaDataForClass(clr.classForName(keyType), clr); - Class value_class=clr.classForName(valueType); + Class value_class = clr.classForName(valueType); if (ClassUtils.isReferenceType(value_class)) { // Map of reference value types (interfaces/Objects) NucleusLogger.PERSISTENCE.warn(Localiser.msg("056066", ownerMemberMetaData.getFullFieldName(), value_class.getName())); - valueCmd = storeMgr.getNucleusContext().getMetaDataManager().getMetaDataForImplementationOfReference(value_class,null,clr); + valueCmd = storeMgr.getNucleusContext().getMetaDataManager().getMetaDataForImplementationOfReference(value_class, null, clr); if (valueCmd != null) { this.valueType = value_class.getName(); @@ -169,101 +186,115 @@ public JoinMapStore(MapTable mapTable, ClassLoaderResolver clr) * @param op ObjectProvider for the Map * @param m The Map to add */ + @Override public void putAll(ObjectProvider op, Map m) { - if (m == null || m.size() == 0) + if (m == null || m.isEmpty()) { return; } - - Set puts = new HashSet<>(); - Set updates = new HashSet<>(); - - Iterator i = m.entrySet().iterator(); - while (i.hasNext()) + // Make sure the related objects are persisted (persistence-by-reachability) + for (Map.Entry e : m.entrySet()) { - Map.Entry e = (Map.Entry)i.next(); - Object key = e.getKey(); - Object value = e.getValue(); - - // Make sure the related objects are persisted (persistence-by-reachability) - validateKeyForWriting(op, key); - validateValueForWriting(op, value); + validateKeyForWriting(op, e.getKey()); + validateValueForWriting(op, e.getValue()); + } - // Check if this is a new entry, or an update - try + // Check if this is a new entry, or an update + ArrayList> puts = new ArrayList<>(); + ArrayList> updates = new ArrayList<>(); + boolean bulkSelect = keyMapping instanceof SingleFieldMapping && keyMapping + .getNumberOfColumnMappings() == 1 && !(keyMapping instanceof SerialisedMapping); + if (bulkSelect) + { + HashMap> map = new HashMap<>(); + this.entrySetStore(); // initialize + final Iterator> dbIterator = this.entrySetStore.iterator(op, m.keySet()); + while (dbIterator.hasNext()) { - Object oldValue = getValue(op, key); - if (oldValue != value) - { - updates.add(e); - } + final Entry e = dbIterator.next(); + map.put(e.getKey(), Optional.ofNullable(e.getValue())); } - catch (NoSuchElementException nsee) + if (map.isEmpty()) { - puts.add(e); + puts.addAll(m.entrySet()); } - } - - boolean batched = allowsBatching(); - - // Put any new entries - if (puts.size() > 0) - { - try + else { - ExecutionContext ec = op.getExecutionContext(); - ManagedConnection mconn = storeMgr.getConnectionManager().getConnection(ec); - try + for (Map.Entry e : m.entrySet()) { - // Loop through all entries - Iterator iter = puts.iterator(); - while (iter.hasNext()) + K key = e.getKey(); + if (map.containsKey(key)) { - // Add the row to the join table - Map.Entry entry = iter.next(); - internalPut(op, mconn, batched, entry.getKey(), entry.getValue(), (!iter.hasNext())); + V newValue = e.getValue(); + Optional oldValue = map.get(key); + if (oldValue.isPresent()) + { + if (oldValue != newValue) + { + updates.add(e); + } + } + else + { + // was null + if (newValue != null) + { + updates.add(e); + } + } + } + else + { + puts.add(e); } - } - finally - { - mconn.release(); } } - catch (MappedDatastoreException e) - { - throw new NucleusDataStoreException(Localiser.msg("056016", e.getMessage()), e); - } } - - // Update any changed entries - if (updates.size() > 0) + else { - try + for (Map.Entry e : m.entrySet()) { - ExecutionContext ec = op.getExecutionContext(); - ManagedConnection mconn = storeMgr.getConnectionManager().getConnection(ec); + K key = e.getKey(); + V value = e.getValue(); try { - // Loop through all entries - Iterator iter = updates.iterator(); - while (iter.hasNext()) + Object oldValue = getValue(op, key); + if (oldValue != value) { - // Update the row in the join table - Map.Entry entry = iter.next(); - internalUpdate(op, mconn, batched, entry.getKey(), entry.getValue(), !iter.hasNext()); + updates.add(e); } } - finally + catch (NoSuchElementException nsee) { - mconn.release(); + puts.add(e); } } - catch (MappedDatastoreException mde) + } + + if (puts.isEmpty() && updates.isEmpty()) + { + return; + } + boolean batched = allowsBatching(); + try + { + ExecutionContext ec = op.getExecutionContext(); + ManagedConnection mconn = storeMgr.getConnectionManager().getConnection(ec); + try + { + internalPut(op, mconn, batched, puts); + internalUpdate(op, mconn, batched, updates); + } + finally { - throw new NucleusDataStoreException(Localiser.msg("056016", mde.getMessage()), mde); + mconn.release(); } } + catch (MappedDatastoreException mde) + { + throw new NucleusDataStoreException(Localiser.msg("056016", mde.getMessage()), mde); + } } /** @@ -273,6 +304,7 @@ public void putAll(ObjectProvider op, Map m) * @param value The value to store. * @return The value stored. **/ + @Override public V put(ObjectProvider op, K key, V value) { validateKeyForWriting(op, key); @@ -300,13 +332,14 @@ public V put(ObjectProvider op, K key, V value) ManagedConnection mconn = storeMgr.getConnectionManager().getConnection(ec); try { + List> singleton = Collections.singletonList(new AbstractMap.SimpleEntry(key, value)); if (exists) { - internalUpdate(op, mconn, false, key, value, true); + internalUpdate(op, mconn, false, singleton); } else { - internalPut(op, mconn, false, key, value, true); + internalPut(op, mconn, false, singleton); } } finally @@ -339,6 +372,7 @@ public V put(ObjectProvider op, K key, V value) * @param key Key of the entry to remove. * @return The value that was removed. */ + @Override public V remove(ObjectProvider op, Object key) { if (!validateKeyForReading(op, key)) @@ -391,6 +425,7 @@ public V remove(ObjectProvider op, Object key) * @param key Key of the item to remove. * @return The value that was removed. */ + @Override public V remove(ObjectProvider op, Object key, Object oldValue) { if (!validateKeyForReading(op, key)) @@ -425,18 +460,19 @@ public V remove(ObjectProvider op, Object key, Object oldValue) * Method to clear the map of all values. * @param ownerOP ObjectProvider for the map. */ + @Override public void clear(ObjectProvider ownerOP) { - Collection dependentElements = null; + Collection dependentElements = null; if (ownerMemberMetaData.getMap().isDependentKey() || ownerMemberMetaData.getMap().isDependentValue()) { // Retain the PC dependent keys/values that need deleting after clearing - dependentElements = new HashSet(); + dependentElements = new HashSet<>(); ApiAdapter api = ownerOP.getExecutionContext().getApiAdapter(); - Iterator iter = entrySetStore().iterator(ownerOP); + Iterator> iter = entrySetStore().iterator(ownerOP); while (iter.hasNext()) { - Map.Entry entry = (Map.Entry)iter.next(); + Entry entry = iter.next(); MapMetaData mapmd = ownerMemberMetaData.getMap(); if (api.isPersistable(entry.getKey()) && mapmd.isDependentKey() && !mapmd.isEmbeddedKey()) { @@ -450,7 +486,7 @@ public void clear(ObjectProvider ownerOP) } clearInternal(ownerOP); - if (dependentElements != null && dependentElements.size() > 0) + if (dependentElements != null && !dependentElements.isEmpty()) { // Delete all dependent objects ownerOP.getExecutionContext().deleteObjects(dependentElements.toArray()); @@ -461,11 +497,12 @@ public void clear(ObjectProvider ownerOP) * Accessor for the keys in the Map. * @return The keys **/ + @Override public synchronized SetStore keySetStore() { if (keySetStore == null) { - keySetStore = new MapKeySetStore((MapTable)mapTable, this, clr); + keySetStore = new MapKeySetStore((MapTable) mapTable, this, clr); } return keySetStore; } @@ -474,11 +511,12 @@ public synchronized SetStore keySetStore() * Accessor for the values in the Map. * @return The values. */ + @Override public synchronized CollectionStore valueCollectionStore() { if (valueSetStore == null) { - valueSetStore = new MapValueCollectionStore((MapTable)mapTable, this, clr); + valueSetStore = new MapValueCollectionStore((MapTable) mapTable, this, clr); } return valueSetStore; } @@ -487,11 +525,12 @@ public synchronized CollectionStore valueCollectionStore() * Accessor for the map entries in the Map. * @return The map entries. */ - public synchronized SetStore entrySetStore() + @Override + public synchronized SetStore> entrySetStore() { if (entrySetStore == null) { - entrySetStore = new MapEntrySetStore((MapTable)mapTable, this, clr); + entrySetStore = new MapEntrySetStore<>((MapTable) mapTable, this, clr); } return entrySetStore; } @@ -502,12 +541,14 @@ public JavaTypeMapping getAdapterMapping() } /** - * Generate statement to add an item to the Map. - * Adds a row to the link table, linking container with value object. + * Generate statement to add an item to the Map. Adds a row to the link table, linking container with + * value object. + * *
      * INSERT INTO MAPTABLE (VALUECOL, OWNERCOL, KEYCOL)
      * VALUES (?, ?, ?)
      * 
+ * * @return Statement to add an item to the Map. */ private String getPutStmt() @@ -515,7 +556,7 @@ private String getPutStmt() StringBuilder stmt = new StringBuilder("INSERT INTO "); stmt.append(mapTable.toString()); stmt.append(" ("); - for (int i=0; i 0) { @@ -524,28 +565,28 @@ private String getPutStmt() stmt.append(valueMapping.getColumnMapping(i).getColumn().getIdentifier().toString()); } - for (int i=0; i 0) { @@ -554,20 +595,20 @@ private String getPutStmt() stmt.append(valueMapping.getColumnMapping(i).getInsertionInputParameter()); } - for (int i=0; i * UPDATE MAPTABLE * SET VALUECOL=? * WHERE OWNERCOL=? * AND KEYCOL=? * + * * @return Statement to update an item in the Map. */ private String getUpdateStmt() @@ -593,7 +636,7 @@ private String getUpdateStmt() StringBuilder stmt = new StringBuilder("UPDATE "); stmt.append(mapTable.toString()); stmt.append(" SET "); - for (int i=0; i 0) { @@ -611,13 +654,15 @@ private String getUpdateStmt() } /** - * Generate statement to remove an item from the Map. - * Deletes the link from the join table, leaving the value object in its own table. + * Generate statement to remove an item from the Map. Deletes the link from the join table, leaving the + * value object in its own table. + * *
      * DELETE FROM MAPTABLE
      * WHERE OWNERCOL=?
      * AND KEYCOL=?
      * 
+ * * @return Return an item from the Map. */ private String getRemoveStmt() @@ -632,12 +677,14 @@ private String getRemoveStmt() } /** - * Generate statement to clear the Map. - * Deletes the links from the join table for this Map, leaving the value objects in their own table(s). + * Generate statement to clear the Map. Deletes the links from the join table for this Map, leaving the + * value objects in their own table(s). + * *
      * DELETE FROM MAPTABLE
      * WHERE OWNERCOL=?
      * 
+ * * @return Statement to clear the Map. */ private String getClearStmt() @@ -657,8 +704,9 @@ private String getClearStmt() * @return The value for this key * @throws NoSuchElementException if the value for the key was not found */ + @Override protected V getValue(ObjectProvider ownerOP, Object key) - throws NoSuchElementException + throws NoSuchElementException { if (!validateKeyForReading(ownerOP, key)) { @@ -666,23 +714,20 @@ protected V getValue(ObjectProvider ownerOP, Object key) } ExecutionContext ec = ownerOP.getExecutionContext(); - if (getStmtLocked == null) + final QueryManager queryManager = storeMgr.getQueryManager(); + final String cacheKey = "JoinMapStore.getValue FROM " + mapTable.toString() + " FetchGroup " + String.join(",", ec.getFetchPlan().getGroups()); + CachedGetStmt cachedGetStmt = (CachedGetStmt) queryManager.getDatastoreQueryCompilation(storeMgr.getQueryCacheKey(), JoinMapStore.class.getSimpleName(), + cacheKey); + if (cachedGetStmt == null) { - synchronized (this) // Make sure this completes in case another thread needs the same info - { - if (getStmtLocked == null) - { - // Generate the statement, and statement mapping/parameter information - SQLStatement sqlStmt = getSQLStatementForGet(ownerOP); - getStmtUnlocked = sqlStmt.getSQLText().toSQL(); - sqlStmt.addExtension(SQLStatement.EXTENSION_LOCK_FOR_UPDATE, true); - getStmtLocked = sqlStmt.getSQLText().toSQL(); - } - } + // Generate the statement, and statement mapping/parameter information + cachedGetStmt = getSQLStatementForGet(ownerOP); + queryManager.addDatastoreQueryCompilation(storeMgr.getQueryCacheKey(), JoinMapStore.class.getSimpleName(), + cacheKey, cachedGetStmt); } Transaction tx = ec.getTransaction(); - String stmt = (tx.getSerializeRead() != null && tx.getSerializeRead() ? getStmtLocked : getStmtUnlocked); + String stmt = (tx.getSerializeRead() != null && tx.getSerializeRead() ? cachedGetStmt.stmtLocked : cachedGetStmt.stmt); Object value = null; try { @@ -692,15 +737,15 @@ protected V getValue(ObjectProvider ownerOP, Object key) { // Create the statement and supply owner/key params PreparedStatement ps = sqlControl.getStatementForQuery(mconn, stmt); - StatementMappingIndex ownerIdx = getMappingParams.getMappingForParameter("owner"); + StatementMappingIndex ownerIdx = cachedGetStmt.mappingParams.getMappingForParameter("owner"); int numParams = ownerIdx.getNumberOfParameterOccurrences(); - for (int paramInstance=0;paramInstance rof = new PersistentClassROF<>(ec, rs, false, ec.getFetchPlan(), cachedGetStmt.mappingDef, valueCmd, + clr.classForName(valueType)); value = rof.getObject(); } @@ -780,20 +824,23 @@ else if (valueMapping instanceof ReferenceMapping) } /** - * Method to return an SQLStatement for retrieving the value for a key. - * Selects the join table and optionally joins to the value table if it has its own table. + * Method to return an SQLStatement for retrieving the value for a key. Selects the join table and + * optionally joins to the value table if it has its own table. * @param ownerOP ObjectProvider for the owning object * @return The SQLStatement */ - protected SelectStatement getSQLStatementForGet(ObjectProvider ownerOP) + protected CachedGetStmt getSQLStatementForGet(ObjectProvider ownerOP) { - SelectStatement sqlStmt = null; + SelectStatement sqlStmt; + StatementClassMapping getMappingDef; + ExecutionContext ec = ownerOP.getExecutionContext(); final ClassLoaderResolver clr = ownerOP.getExecutionContext().getClassLoaderResolver(); - Class valueCls = clr.classForName(this.valueType); + Class valueCls = clr.classForName(this.valueType); if (valuesAreEmbedded || valuesAreSerialised) { + getMappingDef = null; // Value is stored in join table sqlStmt = new SelectStatement(storeMgr, mapTable, null, null); sqlStmt.setClassLoaderResolver(clr); @@ -816,7 +863,8 @@ protected SelectStatement getSQLStatementForGet(ObjectProvider ownerOP) SQLTable valueSqlTbl = sqlStmt.getTable(valueTable, sqlStmt.getPrimaryTable().getGroupName()); if (valueSqlTbl == null) { - // Root value candidate has no table, so try to find a value candidate with a table that exists in this statement + // Root value candidate has no table, so try to find a value candidate with a table that + // exists in this statement Collection valueSubclassNames = storeMgr.getSubClassesForClass(valueType, true, clr); if (valueSubclassNames != null && !valueSubclassNames.isEmpty()) { @@ -834,7 +882,8 @@ protected SelectStatement getSQLStatementForGet(ObjectProvider ownerOP) } } } - SQLStatementHelper.selectFetchPlanOfSourceClassInStatement(sqlStmt, getMappingDef, ec.getFetchPlan(), valueSqlTbl, valueCmd, ec.getFetchPlan().getMaxFetchDepth()); + SQLStatementHelper.selectFetchPlanOfSourceClassInStatement(sqlStmt, getMappingDef, ec.getFetchPlan(), valueSqlTbl, valueCmd, + ec.getFetchPlan().getMaxFetchDepth()); } // Apply condition on owner field to filter by owner @@ -849,7 +898,7 @@ protected SelectStatement getSQLStatementForGet(ObjectProvider ownerOP) { // if the keyMapping contains a BLOB column (or any other column not supported by the database // as primary key), uses like instead of the operator OP_EQ (=) - // in future do not check if the keyMapping is of ObjectMapping, but use the database + // in future do not check if the keyMapping is of ObjectMapping, but use the database // adapter to check the data types not supported as primary key // if object mapping (BLOB) use like SQLExpression keyExpr = exprFactory.newExpression(sqlStmt, sqlStmt.getPrimaryTable(), keyMapping); @@ -867,50 +916,39 @@ protected SelectStatement getSQLStatementForGet(ObjectProvider ownerOP) int inputParamNum = 1; StatementMappingIndex ownerIdx = new StatementMappingIndex(ownerMapping); StatementMappingIndex keyIdx = new StatementMappingIndex(keyMapping); - if (sqlStmt.getNumberOfUnions() > 0) - { - // Add parameter occurrence for each union of statement - for (int j=0;j ownerOP) { try { @@ -938,11 +976,11 @@ protected void clearInternal(ObjectProvider ownerOP) } catch (SQLException e) { - throw new NucleusDataStoreException(Localiser.msg("056013",clearStmt),e); + throw new NucleusDataStoreException(Localiser.msg("056013", clearStmt), e); } } - protected void removeInternal(ObjectProvider op, Object key) + protected void removeInternal(ObjectProvider op, Object key) { ExecutionContext ec = op.getExecutionContext(); try @@ -971,7 +1009,7 @@ protected void removeInternal(ObjectProvider op, Object key) } catch (SQLException e) { - throw new NucleusDataStoreException(Localiser.msg("056012",removeStmt),e); + throw new NucleusDataStoreException(Localiser.msg("056012", removeStmt), e); } } @@ -980,40 +1018,41 @@ protected void removeInternal(ObjectProvider op, Object key) * @param ownerOP ObjectProvider for the owner * @param conn The Connection * @param batched Whether we are batching it - * @param key The key - * @param value The new value - * @param executeNow Whether to execute the statement now or wait til any batch + * @param updates the values to be update by key * @throws MappedDatastoreException Thrown if an error occurs */ - protected void internalUpdate(ObjectProvider ownerOP, ManagedConnection conn, boolean batched, Object key, Object value, boolean executeNow) - throws MappedDatastoreException + protected void internalUpdate(ObjectProvider ownerOP, ManagedConnection conn, boolean batched, List> updates) + throws MappedDatastoreException { + if (updates.isEmpty()) + { + return; + } ExecutionContext ec = ownerOP.getExecutionContext(); SQLController sqlControl = storeMgr.getSQLController(); - try + try { - PreparedStatement ps = sqlControl.getStatementForUpdate(conn, updateStmt, false); + PreparedStatement ps = sqlControl.getStatementForUpdate(conn, updateStmt, batched); try { - int jdbcPosition = 1; - if (valueMapping != null) - { - jdbcPosition = BackingStoreHelper.populateValueInStatement(ec, ps, value, jdbcPosition, valueMapping); - } - else - { - jdbcPosition = BackingStoreHelper.populateEmbeddedValueFieldsInStatement(ownerOP, value, ps, jdbcPosition, (JoinTable)mapTable, this); - } - jdbcPosition = BackingStoreHelper.populateOwnerInStatement(ownerOP, ec, ps, jdbcPosition, this); - jdbcPosition = BackingStoreHelper.populateKeyInStatement(ec, ps, key, jdbcPosition, keyMapping); - - if (batched) - { - ps.addBatch(); - } - else + Iterator> iter = updates.iterator(); + while (iter.hasNext()) { - sqlControl.executeStatementUpdate(ec, conn, updateStmt, ps, true); + Entry entry = iter.next(); + K key = entry.getKey(); + V value = entry.getValue(); + int jdbcPosition = 1; + if (valueMapping != null) + { + jdbcPosition = BackingStoreHelper.populateValueInStatement(ec, ps, value, jdbcPosition, valueMapping); + } + else + { + jdbcPosition = BackingStoreHelper.populateEmbeddedValueFieldsInStatement(ownerOP, value, ps, jdbcPosition, (JoinTable) mapTable, this); + } + jdbcPosition = BackingStoreHelper.populateOwnerInStatement(ownerOP, ec, ps, jdbcPosition, this); + jdbcPosition = BackingStoreHelper.populateKeyInStatement(ec, ps, key, jdbcPosition, keyMapping); + sqlControl.executeStatementUpdate(ec, conn, updateStmt, ps, !iter.hasNext()); } } finally @@ -1032,43 +1071,57 @@ protected void internalUpdate(ObjectProvider ownerOP, ManagedConnection conn, bo * @param ownerOP ObjectProvider for the owner * @param conn The Connection * @param batched Whether we are batching it - * @param key The key - * @param value The value - * @param executeNow Whether to execute the statement now or wait til batching - * @return The return codes from any executed statement + * @param puts the list of key/values being inserted * @throws MappedDatastoreException Thrown if an error occurs */ - protected int[] internalPut(ObjectProvider ownerOP, ManagedConnection conn, boolean batched, Object key, Object value, boolean executeNow) - throws MappedDatastoreException + protected void internalPut(ObjectProvider ownerOP, ManagedConnection conn, boolean batched, List> puts) + throws MappedDatastoreException { + if (puts.isEmpty()) + { + return; + } ExecutionContext ec = ownerOP.getExecutionContext(); SQLController sqlControl = storeMgr.getSQLController(); try { - PreparedStatement ps = sqlControl.getStatementForUpdate(conn, putStmt, false); + int nextIDAdapter = 1; + if (adapterMapping != null) + { + // Only set the adapter mapping if we have a new object + nextIDAdapter = getNextIDForAdapterColumn(ownerOP, ec, conn); + } + PreparedStatement ps = sqlControl.getStatementForUpdate(conn, putStmt, batched); try { - int jdbcPosition = 1; - if (valueMapping != null) + Iterator> iter = puts.iterator(); + while (iter.hasNext()) { - jdbcPosition = BackingStoreHelper.populateValueInStatement(ec, ps, value, jdbcPosition, valueMapping); - } - else - { - jdbcPosition = BackingStoreHelper.populateEmbeddedValueFieldsInStatement(ownerOP, value, ps, jdbcPosition, (JoinTable)mapTable, this); - } - jdbcPosition = BackingStoreHelper.populateOwnerInStatement(ownerOP, ec, ps, jdbcPosition, this); - if (adapterMapping != null) - { - // Only set the adapter mapping if we have a new object - long nextIDAdapter = getNextIDForAdapterColumn(ownerOP); - adapterMapping.setObject(ec, ps, MappingHelper.getMappingIndices(jdbcPosition, adapterMapping), Long.valueOf(nextIDAdapter)); - jdbcPosition += adapterMapping.getNumberOfColumnMappings(); - } - jdbcPosition = BackingStoreHelper.populateKeyInStatement(ec, ps, key, jdbcPosition, keyMapping); + Entry entry = iter.next(); + K key = entry.getKey(); + V value = entry.getValue(); + int jdbcPosition = 1; + if (valueMapping != null) + { + jdbcPosition = BackingStoreHelper.populateValueInStatement(ec, ps, value, jdbcPosition, valueMapping); + } + else + { + jdbcPosition = BackingStoreHelper.populateEmbeddedValueFieldsInStatement(ownerOP, value, ps, jdbcPosition, (JoinTable) mapTable, this); + } + jdbcPosition = BackingStoreHelper.populateOwnerInStatement(ownerOP, ec, ps, jdbcPosition, this); + if (adapterMapping != null) + { + // Only set the adapter mapping if we have a new object + adapterMapping.setObject(ec, ps, MappingHelper.getMappingIndices(jdbcPosition, adapterMapping), Integer.valueOf(nextIDAdapter)); + nextIDAdapter++; + jdbcPosition += adapterMapping.getNumberOfColumnMappings(); + } + jdbcPosition = BackingStoreHelper.populateKeyInStatement(ec, ps, key, jdbcPosition, keyMapping); - // Execute the statement - return sqlControl.executeStatementUpdate(ec, conn, putStmt, ps, true); + // Execute the statement + sqlControl.executeStatementUpdate(ec, conn, putStmt, ps, !iter.hasNext()); + } } finally { @@ -1077,75 +1130,69 @@ protected int[] internalPut(ObjectProvider ownerOP, ManagedConnection conn, bool } catch (SQLException e) { - throw new MappedDatastoreException(getPutStmt(), e); + throw new MappedDatastoreException(putStmt, e); } } /** - * Accessor for the higher id when elements primary key can't be part of - * the primary key by datastore limitations like BLOB types can't be primary keys. + * Accessor for the higher id when elements primary key can't be part of the primary key by datastore + * limitations like BLOB types can't be primary keys. * @param op ObjectProvider for container * @return The next id */ - private int getNextIDForAdapterColumn(ObjectProvider op) + private int getNextIDForAdapterColumn(ObjectProvider op, ExecutionContext ec, ManagedConnection mconn) { int nextID; try { - ExecutionContext ec = op.getExecutionContext(); - ManagedConnection mconn = storeMgr.getConnectionManager().getConnection(ec); SQLController sqlControl = storeMgr.getSQLController(); + String stmt = getMaxAdapterColumnIdStmt(); + PreparedStatement ps = sqlControl.getStatementForQuery(mconn, stmt); + try { - String stmt = getMaxAdapterColumnIdStmt(); - PreparedStatement ps = sqlControl.getStatementForQuery(mconn, stmt); - + int jdbcPosition = 1; + BackingStoreHelper.populateOwnerInStatement(op, ec, ps, jdbcPosition, this); + ResultSet rs = sqlControl.executeStatementQuery(ec, mconn, stmt, ps); try { - int jdbcPosition = 1; - BackingStoreHelper.populateOwnerInStatement(op, ec, ps, jdbcPosition, this); - ResultSet rs = sqlControl.executeStatementQuery(ec, mconn, stmt, ps); - try + if (!rs.next()) { - if (!rs.next()) - { - nextID = 1; - } - else - { - nextID = rs.getInt(1)+1; - } - - JDBCUtils.logWarnings(rs); + nextID = 1; } - finally + else { - rs.close(); + nextID = rs.getInt(1) + 1; } + + JDBCUtils.logWarnings(rs); } finally { - sqlControl.closeStatement(mconn, ps); + rs.close(); } } finally { - mconn.release(); + sqlControl.closeStatement(mconn, ps); } } catch (SQLException e) { - throw new NucleusDataStoreException(Localiser.msg("056020",getMaxAdapterColumnIdStmt()),e); + throw new NucleusDataStoreException(Localiser.msg("056020", getMaxAdapterColumnIdStmt()), e); } return nextID; } + /** * Generate statement for obtaining the maximum id. + * *
      * SELECT MAX(SCOID) FROM MAPTABLE
      * WHERE OWNERCOL=?
      * 
+ * * @return The Statement returning the higher id */ private String getMaxAdapterColumnIdStmt() diff --git a/src/main/java/org/datanucleus/store/rdbms/scostore/MapEntrySetStore.java b/src/main/java/org/datanucleus/store/rdbms/scostore/MapEntrySetStore.java index 417e77102..5001cf611 100644 --- a/src/main/java/org/datanucleus/store/rdbms/scostore/MapEntrySetStore.java +++ b/src/main/java/org/datanucleus/store/rdbms/scostore/MapEntrySetStore.java @@ -25,30 +25,32 @@ import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.datanucleus.ClassLoaderResolver; import org.datanucleus.ExecutionContext; -import org.datanucleus.FetchPlan; import org.datanucleus.Transaction; import org.datanucleus.exceptions.NucleusDataStoreException; import org.datanucleus.metadata.AbstractMemberMetaData; import org.datanucleus.state.ObjectProvider; import org.datanucleus.store.connection.ManagedConnection; +import org.datanucleus.store.rdbms.JDBCUtils; +import org.datanucleus.store.rdbms.SQLController; import org.datanucleus.store.rdbms.exceptions.MappedDatastoreException; import org.datanucleus.store.rdbms.mapping.java.EmbeddedKeyPCMapping; import org.datanucleus.store.rdbms.mapping.java.EmbeddedValuePCMapping; import org.datanucleus.store.rdbms.mapping.java.JavaTypeMapping; import org.datanucleus.store.rdbms.mapping.java.SerialisedPCMapping; import org.datanucleus.store.rdbms.mapping.java.SerialisedReferenceMapping; -import org.datanucleus.store.rdbms.JDBCUtils; -import org.datanucleus.store.rdbms.SQLController; import org.datanucleus.store.rdbms.query.StatementMappingIndex; import org.datanucleus.store.rdbms.query.StatementParameterMapping; +import org.datanucleus.store.rdbms.sql.SQLJoin.JoinType; import org.datanucleus.store.rdbms.sql.SQLStatement; import org.datanucleus.store.rdbms.sql.SQLStatementHelper; import org.datanucleus.store.rdbms.sql.SQLTable; import org.datanucleus.store.rdbms.sql.SelectStatement; -import org.datanucleus.store.rdbms.sql.SQLJoin.JoinType; import org.datanucleus.store.rdbms.sql.expression.SQLExpression; import org.datanucleus.store.rdbms.sql.expression.SQLExpressionFactory; import org.datanucleus.store.rdbms.table.DatastoreClass; @@ -62,30 +64,40 @@ */ class MapEntrySetStore extends BaseContainerStore implements SetStore> { - /** Table containing the key and value forming the entry. This may be a join table, or key table (with value "FK"), or value table (with key "FK"). */ - protected Table mapTable; + /** + * Table containing the key and value forming the entry. This may be a join table, or key table (with + * value "FK"), or value table (with key "FK"). + */ + protected final Table mapTable; /** The backing store for the Map. */ - protected MapStore mapStore; + protected final MapStore mapStore; /** Mapping for the key. */ - protected JavaTypeMapping keyMapping; + protected final JavaTypeMapping keyMapping; /** Mapping for the value. */ - protected JavaTypeMapping valueMapping; + protected final JavaTypeMapping valueMapping; private String sizeStmt; /** SQL statement to use for retrieving data from the map (normal). */ - private String iteratorSelectStmtSql = null; + private String iteratorSelectStmtSql; /** SQL statement to use for retrieving data from the map (locking). */ - private String iteratorSelectStmtLockedSql = null; + private String iteratorSelectStmtLockedSql; + + /** SQL statement to use for retrieving data from the map (filter by key). */ + private String iteratorSelectWithKeysStmtSql; + + /** SQL statement to use for retrieving data from the map (filter by key + locking). */ + private String iteratorSelectWithKeysStmtLockedSql; + + private int[] iteratorKeyResultCols; - private int[] iteratorKeyResultCols = null; - private int[] iteratorValueResultCols = null; + private int[] iteratorValueResultCols; - private StatementParameterMapping iteratorMappingParams = null; + private StatementParameterMapping iteratorMappingParams; /** * Constructor for a store of the entries in a map when represented in a join table. @@ -100,13 +112,16 @@ class MapEntrySetStore extends BaseContainerStore implements SetStore extends BaseContainerStore implements SetStore getMapStore() return mapStore; } + @Override public JavaTypeMapping getOwnerMapping() { return ownerMapping; @@ -156,6 +175,7 @@ public JavaTypeMapping getValueMapping() * @param value The value * @return Whether the element was modified */ + @Override public boolean updateEmbeddedElement(ObjectProvider op, Map.Entry element, int fieldNumber, Object value) { // Do nothing since of no use here @@ -172,6 +192,7 @@ protected boolean validateElementType(Object element) * @param op ObjectProvider of the object * @param coll The collection to use */ + @Override public void update(ObjectProvider op, Collection coll) { // Crude update - remove existing and add new! @@ -180,22 +201,25 @@ public void update(ObjectProvider op, Collection coll) addAll(op, coll, 0); } + @Override public boolean contains(ObjectProvider op, Object element) { if (!validateElementType(element)) { return false; } - Entry entry = (Entry)element; + Entry entry = (Entry) element; return mapStore.containsKey(op, entry.getKey()); } + @Override public boolean add(ObjectProvider op, Map.Entry entry, int size) { throw new UnsupportedOperationException("Cannot add to a map through its entry set"); } + @Override public boolean addAll(ObjectProvider op, Collection entries, int size) { throw new UnsupportedOperationException("Cannot add to a map through its entry set"); @@ -207,6 +231,7 @@ public boolean addAll(ObjectProvider op, Collection entries, int size) * @param element Entry to remove * @return Whether it was removed */ + @Override public boolean remove(ObjectProvider op, Object element, int size, boolean allowDependentField) { if (!validateElementType(element)) @@ -214,7 +239,7 @@ public boolean remove(ObjectProvider op, Object element, int size, boolean allow return false; } - Entry entry = (Entry)element; + Entry entry = (Entry) element; Object removed = mapStore.remove(op, entry.getKey()); // NOTE: this may not return an accurate result if a null value is being removed @@ -227,6 +252,7 @@ public boolean remove(ObjectProvider op, Object element, int size, boolean allow * @param elements Entries to remove * @return Whether they were removed */ + @Override public boolean removeAll(ObjectProvider op, Collection elements, int size) { if (elements == null || elements.size() == 0) @@ -234,12 +260,12 @@ public boolean removeAll(ObjectProvider op, Collection elements, int size) return false; } - Iterator iter=elements.iterator(); - boolean modified=false; + Iterator iter = elements.iterator(); + boolean modified = false; while (iter.hasNext()) { - Object element=iter.next(); - Entry entry = (Entry)element; + Object element = iter.next(); + Entry entry = (Entry) element; Object removed = mapStore.remove(op, entry.getKey()); @@ -254,11 +280,13 @@ public boolean removeAll(ObjectProvider op, Collection elements, int size) * Method to clear the Map. * @param op ObjectProvider for the owner. */ + @Override public void clear(ObjectProvider op) { mapStore.clear(op); } + @Override public int size(ObjectProvider op) { int numRows; @@ -312,9 +340,11 @@ public int size(ObjectProvider op) /** * Method to return a size statement. + * *
      * SELECT COUNT(*) FROM MAP_TABLE WHERE OWNER=? AND KEY IS NOT NULL
      * 
+ * * @return The size statement */ private String getSizeStmt() @@ -328,7 +358,7 @@ private String getSizeStmt() if (keyMapping != null) { // We don't accept null keys - for (int i=0; imap.entrySet().iterator()). + * @return The iterator for the entries ( + * + *
+     * map.entrySet().iterator()
+     * 
+ * + * ). */ + @Override public Iterator> iterator(ObjectProvider ownerOP) + { + return iterator(ownerOP, null); + + } + + public Iterator> iterator(ObjectProvider ownerOP, Set selectedKeysOnly) { ExecutionContext ec = ownerOP.getExecutionContext(); Transaction tx = ec.getTransaction(); - String stmtSql = tx.getSerializeRead() ? iteratorSelectStmtLockedSql : iteratorSelectStmtSql; + String stmtSql; + if (selectedKeysOnly == null || selectedKeysOnly.size() > 1024) + { + selectedKeysOnly = null; + stmtSql = tx.getSerializeRead() ? iteratorSelectStmtLockedSql : iteratorSelectStmtSql; + } + else + { + stmtSql = tx.getSerializeRead() ? iteratorSelectWithKeysStmtLockedSql : iteratorSelectWithKeysStmtSql; + } + return iterator(ownerOP, ec, stmtSql, selectedKeysOnly); + } - if (stmtSql == null) + private void initStmt() + { + // Generate the statement + SelectStatement selectStmt = getSQLStatementForIterator(true); + + // Input parameter(s) - the owner + int inputParamNum = 1; + StatementMappingIndex ownerIdx = new StatementMappingIndex(ownerMapping); + int numberOfUnions = selectStmt.getNumberOfUnions(); + // Add parameter occurrence for each union of statement + for (int j = 0; j < numberOfUnions + 1; j++) { - synchronized (this) // Make sure this completes in case another thread needs the same info + int[] paramPositions = new int[ownerMapping.getNumberOfColumnMappings()]; + for (int k = 0; k < ownerMapping.getNumberOfColumnMappings(); k++) { - // Generate the statement - SelectStatement selectStmt = getSQLStatementForIterator(ownerOP, ec.getFetchPlan(), true); + paramPositions[k] = inputParamNum++; + } + ownerIdx.addParameterOccurrence(paramPositions); + } - // Input parameter(s) - the owner - int inputParamNum = 1; - StatementMappingIndex ownerIdx = new StatementMappingIndex(ownerMapping); - if (selectStmt.getNumberOfUnions() > 0) - { - // Add parameter occurrence for each union of statement - for (int j=0;j "?").limit(1024) + .collect(Collectors.joining(",", "(", ")")); + final String andInKeys = String.format(" AND %s IN %s ", keyMapping.getColumnMapping(0).getColumn().getIdentifier().toString(), keysPlaceholder); + iteratorSelectWithKeysStmtSql = iteratorSelectStmtSql + andInKeys; + iteratorSelectWithKeysStmtLockedSql = iteratorSelectStmtLockedSql.substring(0, iteratorSelectStmtSql.length()) + andInKeys + iteratorSelectStmtLockedSql + .substring(iteratorSelectStmtSql.length()); + } + private Iterator> iterator(ObjectProvider ownerOP, ExecutionContext ec, String stmtSql, Collection selectedKeysOnly) + { try { ManagedConnection mconn = storeMgr.getConnectionManager().getConnection(ec); @@ -407,35 +455,36 @@ public Iterator> iterator(ObjectProvider ownerOP) PreparedStatement ps = sqlControl.getStatementForQuery(mconn, stmtSql); StatementMappingIndex ownerIdx = iteratorMappingParams.getMappingForParameter("owner"); int numParams = ownerIdx.getNumberOfParameterOccurrences(); - for (int paramInstance=0;paramInstance iterator = selectedKeysOnly.iterator(); + for (int i = 0; i < 1024; i++) { - return new SetIterator(ownerOP, this, ownerMemberMetaData, rs, iteratorKeyResultCols, iteratorValueResultCols) + numParams++; + this.keyMapping.setObject(ec, ps, new int[]{numParams}, iterator.hasNext() ? iterator.next() : null); + } + } + try (ResultSet rs = sqlControl.executeStatementQuery(ec, mconn, stmtSql, ps)) + { + return new SetIterator(ownerOP, this, ownerMemberMetaData, rs, iteratorKeyResultCols, iteratorValueResultCols) + { + @Override + protected boolean next(Object rs) throws MappedDatastoreException { - protected boolean next(Object rs) throws MappedDatastoreException + try { - try - { - return ((ResultSet) rs).next(); - } - catch (SQLException e) - { - throw new MappedDatastoreException("SQLException", e); - } + return ((ResultSet) rs).next(); } - }; - } - finally - { - rs.close(); - } + catch (SQLException e) + { + throw new MappedDatastoreException("SQLException", e); + } + } + }; } finally { @@ -447,112 +496,30 @@ protected boolean next(Object rs) throws MappedDatastoreException mconn.release(); } } - catch (SQLException e) - { - throw new NucleusDataStoreException("Iteration request failed: " + stmtSql, e); - } - catch (MappedDatastoreException e) + catch (SQLException | MappedDatastoreException e) { throw new NucleusDataStoreException("Iteration request failed: " + stmtSql, e); } } /** - * Method to generate a SelectStatement for iterating through entries of the map. - * Creates a statement that selects the table holding the map definition (key/value mappings). - * Adds a restriction on the ownerMapping of the containerTable so we can restrict to the owner object. - * Adds a restriction on the keyMapping not being null. + * Method to generate a SelectStatement for iterating through entries of the map. Creates a statement that + * selects the table holding the map definition (key/value mappings). Adds a restriction on the + * ownerMapping of the containerTable so we can restrict to the owner object. Adds a restriction on the + * keyMapping not being null. + * *
      * SELECT KEY, VALUE FROM MAP_TABLE WHERE OWNER_ID=? AND KEY IS NOT NULL
      * 
- * @param ownerOP ObjectProvider for the owner object - * @param fp Fetch Plan to observe when selecting key/value + * * @param addRestrictionOnOwner Whether to add a restriction on the owner object for this map - * @return The SelectStatement - * TODO Change this to return KeyValueIteratorStatement */ - protected SelectStatement getSQLStatementForIterator(ObjectProvider ownerOP, FetchPlan fp, boolean addRestrictionOnOwner) + protected SelectStatement getSQLStatementForIterator(boolean addRestrictionOnOwner) { SQLExpressionFactory exprFactory = storeMgr.getSQLExpressionFactory(); SelectStatement sqlStmt = new SelectStatement(storeMgr, mapTable, null, null); sqlStmt.setClassLoaderResolver(clr); - // MapMetaData mapmd = ownerMemberMetaData.getMap(); - - // Select key and value - /*if (mapmd.getMapType() == MapType.MAP_TYPE_JOIN) - { - JoinMapStore joinMapStore = (JoinMapStore)mapStore; - if (mapmd.isEmbeddedKey() || mapmd.isSerializedKey() || !mapmd.keyIsPersistent()) - { - // Key stored wholely in join table - } - else if (joinMapStore.keyMapping instanceof ReferenceMapping) - { - // Key = Reference type (interface/Object) - // Just select the key here since we're going to return the implementation id columns only - } - else - { - // Key stored in own table, so join to table and select requisite fields - } - - if (mapmd.isEmbeddedValue() || mapmd.isSerializedValue() || !mapmd.valueIsPersistent()) - { - // Value stored wholely in join table - } - else if (joinMapStore.valueMapping instanceof ReferenceMapping) - { - // Value = Reference type (interface/Object) - // Just select the value here since we're going to return the implementation id columns only - } - else - { - // Value stored in own table, so join to table and select requisite fields - } - } - else if (mapmd.getMapType() == MapType.MAP_TYPE_KEY_IN_VALUE) - { - FKMapStore fkMapStore = (FKMapStore)mapStore; - - // Select key - if (mapmd.isEmbeddedKey() || mapmd.isSerializedKey() || !mapmd.keyIsPersistent()) - { - // Key stored wholely in value table - } - else if (fkMapStore.keyMapping instanceof ReferenceMapping) - { - // Key = Reference type (interface/Object) - // Just select the key here since we're going to return the implementation id columns only - } - else - { - // Key stored in own table, so join to table and select requisite fields - } - - // Select the requisite fields of the value from this table - } - else - { - FKMapStore fkMapStore = (FKMapStore)mapStore; - - // Select the requisite fields of the key from this table - - // Select value - if (mapmd.isEmbeddedValue() || mapmd.isSerializedValue() || !mapmd.valueIsPersistent()) - { - // Value stored wholely in value table - } - else if (fkMapStore.valueMapping instanceof ReferenceMapping) - { - // Value = Reference type (interface/Object) - // Just select the value here since we're going to return the implementation id columns only - } - else - { - // Value stored in own table, so join to table and select requisite fields - } - }*/ // TODO If key is persistable and has inheritance also select a discriminator to get the type SQLTable entrySqlTblForKey = sqlStmt.getPrimaryTable(); @@ -562,10 +529,9 @@ else if (fkMapStore.valueMapping instanceof ReferenceMapping) if (entrySqlTblForKey == null) { // Add join to key table - entrySqlTblForKey = sqlStmt.join(JoinType.INNER_JOIN, sqlStmt.getPrimaryTable(), sqlStmt.getPrimaryTable().getTable().getIdMapping(), + entrySqlTblForKey = sqlStmt.join(JoinType.INNER_JOIN, sqlStmt.getPrimaryTable(), sqlStmt.getPrimaryTable().getTable().getIdMapping(), keyMapping.getTable(), null, keyMapping.getTable().getIdMapping(), null, null, true); } - // TODO Select FetchPlan of the key? } iteratorKeyResultCols = sqlStmt.select(entrySqlTblForKey, keyMapping, null); @@ -578,10 +544,9 @@ else if (fkMapStore.valueMapping instanceof ReferenceMapping) { // Add join to value table // TODO If this map allows null values, we should do left outer join - entrySqlTblForVal = sqlStmt.join(JoinType.INNER_JOIN, sqlStmt.getPrimaryTable(), sqlStmt.getPrimaryTable().getTable().getIdMapping(), + entrySqlTblForVal = sqlStmt.join(JoinType.INNER_JOIN, sqlStmt.getPrimaryTable(), sqlStmt.getPrimaryTable().getTable().getIdMapping(), valueMapping.getTable(), null, valueMapping.getTable().getIdMapping(), null, null, true); } - // TODO Select FetchPlan of the value? } iteratorValueResultCols = sqlStmt.select(entrySqlTblForVal, valueMapping, null); @@ -603,15 +568,18 @@ else if (fkMapStore.valueMapping instanceof ReferenceMapping) } /** - * Inner class representing an iterator for the Set. - * TODO Provide an option where a PersistentClassROF is provided for key and/or value so we can load fetch plan fields rather than just id. + * Inner class representing an iterator for the Set. TODO Provide an option where a PersistentClassROF is + * provided for key and/or value so we can load fetch plan fields rather than just id. */ - public static abstract class SetIterator implements Iterator + public abstract static class SetIterator implements Iterator> { - private final ObjectProvider op; - private final Iterator delegate; - private Entry lastElement = null; - private final MapEntrySetStore setStore; + private final ObjectProvider op; + + private final Iterator> delegate; + + private Entry lastElement = null; + + private final MapEntrySetStore setStore; /** * Constructor for iterating the Set of entries. @@ -623,14 +591,14 @@ public static abstract class SetIterator implements Iterator * @param valueResultCols Column(s) for the value id * @throws MappedDatastoreException Thrown if an error occurs extracting the results */ - protected SetIterator(ObjectProvider op, MapEntrySetStore setStore, AbstractMemberMetaData ownerMmd, + protected SetIterator(ObjectProvider op, MapEntrySetStore setStore, AbstractMemberMetaData ownerMmd, ResultSet rs, int[] keyResultCols, int[] valueResultCols) throws MappedDatastoreException { this.op = op; this.setStore = setStore; ExecutionContext ec = op.getExecutionContext(); - ArrayList results = new ArrayList(); + ArrayList> results = new ArrayList<>(); while (next(rs)) { Object key = null; @@ -667,18 +635,21 @@ protected SetIterator(ObjectProvider op, MapEntrySetStore setStore, AbstractMemb delegate = results.iterator(); } + @Override public boolean hasNext() { return delegate.hasNext(); } - public Object next() + @Override + public Entry next() { - lastElement = (Entry)delegate.next(); + lastElement = delegate.next(); return lastElement; } + @Override public synchronized void remove() { if (lastElement == null) @@ -700,12 +671,15 @@ public synchronized void remove() */ private static class EntryImpl implements Entry { - private final ObjectProvider ownerOP; + private final ObjectProvider ownerOP; + private final K key; + private final V value; + private final MapStore mapStore; - public EntryImpl(ObjectProvider op, K key, V value, MapStore mapStore) + public EntryImpl(ObjectProvider op, K key, V value, MapStore mapStore) { this.ownerOP = op; this.key = key; @@ -713,11 +687,13 @@ public EntryImpl(ObjectProvider op, K key, V value, MapStore mapStore) this.mapStore = mapStore; } + @Override public int hashCode() { return (key == null ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode()); } + @Override public boolean equals(Object o) { if (o == this) @@ -729,19 +705,23 @@ public boolean equals(Object o) return false; } - Entry e = (Entry)o; - return (key == null ? e.getKey() == null : key.equals(e.getKey())) && - (value == null ? e.getValue() == null : value.equals(e.getValue())); + Entry e = (Entry) o; + return (key == null ? e.getKey() == null : key.equals(e.getKey())) && (value == null ? e.getValue() == null : value.equals(e.getValue())); } + @Override public K getKey() { return key; } + + @Override public V getValue() { return value; } + + @Override public V setValue(V value) { return mapStore.put(ownerOP, key, value);