You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by aa...@apache.org on 2016/10/24 19:06:13 UTC

cayenne git commit: CAY-2125 SchemaUpdateStrategy doesn't work with multiple DataNodes

Repository: cayenne
Updated Branches:
  refs/heads/master c430014d1 -> 60fc4d267


CAY-2125 SchemaUpdateStrategy doesn't work with multiple DataNodes


Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/60fc4d26
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/60fc4d26
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/60fc4d26

Branch: refs/heads/master
Commit: 60fc4d2678b3d57f7dd896e9c8d07b7029cf3572
Parents: c430014
Author: Andrus Adamchik <an...@objectstyle.com>
Authored: Mon Oct 24 14:47:54 2016 -0400
Committer: Andrus Adamchik <an...@objectstyle.com>
Committed: Mon Oct 24 15:06:01 2016 -0400

----------------------------------------------------------------------
 .../org/apache/cayenne/access/DataNode.java     |  42 ++----
 .../access/dbsync/BaseSchemaUpdateStrategy.java |  18 +--
 .../access/dbsync/CreateIfNoSchemaStrategy.java | 125 ++++++++--------
 .../DefaultSchemaUpdateStrategyFactory.java     |  47 ++++++
 .../access/dbsync/SchemaUpdateStrategy.java     |   4 +-
 .../dbsync/SchemaUpdateStrategyFactory.java     |  31 ++++
 .../access/dbsync/SkipSchemaUpdateStrategy.java |   6 +-
 .../ThrowOnPartialOrCreateSchemaStrategy.java   |  20 +--
 .../dbsync/ThrowOnPartialSchemaStrategy.java    | 144 +++++++++----------
 .../server/DefaultDataNodeFactory.java          |  22 +--
 .../configuration/server/ServerModule.java      |  11 +-
 .../access/dbsync/SchemaUpdateStrategyBase.java |  19 ++-
 .../access/dbsync/TstSchemaUpdateStrategy.java  |   8 +-
 .../server/DataDomainProviderTest.java          |   7 +-
 docs/doc/src/main/resources/RELEASE-NOTES.txt   |   1 +
 docs/doc/src/main/resources/UPGRADE.txt         |   4 +
 16 files changed, 273 insertions(+), 236 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/60fc4d26/cayenne-server/src/main/java/org/apache/cayenne/access/DataNode.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/DataNode.java b/cayenne-server/src/main/java/org/apache/cayenne/access/DataNode.java
index 66651be..5c09b49 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/DataNode.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/DataNode.java
@@ -19,21 +19,8 @@
 
 package org.apache.cayenne.access;
 
-import java.io.PrintWriter;
-import java.sql.Connection;
-import java.sql.SQLException;
-import java.sql.SQLFeatureNotSupportedException;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.logging.Logger;
-
-import javax.sql.DataSource;
-
 import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.access.dbsync.SchemaUpdateStrategy;
-import org.apache.cayenne.access.dbsync.SkipSchemaUpdateStrategy;
 import org.apache.cayenne.access.jdbc.ColumnDescriptor;
 import org.apache.cayenne.access.jdbc.RowDescriptor;
 import org.apache.cayenne.access.jdbc.SQLTemplateProcessor;
@@ -58,6 +45,17 @@ import org.apache.cayenne.tx.BaseTransaction;
 import org.apache.cayenne.tx.Transaction;
 import org.apache.cayenne.util.ToStringBuilder;
 
+import javax.sql.DataSource;
+import java.io.PrintWriter;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Logger;
+
 /**
  * An abstraction of a single physical data storage. This is usually a database
  * server, but can potentially be some other storage type like an LDAP server,
@@ -70,7 +68,6 @@ public class DataNode implements QueryEngine {
 	protected DbAdapter adapter;
 	protected String dataSourceLocation;
 	protected String dataSourceFactory;
-	protected String schemaUpdateStrategyName;
 	protected EntityResolver entityResolver;
 	protected SchemaUpdateStrategy schemaUpdateStrategy;
 	protected Map<String, DataMap> dataMaps;
@@ -106,23 +103,6 @@ public class DataNode implements QueryEngine {
 	/**
 	 * @since 3.0
 	 */
-	public String getSchemaUpdateStrategyName() {
-		if (schemaUpdateStrategyName == null) {
-			schemaUpdateStrategyName = SkipSchemaUpdateStrategy.class.getName();
-		}
-		return schemaUpdateStrategyName;
-	}
-
-	/**
-	 * @since 3.0
-	 */
-	public void setSchemaUpdateStrategyName(String schemaUpdateStrategyName) {
-		this.schemaUpdateStrategyName = schemaUpdateStrategyName;
-	}
-
-	/**
-	 * @since 3.0
-	 */
 	public SchemaUpdateStrategy getSchemaUpdateStrategy() {
 		return schemaUpdateStrategy;
 	}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/60fc4d26/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/BaseSchemaUpdateStrategy.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/BaseSchemaUpdateStrategy.java b/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/BaseSchemaUpdateStrategy.java
index f4bf779..7158c5b 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/BaseSchemaUpdateStrategy.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/BaseSchemaUpdateStrategy.java
@@ -18,10 +18,10 @@
  ****************************************************************/
 package org.apache.cayenne.access.dbsync;
 
-import java.sql.SQLException;
-
 import org.apache.cayenne.access.DataNode;
 
+import java.sql.SQLException;
+
 /**
  * @since 3.0
  */
@@ -31,18 +31,20 @@ public abstract class BaseSchemaUpdateStrategy implements SchemaUpdateStrategy {
     protected volatile ThreadLocal<Boolean> threadRunInProgress;
 
     public BaseSchemaUpdateStrategy() {
-        super();
-        threadRunInProgress = new ThreadLocal<Boolean>();
+
+        // this barrier is needed to prevent stack overflow in the same thread
+        // (getConnection/updateSchema/getConnection/...)
+        this.threadRunInProgress = new ThreadLocal<>();
+        this.threadRunInProgress.set(false);
     }
 
-    /**
-     * @since 3.0
-     */
+    @Override
     public void updateSchema(DataNode dataNode) throws SQLException {
 
-        if (!run && (threadRunInProgress.get() == null || !threadRunInProgress.get())) {
+        if (!run && !threadRunInProgress.get()) {
             synchronized (this) {
                 if (!run) {
+
                     try {
                         threadRunInProgress.set(true);
                         processSchemaUpdate(dataNode);

http://git-wip-us.apache.org/repos/asf/cayenne/blob/60fc4d26/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/CreateIfNoSchemaStrategy.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/CreateIfNoSchemaStrategy.java b/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/CreateIfNoSchemaStrategy.java
index 2d72493..bcf0010 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/CreateIfNoSchemaStrategy.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/CreateIfNoSchemaStrategy.java
@@ -18,14 +18,6 @@
  ****************************************************************/
 package org.apache.cayenne.access.dbsync;
 
-import java.sql.Connection;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-
 import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.access.DbGenerator;
@@ -34,73 +26,80 @@ import org.apache.cayenne.map.DbEntity;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
 /**
  * @since 3.0
  */
 public class CreateIfNoSchemaStrategy extends BaseSchemaUpdateStrategy {
 
-	final static Log logger = LogFactory.getLog(CreateIfNoSchemaStrategy.class);
+    private final static Log LOGGER = LogFactory.getLog(CreateIfNoSchemaStrategy.class);
+
 
-	@Override
-	protected void processSchemaUpdate(DataNode dataNode) throws SQLException {
+    @Override
+    protected void processSchemaUpdate(DataNode dataNode) throws SQLException {
 
-		Map<String, Boolean> nameTables = getNameTablesInDB(dataNode);
-		Collection<DbEntity> entities = dataNode.getEntityResolver().getDbEntities();
-		boolean generate = true;
-		Iterator<DbEntity> it = entities.iterator();
-		while (it.hasNext()) {
-			if (nameTables.get(it.next().getName()) != null) {
-				generate = false;
-				break;
-			}
-		}
+        Map<String, Boolean> nameTables = getNameTablesInDB(dataNode);
+        Collection<DbEntity> entities = dataNode.getEntityResolver().getDbEntities();
+        boolean generate = true;
+        Iterator<DbEntity> it = entities.iterator();
+        while (it.hasNext()) {
+            if (nameTables.get(it.next().getName()) != null) {
+                generate = false;
+                break;
+            }
+        }
 
-		if (generate) {
-			logger.info("No schema detected, will create mapped tables");
-			generate(dataNode);
-		} else {
-			logger.info("Full or partial schema detected, skipping tables creation");
-		}
-	}
+        if (generate) {
+            LOGGER.info("No schema detected, will create mapped tables");
+            generate(dataNode);
+        } else {
+            LOGGER.info("Full or partial schema detected, skipping tables creation");
+        }
+    }
 
-	private void generate(DataNode dataNode) {
-		Collection<DataMap> map = dataNode.getDataMaps();
-		Iterator<DataMap> iterator = map.iterator();
-		while (iterator.hasNext()) {
-			DbGenerator gen = new DbGenerator(dataNode.getAdapter(), iterator.next(), dataNode.getJdbcEventLogger());
-			gen.setShouldCreateTables(true);
-			gen.setShouldDropTables(false);
-			gen.setShouldCreateFKConstraints(true);
-			gen.setShouldCreatePKSupport(true);
-			gen.setShouldDropPKSupport(false);
-			try {
-				gen.runGenerator(dataNode.getDataSource());
-			} catch (Exception e) {
-				throw new CayenneRuntimeException(e);
-			}
-		}
-	}
+    private void generate(DataNode dataNode) {
+        Collection<DataMap> map = dataNode.getDataMaps();
+        Iterator<DataMap> iterator = map.iterator();
+        while (iterator.hasNext()) {
+            DbGenerator gen = new DbGenerator(dataNode.getAdapter(), iterator.next(), dataNode.getJdbcEventLogger());
+            gen.setShouldCreateTables(true);
+            gen.setShouldDropTables(false);
+            gen.setShouldCreateFKConstraints(true);
+            gen.setShouldCreatePKSupport(true);
+            gen.setShouldDropPKSupport(false);
+            try {
+                gen.runGenerator(dataNode.getDataSource());
+            } catch (Exception e) {
+                throw new CayenneRuntimeException(e);
+            }
+        }
+    }
 
-	/**
-	 * Returns all the table names in database.
-	 * 
-	 * @throws SQLException
-	 */
-	protected Map<String, Boolean> getNameTablesInDB(DataNode dataNode) throws SQLException {
-		String tableLabel = dataNode.getAdapter().tableTypeForTable();
-		Map<String, Boolean> nameTables = new HashMap<>();
+    /**
+     * Returns all the table names in database.
+     */
+    protected Map<String, Boolean> getNameTablesInDB(DataNode dataNode) throws SQLException {
+        String tableLabel = dataNode.getAdapter().tableTypeForTable();
+        Map<String, Boolean> nameTables = new HashMap<>();
 
-		try (Connection con = dataNode.getDataSource().getConnection();) {
+        try (Connection con = dataNode.getDataSource().getConnection();) {
 
-			try (ResultSet rs = con.getMetaData().getTables(null, null, "%", new String[] { tableLabel });) {
+            try (ResultSet rs = con.getMetaData().getTables(null, null, "%", new String[]{tableLabel});) {
 
-				while (rs.next()) {
-					String name = rs.getString("TABLE_NAME");
-					nameTables.put(name, false);
-				}
-			}
-		}
+                while (rs.next()) {
+                    String name = rs.getString("TABLE_NAME");
+                    nameTables.put(name, false);
+                }
+            }
+        }
 
-		return nameTables;
-	}
+        return nameTables;
+    }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/60fc4d26/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/DefaultSchemaUpdateStrategyFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/DefaultSchemaUpdateStrategyFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/DefaultSchemaUpdateStrategyFactory.java
new file mode 100644
index 0000000..0ccbc5f
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/DefaultSchemaUpdateStrategyFactory.java
@@ -0,0 +1,47 @@
+/*
+ *    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.dbsync;
+
+import org.apache.cayenne.configuration.DataNodeDescriptor;
+import org.apache.cayenne.di.AdhocObjectFactory;
+import org.apache.cayenne.di.Inject;
+
+/**
+ * @since 4.0
+ */
+public class DefaultSchemaUpdateStrategyFactory implements SchemaUpdateStrategyFactory {
+
+    @Inject
+    protected AdhocObjectFactory objectFactory;
+
+    @Override
+    public SchemaUpdateStrategy create(DataNodeDescriptor nodeDescriptor) {
+        String type = nodeDescriptor.getSchemaUpdateStrategyType();
+
+        if (type == null) {
+            return createDefaultStrategy();
+        }
+
+        return objectFactory.newInstance(SchemaUpdateStrategy.class, type);
+    }
+
+    protected SchemaUpdateStrategy createDefaultStrategy() {
+        return new SkipSchemaUpdateStrategy();
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/60fc4d26/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/SchemaUpdateStrategy.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/SchemaUpdateStrategy.java b/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/SchemaUpdateStrategy.java
index 8459ac0..44b8bef 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/SchemaUpdateStrategy.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/SchemaUpdateStrategy.java
@@ -18,10 +18,10 @@
  ****************************************************************/
 package org.apache.cayenne.access.dbsync;
 
-import java.sql.SQLException;
-
 import org.apache.cayenne.access.DataNode;
 
+import java.sql.SQLException;
+
 /**
  * A strategy for auto generating a database schema on the application startup.
  * 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/60fc4d26/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/SchemaUpdateStrategyFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/SchemaUpdateStrategyFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/SchemaUpdateStrategyFactory.java
new file mode 100644
index 0000000..0508042
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/SchemaUpdateStrategyFactory.java
@@ -0,0 +1,31 @@
+/*
+ *    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.dbsync;
+
+import org.apache.cayenne.configuration.DataNodeDescriptor;
+
+/**
+ * A factory for {@link SchemaUpdateStrategy} instances.
+ *
+ * @since 4.0
+ */
+public interface SchemaUpdateStrategyFactory {
+
+    SchemaUpdateStrategy create(DataNodeDescriptor nodeDescriptor);
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/60fc4d26/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/SkipSchemaUpdateStrategy.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/SkipSchemaUpdateStrategy.java b/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/SkipSchemaUpdateStrategy.java
index 8a8f7b6..587c4b0 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/SkipSchemaUpdateStrategy.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/SkipSchemaUpdateStrategy.java
@@ -23,14 +23,12 @@ import org.apache.cayenne.access.DataNode;
 /**
  * A SchemaUpdateStrategy that does nothing. This is usually the default strategy, as in
  * most cases DB schema management is outside the scope of Cayenne.
- * 
+ *
  * @since 3.0
  */
 public class SkipSchemaUpdateStrategy implements SchemaUpdateStrategy {
 
-    /**
-     * @since 3.0
-     */
+    @Override
     public void updateSchema(DataNode dataNode) {
         // does nothing
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/60fc4d26/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/ThrowOnPartialOrCreateSchemaStrategy.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/ThrowOnPartialOrCreateSchemaStrategy.java b/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/ThrowOnPartialOrCreateSchemaStrategy.java
index bc215a7..46ea944 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/ThrowOnPartialOrCreateSchemaStrategy.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/ThrowOnPartialOrCreateSchemaStrategy.java
@@ -18,11 +18,6 @@
  ****************************************************************/
 package org.apache.cayenne.access.dbsync;
 
-import java.sql.SQLException;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
-
 import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.access.DbGenerator;
@@ -30,13 +25,18 @@ import org.apache.cayenne.map.DataMap;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
+import java.sql.SQLException;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
 /**
  * @since 3.0
  */
 public class ThrowOnPartialOrCreateSchemaStrategy extends ThrowOnPartialSchemaStrategy {
 
-    final static Log logger = LogFactory
-            .getLog(ThrowOnPartialOrCreateSchemaStrategy.class);
+    private final static Log LOGGER = LogFactory.getLog(ThrowOnPartialOrCreateSchemaStrategy.class);
+
 
     @Override
     protected void processSchemaUpdate(
@@ -46,14 +46,14 @@ public class ThrowOnPartialOrCreateSchemaStrategy extends ThrowOnPartialSchemaSt
             int entitiesSize) throws SQLException {
 
         if (mergerOnlyTable.size() == 0 && errorMessage == null) {
-            logger.info("Full schema is present");
+            LOGGER.info("Full schema is present");
         }
         else if (mergerOnlyTable.size() == entitiesSize) {
-            logger.info("No schema detected, will create mapped tables");
+            LOGGER.info("No schema detected, will create mapped tables");
             generate(dataNode);
         }
         else {
-            logger.info("Error - partial schema detected");
+            LOGGER.info("Error - partial schema detected");
 
             StringBuilder buffer = new StringBuilder("Schema mismatch detected");
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/60fc4d26/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/ThrowOnPartialSchemaStrategy.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/ThrowOnPartialSchemaStrategy.java b/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/ThrowOnPartialSchemaStrategy.java
index 051feab..4cf7304 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/ThrowOnPartialSchemaStrategy.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/ThrowOnPartialSchemaStrategy.java
@@ -18,6 +18,12 @@
  ****************************************************************/
 package org.apache.cayenne.access.dbsync;
 
+import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
 import java.sql.Connection;
 import java.sql.DatabaseMetaData;
 import java.sql.ResultSet;
@@ -26,82 +32,74 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 
-import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.access.DataNode;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
 /**
  * @since 3.0
  */
 public class ThrowOnPartialSchemaStrategy extends BaseSchemaUpdateStrategy {
 
-	final static Log logger = LogFactory.getLog(ThrowOnPartialSchemaStrategy.class);
-
-	/**
-	 * @since 3.0
-	 */
-	@Override
-	protected void processSchemaUpdate(DataNode dataNode) throws SQLException {
-
-		SchemaAnalyzer analyzer = new SchemaAnalyzer();
-
-		List<String> schemas = new ArrayList<String>();
-		DatabaseMetaData md = null;
-		try {
-
-			try (Connection connection = dataNode.getDataSource().getConnection();) {
-				md = connection.getMetaData();
-
-				try (ResultSet rs = md.getSchemas();) {
-					while (rs.next()) {
-						String schemaName = rs.getString(1);
-						schemas.add(schemaName);
-					}
-				}
-			}
-
-			analyzer.analyzeSchemas(schemas, md);
-		} catch (Exception e) {
-			logger.debug("Exception analyzing schema, ignoring", e);
-		}
-
-		Collection<DbEntity> entities = dataNode.getEntityResolver().getDbEntities();
-
-		boolean isIncluded = analyzer.compareTables(md, entities);
-
-		if (isIncluded && analyzer.getErrorMessage() == null) {
-			try {
-				analyzer.compareColumns(md);
-			} catch (SQLException e) {
-				logger.debug("Exception analyzing schema, ignoring", e);
-			}
-		}
-
-		processSchemaUpdate(dataNode, analyzer.getTableNoInDB(), analyzer.getErrorMessage(), entities.size());
-	}
-
-	protected void processSchemaUpdate(DataNode dataNode, List<String> mergerOnlyTable, String errorMessage,
-			int entitiesSize) throws SQLException {
-
-		if (mergerOnlyTable.size() == 0 && errorMessage == null) {
-			logger.info("Full schema is present");
-		} else {
-			logger.info("Error - missing or partial schema detected");
-			StringBuilder buffer = new StringBuilder("Schema mismatch detected");
-
-			if (errorMessage != null) {
-				buffer.append(": ").append(errorMessage);
-			} else if (mergerOnlyTable.size() == entitiesSize) {
-				buffer.append(": no schema found");
-			} else {
-				if (mergerOnlyTable.size() > 0) {
-					buffer.append(": missing table '").append(mergerOnlyTable.get(0)).append('\'');
-				}
-			}
-
-			throw new CayenneRuntimeException(buffer.toString());
-		}
-	}
+    private final static Log LOGGER = LogFactory.getLog(ThrowOnPartialSchemaStrategy.class);
+
+
+    @Override
+    protected void processSchemaUpdate(DataNode dataNode) throws SQLException {
+
+        SchemaAnalyzer analyzer = new SchemaAnalyzer();
+
+        List<String> schemas = new ArrayList<>();
+        DatabaseMetaData md = null;
+        try {
+
+            try (Connection connection = dataNode.getDataSource().getConnection();) {
+                md = connection.getMetaData();
+
+                try (ResultSet rs = md.getSchemas();) {
+                    while (rs.next()) {
+                        String schemaName = rs.getString(1);
+                        schemas.add(schemaName);
+                    }
+                }
+            }
+
+            analyzer.analyzeSchemas(schemas, md);
+        } catch (Exception e) {
+            LOGGER.debug("Exception analyzing schema, ignoring", e);
+        }
+
+        Collection<DbEntity> entities = dataNode.getEntityResolver().getDbEntities();
+
+        boolean isIncluded = analyzer.compareTables(md, entities);
+
+        if (isIncluded && analyzer.getErrorMessage() == null) {
+            try {
+                analyzer.compareColumns(md);
+            } catch (SQLException e) {
+                LOGGER.debug("Exception analyzing schema, ignoring", e);
+            }
+        }
+
+        processSchemaUpdate(dataNode, analyzer.getTableNoInDB(), analyzer.getErrorMessage(), entities.size());
+    }
+
+    protected void processSchemaUpdate(DataNode dataNode, List<String> mergerOnlyTable, String errorMessage, int entitiesSize)
+            throws SQLException {
+
+        if (mergerOnlyTable.size() == 0 && errorMessage == null) {
+            LOGGER.info("Full schema is present");
+        } else {
+            LOGGER.info("Error - missing or partial schema detected");
+            StringBuilder buffer = new StringBuilder("Schema mismatch detected");
+
+            if (errorMessage != null) {
+                buffer.append(": ").append(errorMessage);
+            } else if (mergerOnlyTable.size() == entitiesSize) {
+                buffer.append(": no schema found");
+            } else {
+                if (mergerOnlyTable.size() > 0) {
+                    buffer.append(": missing table '").append(mergerOnlyTable.get(0)).append('\'');
+                }
+            }
+
+            throw new CayenneRuntimeException(buffer.toString());
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/60fc4d26/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/DefaultDataNodeFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/DefaultDataNodeFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/DefaultDataNodeFactory.java
index 07c7fdf..d419eb9 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/DefaultDataNodeFactory.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/DefaultDataNodeFactory.java
@@ -18,10 +18,8 @@
  ****************************************************************/
 package org.apache.cayenne.configuration.server;
 
-import javax.sql.DataSource;
-
 import org.apache.cayenne.access.DataNode;
-import org.apache.cayenne.access.dbsync.SchemaUpdateStrategy;
+import org.apache.cayenne.access.dbsync.SchemaUpdateStrategyFactory;
 import org.apache.cayenne.access.jdbc.SQLTemplateProcessor;
 import org.apache.cayenne.access.jdbc.reader.RowReaderFactory;
 import org.apache.cayenne.access.translator.batch.BatchTranslatorFactory;
@@ -31,6 +29,8 @@ import org.apache.cayenne.di.AdhocObjectFactory;
 import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.log.JdbcEventLogger;
 
+import javax.sql.DataSource;
+
 /**
  * @since 4.0
  */
@@ -58,7 +58,7 @@ public class DefaultDataNodeFactory implements DataNodeFactory {
     protected AdhocObjectFactory objectFactory;
 
     @Inject
-    protected SchemaUpdateStrategy defaultSchemaUpdateStrategy;
+    protected SchemaUpdateStrategyFactory schemaUpdateStrategyFactory;
     
     @Inject
     protected SQLTemplateProcessor sqlTemplateProcessor;
@@ -81,20 +81,8 @@ public class DefaultDataNodeFactory implements DataNodeFactory {
         dataNode.setDataSourceFactory(nodeDescriptor.getDataSourceFactoryType());
         dataNode.setDataSource(dataSource);
 
-        // schema update strategy
-        String schemaUpdateStrategyType = nodeDescriptor.getSchemaUpdateStrategyType();
-
-        if (schemaUpdateStrategyType == null) {
-            dataNode.setSchemaUpdateStrategy(defaultSchemaUpdateStrategy);
-            dataNode.setSchemaUpdateStrategyName(defaultSchemaUpdateStrategy.getClass().getName());
-        } else {
-            SchemaUpdateStrategy strategy = objectFactory.newInstance(SchemaUpdateStrategy.class,
-                    schemaUpdateStrategyType);
-            dataNode.setSchemaUpdateStrategyName(schemaUpdateStrategyType);
-            dataNode.setSchemaUpdateStrategy(strategy);
-        }
+        dataNode.setSchemaUpdateStrategy(schemaUpdateStrategyFactory.create(nodeDescriptor));
 
-        // DbAdapter
         dataNode.setAdapter(adapterFactory.createAdapter(nodeDescriptor, dataSource));
 
         return dataNode;

http://git-wip-us.apache.org/repos/asf/cayenne/blob/60fc4d26/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerModule.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerModule.java b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerModule.java
index 36516d9..286edce 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerModule.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerModule.java
@@ -22,9 +22,8 @@ import org.apache.cayenne.DataChannel;
 import org.apache.cayenne.access.DataDomain;
 import org.apache.cayenne.access.DefaultObjectMapRetainStrategy;
 import org.apache.cayenne.access.ObjectMapRetainStrategy;
-import org.apache.cayenne.tx.TransactionFilter;
-import org.apache.cayenne.access.dbsync.SchemaUpdateStrategy;
-import org.apache.cayenne.access.dbsync.SkipSchemaUpdateStrategy;
+import org.apache.cayenne.access.dbsync.DefaultSchemaUpdateStrategyFactory;
+import org.apache.cayenne.access.dbsync.SchemaUpdateStrategyFactory;
 import org.apache.cayenne.access.jdbc.SQLTemplateProcessor;
 import org.apache.cayenne.access.jdbc.reader.DefaultRowReaderFactory;
 import org.apache.cayenne.access.jdbc.reader.RowReaderFactory;
@@ -99,6 +98,7 @@ import org.apache.cayenne.resource.ResourceLocator;
 import org.apache.cayenne.tx.DefaultTransactionFactory;
 import org.apache.cayenne.tx.DefaultTransactionManager;
 import org.apache.cayenne.tx.TransactionFactory;
+import org.apache.cayenne.tx.TransactionFilter;
 import org.apache.cayenne.tx.TransactionManager;
 import org.apache.cayenne.velocity.VelocitySQLTemplateProcessor;
 
@@ -207,10 +207,7 @@ public class ServerModule implements Module {
 		// call depending on DataNodeDescriptor data and the environment
 		binder.bind(DataSourceFactory.class).to(DelegatingDataSourceFactory.class);
 
-		// a default SchemaUpdateStrategy (used when no explicit strategy is
-		// specified in
-		// XML)
-		binder.bind(SchemaUpdateStrategy.class).to(SkipSchemaUpdateStrategy.class);
+		binder.bind(SchemaUpdateStrategyFactory.class).to(DefaultSchemaUpdateStrategyFactory.class);
 
 		// a default DBAdapterFactory used to load custom and automatic
 		// DbAdapters

http://git-wip-us.apache.org/repos/asf/cayenne/blob/60fc4d26/cayenne-server/src/test/java/org/apache/cayenne/access/dbsync/SchemaUpdateStrategyBase.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/dbsync/SchemaUpdateStrategyBase.java b/cayenne-server/src/test/java/org/apache/cayenne/access/dbsync/SchemaUpdateStrategyBase.java
index 7c63192..3e35449 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/dbsync/SchemaUpdateStrategyBase.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/dbsync/SchemaUpdateStrategyBase.java
@@ -18,15 +18,6 @@
  ****************************************************************/
 package org.apache.cayenne.access.dbsync;
 
-import java.sql.Connection;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Map.Entry;
-
 import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.access.DataNode;
@@ -36,6 +27,15 @@ import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.query.SQLTemplate;
 import org.apache.cayenne.unit.di.server.ServerCase;
 
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
 public class SchemaUpdateStrategyBase extends ServerCase {
 
 	@Inject
@@ -59,7 +59,6 @@ public class SchemaUpdateStrategyBase extends ServerCase {
 	}
 
 	protected void setStrategy(Class<? extends SchemaUpdateStrategy> type) throws Exception {
-		node.setSchemaUpdateStrategyName(type.getName());
 		node.setSchemaUpdateStrategy(type.newInstance());
 	}
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/60fc4d26/cayenne-server/src/test/java/org/apache/cayenne/access/dbsync/TstSchemaUpdateStrategy.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/dbsync/TstSchemaUpdateStrategy.java b/cayenne-server/src/test/java/org/apache/cayenne/access/dbsync/TstSchemaUpdateStrategy.java
index 8e32500..a23cb8c 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/dbsync/TstSchemaUpdateStrategy.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/dbsync/TstSchemaUpdateStrategy.java
@@ -19,16 +19,10 @@
 package org.apache.cayenne.access.dbsync;
 
 import org.apache.cayenne.access.DataNode;
-import org.apache.cayenne.access.dbsync.SchemaUpdateStrategy;
 
 public class TstSchemaUpdateStrategy implements SchemaUpdateStrategy {
 
-    public TstSchemaUpdateStrategy() {
-    }
-
+    @Override
     public void updateSchema(DataNode dataNode) {
     }
-
-    public void generateUpdateSchema(DataNode dataNode) {
-    }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/60fc4d26/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/DataDomainProviderTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/DataDomainProviderTest.java b/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/DataDomainProviderTest.java
index b0ba2b0..b8f4f7e 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/DataDomainProviderTest.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/DataDomainProviderTest.java
@@ -22,7 +22,8 @@ import org.apache.cayenne.ConfigurationException;
 import org.apache.cayenne.DataChannel;
 import org.apache.cayenne.access.DataDomain;
 import org.apache.cayenne.access.DataNode;
-import org.apache.cayenne.access.dbsync.SchemaUpdateStrategy;
+import org.apache.cayenne.access.dbsync.DefaultSchemaUpdateStrategyFactory;
+import org.apache.cayenne.access.dbsync.SchemaUpdateStrategyFactory;
 import org.apache.cayenne.access.dbsync.SkipSchemaUpdateStrategy;
 import org.apache.cayenne.access.dbsync.ThrowOnPartialOrCreateSchemaStrategy;
 import org.apache.cayenne.access.jdbc.SQLTemplateProcessor;
@@ -157,6 +158,7 @@ public class DataDomainProviderTest {
 
 				binder.bind(EventManager.class).toInstance(eventManager);
 				binder.bind(EntitySorter.class).toInstance(new AshwoodEntitySorter());
+				binder.bind(SchemaUpdateStrategyFactory.class).to(DefaultSchemaUpdateStrategyFactory.class);
 
 				final ResourceLocator locator = new ClassLoaderResourceLocator(classLoaderManager) {
 
@@ -179,7 +181,6 @@ public class DataDomainProviderTest {
 				binder.bind(ConfigurationNameMapper.class).to(DefaultConfigurationNameMapper.class);
 				binder.bind(DataChannelDescriptorMerger.class).to(DefaultDataChannelDescriptorMerger.class);
 				binder.bind(DataChannelDescriptorLoader.class).toInstance(testLoader);
-				binder.bind(SchemaUpdateStrategy.class).toInstance(new SkipSchemaUpdateStrategy());
 				binder.bind(DbAdapterFactory.class).to(DefaultDbAdapterFactory.class);
 				binder.bind(RuntimeProperties.class).to(DefaultRuntimeProperties.class);
 				binder.bind(BatchTranslatorFactory.class).to(DefaultBatchTranslatorFactory.class);
@@ -221,7 +222,6 @@ public class DataDomainProviderTest {
 		assertNotNull(node1.getDataSource());
 		assertEquals(nodeDescriptor1.getParameters(), node1.getDataSourceLocation());
 
-		assertEquals(nodeDescriptor1.getSchemaUpdateStrategyType(), node1.getSchemaUpdateStrategyName());
 		assertNotNull(node1.getSchemaUpdateStrategy());
 		assertEquals(nodeDescriptor1.getSchemaUpdateStrategyType(), node1.getSchemaUpdateStrategy().getClass()
 				.getName());
@@ -237,7 +237,6 @@ public class DataDomainProviderTest {
 		assertNull(node2.getDataSourceFactory());
 		assertNotNull(node2.getDataSource());
 		assertEquals(nodeDescriptor2.getParameters(), node2.getDataSourceLocation());
-		assertEquals(SkipSchemaUpdateStrategy.class.getName(), node2.getSchemaUpdateStrategyName());
 		assertNotNull(node2.getSchemaUpdateStrategy());
 		assertEquals(SkipSchemaUpdateStrategy.class.getName(), node2.getSchemaUpdateStrategy().getClass().getName());
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/60fc4d26/docs/doc/src/main/resources/RELEASE-NOTES.txt
----------------------------------------------------------------------
diff --git a/docs/doc/src/main/resources/RELEASE-NOTES.txt b/docs/doc/src/main/resources/RELEASE-NOTES.txt
index b8811de..de5376d 100644
--- a/docs/doc/src/main/resources/RELEASE-NOTES.txt
+++ b/docs/doc/src/main/resources/RELEASE-NOTES.txt
@@ -56,6 +56,7 @@ CAY-2108 cayenne-di: StackOverflow for decorator that takes Provider of the dele
 CAY-2110 Obfuscated exception when processing iterated results
 CAY-2119 ProjectUpgrader test failure (Windows)
 CAY-2122 Vertical Inheritance: Cannot Insert Record For Implementing Class with Attribute And Relationship
+CAY-2125 SchemaUpdateStrategy doesn't work with multiple DataNodes
 
 ----------------------------------
 Release: 4.0.M3

http://git-wip-us.apache.org/repos/asf/cayenne/blob/60fc4d26/docs/doc/src/main/resources/UPGRADE.txt
----------------------------------------------------------------------
diff --git a/docs/doc/src/main/resources/UPGRADE.txt b/docs/doc/src/main/resources/UPGRADE.txt
index a99e083..50e3479 100644
--- a/docs/doc/src/main/resources/UPGRADE.txt
+++ b/docs/doc/src/main/resources/UPGRADE.txt
@@ -6,6 +6,10 @@ IMPORTANT: be sure to read all notes for the intermediate releases between your
 -------------------------------------------------------------------------------
 UPGRADING TO 4.0.M4
 
+* Per CAY-2125 we no longer inject SchemaUpdateStrategy directly. Instead SchemaUpgradeStrategyFactory is injected.
+  If you have your own modules with SchemaUpdateStrategy injection, it will be ignored. So please review your
+  DI code and update it to use SchemaUpgradeStrategyFactory (or likely its subclass).
+
 * Per CAY-2060 4.0.M4 changes the way queries are stored in the mapping files, so all existing *.map.xml files should be upgraded.
   To do that open each of your existing projects in the new CayenneModeler. Agree to perform an upgrade when asked.