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/03 18:37:41 UTC

cayenne git commit: CAY-2115 DbLoader - allow loading DataMap without Obj layer

Repository: cayenne
Updated Branches:
  refs/heads/master 756e06b33 -> ecbacdf00


CAY-2115 DbLoader - allow loading DataMap without Obj layer

* DbLoader API cleanup


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

Branch: refs/heads/master
Commit: ecbacdf00c3ab2db548e2e39f2a90756484ab8f6
Parents: 756e06b
Author: Andrus Adamchik <an...@objectstyle.com>
Authored: Mon Oct 3 18:49:03 2016 +0300
Committer: Andrus Adamchik <an...@objectstyle.com>
Committed: Mon Oct 3 21:34:20 2016 +0300

----------------------------------------------------------------------
 .../cayenne/dbsync/reverse/db/DbLoader.java     | 128 ++++-------------
 .../reverse/db/DbLoaderConfiguration.java       |  39 +----
 .../reverse/db/ManyToManyCandidateEntity.java   | 143 -------------------
 .../apache/cayenne/dbsync/merge/MergeCase.java  |   6 +-
 .../db/ManyToManyCandidateEntityTest.java       | 113 ---------------
 .../db/cayenne-relationship-optimisation.xml    |   4 -
 .../db/relationship-optimisation.map.xml        |  43 ------
 .../tools/dbimport/DbImportConfiguration.java   |   5 +-
 .../tools/dbimport/DefaultDbImportAction.java   |  28 +++-
 .../dbimport/ManyToManyCandidateEntity.java     | 143 +++++++++++++++++++
 .../dbimport/ManyToManyCandidateEntityTest.java | 113 +++++++++++++++
 .../cayenne-relationship-optimisation.xml       |   4 +
 .../dbimport/relationship-optimisation.map.xml  |  43 ++++++
 .../modeler/dialog/db/MergerOptions.java        |   8 +-
 .../dialog/db/ModelerDbImportAction.java        |   4 +-
 .../modeler/dialog/db/ModelerDbLoader.java      |   7 +-
 .../dialog/db/ReverseEngineeringController.java |   5 +-
 17 files changed, 378 insertions(+), 458 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/ecbacdf0/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/db/DbLoader.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/db/DbLoader.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/db/DbLoader.java
index 7d32671..6bc6a65 100644
--- a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/db/DbLoader.java
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/db/DbLoader.java
@@ -1,21 +1,21 @@
-/*****************************************************************
- *   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
+/*
+ * 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
+ *      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.
- ****************************************************************/
+ *    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.dbsync.reverse.db;
 
 import org.apache.cayenne.dba.DbAdapter;
@@ -44,9 +44,7 @@ import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashMap;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -87,74 +85,6 @@ public class DbLoader {
         return strings;
     }
 
-    private static Collection<ObjEntity> loadObjEntities(
-            DataMap map,
-            DbLoaderConfiguration config,
-            Collection<DbEntity> entities,
-            ObjectNameGenerator nameGenerator) {
-
-        if (entities.isEmpty()) {
-            return Collections.emptyList();
-        }
-
-        Collection<ObjEntity> loadedEntities = new ArrayList<>(entities.size());
-
-        // doLoad empty ObjEntities for all the tables
-        for (DbEntity dbEntity : entities) {
-
-            // check if there are existing entities
-
-            // TODO: performance. This is an O(n^2) search and it shows on
-            // YourKit profiles. Pre-cache mapped entities perhaps (?)
-            Collection<ObjEntity> existing = map.getMappedEntities(dbEntity);
-            if (!existing.isEmpty()) {
-                loadedEntities.addAll(existing);
-                continue;
-            }
-
-            ObjEntity objEntity = new ObjEntity();
-            objEntity.setName(NameBuilder
-                    .builder(objEntity, map)
-                    .baseName(nameGenerator.objEntityName(dbEntity))
-                    .name());
-
-            objEntity.setDbEntity(dbEntity);
-            objEntity.setClassName(config.getGenericClassName() != null ? config.getGenericClassName() : map
-                    .getNameWithDefaultPackage(objEntity.getName()));
-
-            map.addObjEntity(objEntity);
-            loadedEntities.add(objEntity);
-        }
-
-        return loadedEntities;
-    }
-
-    /**
-     * Flattens many-to-many relationships in the generated model.
-     */
-    public static void flattenManyToManyRelationships(DataMap map, Collection<ObjEntity> loadedObjEntities,
-                                                      ObjectNameGenerator objectNameGenerator) {
-        if (loadedObjEntities.isEmpty()) {
-            return;
-        }
-        Collection<ObjEntity> entitiesForDelete = new LinkedList<>();
-
-        for (ObjEntity curEntity : loadedObjEntities) {
-            ManyToManyCandidateEntity entity = ManyToManyCandidateEntity.build(curEntity);
-
-            if (entity != null) {
-                entity.optimizeRelationships(objectNameGenerator);
-                entitiesForDelete.add(curEntity);
-            }
-        }
-
-        // remove needed entities
-        for (ObjEntity curDeleteEntity : entitiesForDelete) {
-            map.removeObjEntity(curDeleteEntity.getName(), true);
-        }
-        loadedObjEntities.removeAll(entitiesForDelete);
-    }
-
     private static int getDirection(short type) {
         switch (type) {
             case DatabaseMetaData.procedureColumnIn:
@@ -442,13 +372,18 @@ public class DbLoader {
     }
 
     /**
-     * Performs database reverse engineering based on the specified config and
-     * fills the specified DataMap object with DB and object mapping info.
+     * Performs database reverse engineering to match the specified catalog,
+     * schema, table name and table type patterns and fills the specified
+     * DataMap object with DB and object mapping info.
      *
      * @since 4.0
      */
     public void load(DataMap dataMap, DbLoaderConfiguration config) throws SQLException {
-        LOGGER.info("Schema loading...");
+        loadDbEntities(dataMap, config);
+        loadProcedures(dataMap, config);
+    }
+
+    protected void loadDbEntities(DataMap dataMap, DbLoaderConfiguration config) throws SQLException {
 
         String[] types = getTableTypes(config);
 
@@ -469,21 +404,6 @@ public class DbLoader {
                 new DbAttributesPerSchemaLoader(catalog, schema, getMetaData(), adapter, filter));
     }
 
-    /**
-     * Performs database reverse engineering to match the specified catalog,
-     * schema, table name and table type patterns and fills the specified
-     * DataMap object with DB and object mapping info.
-     *
-     * @since 4.0
-     */
-    public DataMap load(DbLoaderConfiguration config) throws SQLException {
-
-        DataMap dataMap = new DataMap();
-        load(dataMap, config);
-        loadProcedures(dataMap, config);
-
-        return dataMap;
-    }
 
     /**
      * Loads database stored procedures into the DataMap.

http://git-wip-us.apache.org/repos/asf/cayenne/blob/ecbacdf0/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/db/DbLoaderConfiguration.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/db/DbLoaderConfiguration.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/db/DbLoaderConfiguration.java
index 9784b1c..e460cb9 100644
--- a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/db/DbLoaderConfiguration.java
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/db/DbLoaderConfiguration.java
@@ -28,15 +28,6 @@ import org.apache.cayenne.dbsync.reverse.filters.PatternFilter;
 public class DbLoaderConfiguration {
 
 
-    private String genericClassName;
-
-    /**
-     * Java class implementing org.apache.cayenne.map.naming.NamingStrategy.
-     * This is used to specify how ObjEntities will be mapped from the imported
-     * DB schema.
-     */
-    private String namingStrategy;
-
     private Boolean skipRelationshipsLoading;
 
     private Boolean skipPrimaryKeyLoading;
@@ -45,20 +36,6 @@ public class DbLoaderConfiguration {
 
     private FiltersConfig filtersConfig;
 
-    /**
-     * Returns a name of a generic class that should be used for all ObjEntities. The most common generic class is
-     * {@link org.apache.cayenne.CayenneDataObject}. If generic class name is
-     * null (which is the default), DbLoader will assign each entity a unique
-     * class name derived from the table name.
-     */
-    public String getGenericClassName() {
-        return genericClassName;
-    }
-
-    public void setGenericClassName(String genericClassName) {
-        this.genericClassName = genericClassName;
-    }
-
     public String[] getTableTypes() {
         return tableTypes;
     }
@@ -67,14 +44,6 @@ public class DbLoaderConfiguration {
         this.tableTypes = tableTypes;
     }
 
-    public String getNamingStrategy() {
-        return namingStrategy;
-    }
-
-    public void setNamingStrategy(String namingStrategy) {
-        this.namingStrategy = namingStrategy;
-    }
-
     public FiltersConfig getFiltersConfig() {
         if (filtersConfig == null) {
             // this case is used often in tests where config not initialized properly
@@ -99,10 +68,6 @@ public class DbLoaderConfiguration {
         this.skipRelationshipsLoading = skipRelationshipsLoading;
     }
 
-    public void setSkipPrimaryKeyLoading(Boolean skipPrimaryKeyLoading) {
-        this.skipPrimaryKeyLoading = skipPrimaryKeyLoading;
-    }
-
     public boolean getSkipPrimaryKeyLoading() {
         return skipPrimaryKeyLoading;
     }
@@ -111,6 +76,10 @@ public class DbLoaderConfiguration {
         return skipPrimaryKeyLoading != null && skipPrimaryKeyLoading;
     }
 
+    public void setSkipPrimaryKeyLoading(Boolean skipPrimaryKeyLoading) {
+        this.skipPrimaryKeyLoading = skipPrimaryKeyLoading;
+    }
+
     @Override
     public String toString() {
         String res = "EntitiesFilters: " + getFiltersConfig();

http://git-wip-us.apache.org/repos/asf/cayenne/blob/ecbacdf0/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/db/ManyToManyCandidateEntity.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/db/ManyToManyCandidateEntity.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/db/ManyToManyCandidateEntity.java
deleted file mode 100644
index f93c7cf..0000000
--- a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/db/ManyToManyCandidateEntity.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*****************************************************************
- *   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.dbsync.reverse.db;
-
-import org.apache.cayenne.dbsync.naming.NameBuilder;
-import org.apache.cayenne.dbsync.naming.ObjectNameGenerator;
-import org.apache.cayenne.map.DbRelationship;
-import org.apache.cayenne.map.ObjEntity;
-import org.apache.cayenne.map.ObjRelationship;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Class represent ObjEntity that may be optimized using flattened relationships
- * as many to many table
- */
-class ManyToManyCandidateEntity {
-
-    private static final Log LOG = LogFactory.getLog(ManyToManyCandidateEntity.class);
-
-    private final ObjEntity joinEntity;
-
-    private final DbRelationship dbRel1;
-    private final DbRelationship dbRel2;
-
-    private final ObjEntity entity1;
-    private final ObjEntity entity2;
-
-    private final DbRelationship reverseRelationship1;
-    private final DbRelationship reverseRelationship2;
-
-    private ManyToManyCandidateEntity(ObjEntity entityValue, List<ObjRelationship> relationships) {
-        joinEntity = entityValue;
-
-        ObjRelationship rel1 = relationships.get(0);
-        ObjRelationship rel2 = relationships.get(1);
-
-        dbRel1 = rel1.getDbRelationships().get(0);
-        dbRel2 = rel2.getDbRelationships().get(0);
-
-        reverseRelationship1 = dbRel1.getReverseRelationship();
-        reverseRelationship2 = dbRel2.getReverseRelationship();
-
-        entity1 = rel1.getTargetEntity();
-        entity2 = rel2.getTargetEntity();
-    }
-
-    /**
-     * Method check - if current entity represent many to many temporary table
-     *
-     * @return true if current entity is represent many to many table; otherwise returns false
-     */
-    public static ManyToManyCandidateEntity build(ObjEntity joinEntity) {
-        ArrayList<ObjRelationship> relationships = new ArrayList<ObjRelationship>(joinEntity.getRelationships());
-        if (relationships.size() != 2 || (relationships.get(0).getDbRelationships().isEmpty() || relationships.get(1).getDbRelationships().isEmpty())) {
-            return null;
-        }
-
-        ManyToManyCandidateEntity candidateEntity = new ManyToManyCandidateEntity(joinEntity, relationships);
-        if (candidateEntity.isManyToMany()) {
-            return candidateEntity;
-        }
-
-        return null;
-    }
-
-    private boolean isManyToMany() {
-        boolean isNotHaveAttributes = joinEntity.getAttributes().size() == 0;
-
-        return isNotHaveAttributes
-                && reverseRelationship1 != null && reverseRelationship1.isToDependentPK()
-                && reverseRelationship2 != null && reverseRelationship2.isToDependentPK()
-                && entity1 != null && entity2 != null;
-    }
-
-    private void addFlattenedRelationship(ObjectNameGenerator nameGenerator, ObjEntity srcEntity, ObjEntity dstEntity,
-                                          DbRelationship rel1, DbRelationship rel2) {
-
-        if (rel1.getSourceAttributes().isEmpty() && rel2.getTargetAttributes().isEmpty()) {
-            LOG.warn("Wrong call ManyToManyCandidateEntity.addFlattenedRelationship(... , " + srcEntity.getName()
-                    + ", " + dstEntity.getName() + ", ...)");
-
-            return;
-        }
-
-        ExportedKey key = new ExportedKey(
-                rel1.getSourceEntity().getName(),
-                rel1.getSourceAttributes().iterator().next().getName(),
-                null,
-                rel2.getTargetEntity().getName(),
-                rel2.getTargetAttributes().iterator().next().getName(),
-                null,
-                (short) 1);
-
-        ObjRelationship newRelationship = new ObjRelationship();
-        newRelationship.setName(NameBuilder
-                .builder(newRelationship, srcEntity)
-                .baseName(nameGenerator.dbRelationshipName(key, true))
-                .name());
-
-        newRelationship.setSourceEntity(srcEntity);
-        newRelationship.setTargetEntityName(dstEntity);
-
-        newRelationship.addDbRelationship(rel1);
-        newRelationship.addDbRelationship(rel2);
-
-        srcEntity.addRelationship(newRelationship);
-    }
-
-    /**
-     * Method make direct relationships between 2 entities and remove relationships to
-     * many to many entity
-     *
-     * @param nameGenerator
-     */
-    public void optimizeRelationships(ObjectNameGenerator nameGenerator) {
-        entity1.removeRelationship(reverseRelationship1.getName());
-        entity2.removeRelationship(reverseRelationship2.getName());
-
-        addFlattenedRelationship(nameGenerator, entity1, entity2, reverseRelationship1, dbRel2);
-        addFlattenedRelationship(nameGenerator, entity2, entity1, reverseRelationship2, dbRel1);
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/ecbacdf0/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/MergeCase.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/MergeCase.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/MergeCase.java
index 6c0fca2..0f4f1b1 100644
--- a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/MergeCase.java
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/MergeCase.java
@@ -107,13 +107,13 @@ public abstract class MergeCase extends DbSyncCase {
         DbLoaderConfiguration loaderConfiguration = new DbLoaderConfiguration();
         loaderConfiguration.setFiltersConfig(filters);
 
-        DataMap dbImport;
+        DataMap dbImport = new DataMap();
         try (Connection conn = node.getDataSource().getConnection();) {
-            dbImport = new DbLoader(conn,
+            new DbLoader(conn,
                     node.getAdapter(),
                     new LoggingDbLoaderDelegate(LogFactory.getLog(DbLoader.class)),
                     new DefaultObjectNameGenerator())
-                    .load(loaderConfiguration);
+                    .load(dbImport, loaderConfiguration);
 
         } catch (SQLException e) {
             throw new CayenneRuntimeException("Can't doLoad dataMap from db.", e);

http://git-wip-us.apache.org/repos/asf/cayenne/blob/ecbacdf0/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/db/ManyToManyCandidateEntityTest.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/db/ManyToManyCandidateEntityTest.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/db/ManyToManyCandidateEntityTest.java
deleted file mode 100644
index d56560a..0000000
--- a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/db/ManyToManyCandidateEntityTest.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*****************************************************************
- *   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.dbsync.reverse.db;
-
-import org.apache.cayenne.configuration.ConfigurationNameMapper;
-import org.apache.cayenne.configuration.ConfigurationTree;
-import org.apache.cayenne.configuration.DataChannelDescriptor;
-import org.apache.cayenne.configuration.DataMapLoader;
-import org.apache.cayenne.configuration.DefaultConfigurationNameMapper;
-import org.apache.cayenne.configuration.XMLDataChannelDescriptorLoader;
-import org.apache.cayenne.configuration.XMLDataMapLoader;
-import org.apache.cayenne.di.AdhocObjectFactory;
-import org.apache.cayenne.di.Binder;
-import org.apache.cayenne.di.ClassLoaderManager;
-import org.apache.cayenne.di.DIBootstrap;
-import org.apache.cayenne.di.Injector;
-import org.apache.cayenne.di.Module;
-import org.apache.cayenne.di.spi.DefaultAdhocObjectFactory;
-import org.apache.cayenne.di.spi.DefaultClassLoaderManager;
-import org.apache.cayenne.map.DataMap;
-import org.apache.cayenne.map.ObjEntity;
-import org.apache.cayenne.map.Relationship;
-import org.apache.cayenne.dbsync.naming.LegacyObjectNameGenerator;
-import org.apache.cayenne.resource.URLResource;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.net.URL;
-import java.util.ArrayList;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-
-public class ManyToManyCandidateEntityTest {
-
-    private DataMap map;
-
-    @Before
-    public void setUp() throws Exception {
-        Module testModule = new Module() {
-
-            public void configure(Binder binder) {
-                binder.bind(ClassLoaderManager.class).to(DefaultClassLoaderManager.class);
-                binder.bind(AdhocObjectFactory.class).to(DefaultAdhocObjectFactory.class);
-                binder.bind(DataMapLoader.class).to(XMLDataMapLoader.class);
-                binder.bind(ConfigurationNameMapper.class).to(DefaultConfigurationNameMapper.class);
-            }
-        };
-
-        Injector injector = DIBootstrap.createInjector(testModule);
-
-        // create and initialize loader instance to test
-        XMLDataChannelDescriptorLoader loader = new XMLDataChannelDescriptorLoader();
-        injector.injectMembers(loader);
-
-        String testConfigName = "relationship-optimisation";
-        URL url = getClass().getResource("cayenne-" + testConfigName + ".xml");
-
-        ConfigurationTree<DataChannelDescriptor> tree = loader.load(new URLResource(url));
-
-        map = tree.getRootNode().getDataMap(testConfigName);
-    }
-
-    @Test
-    public void testMatchingForManyToManyEntity() throws Exception {
-        ObjEntity manyToManyEntity = map.getObjEntity("Table1Table2");
-
-        assertNotNull(ManyToManyCandidateEntity.build(manyToManyEntity));
-    }
-
-    @Test
-    public void testMatchingForNotManyToManyEntity() throws Exception {
-        ObjEntity entity = map.getObjEntity("Table1");
-
-        assertNull(ManyToManyCandidateEntity.build(entity));
-    }
-
-    @Test
-    public void testOptimisationForManyToManyEntity() {
-        ObjEntity manyToManyEntity = map.getObjEntity("Table1Table2");
-
-        ManyToManyCandidateEntity.build(manyToManyEntity).optimizeRelationships(new LegacyObjectNameGenerator());
-
-        ObjEntity table1Entity = map.getObjEntity("Table1");
-        ObjEntity table2Entity = map.getObjEntity("Table2");
-
-        assertEquals(1, table1Entity.getRelationships().size());
-        assertEquals(table2Entity, new ArrayList<Relationship>(table1Entity.getRelationships()).get(0)
-                .getTargetEntity());
-
-        assertEquals(1, table2Entity.getRelationships().size());
-        assertEquals(table1Entity, new ArrayList<Relationship>(table2Entity.getRelationships()).get(0)
-                .getTargetEntity());
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/ecbacdf0/cayenne-dbsync/src/test/resources/org/apache/cayenne/dbsync/reverse/db/cayenne-relationship-optimisation.xml
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/resources/org/apache/cayenne/dbsync/reverse/db/cayenne-relationship-optimisation.xml b/cayenne-dbsync/src/test/resources/org/apache/cayenne/dbsync/reverse/db/cayenne-relationship-optimisation.xml
deleted file mode 100644
index d4fea49..0000000
--- a/cayenne-dbsync/src/test/resources/org/apache/cayenne/dbsync/reverse/db/cayenne-relationship-optimisation.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<domain project-version="6">
-	<map name="relationship-optimisation"/>
-</domain>

http://git-wip-us.apache.org/repos/asf/cayenne/blob/ecbacdf0/cayenne-dbsync/src/test/resources/org/apache/cayenne/dbsync/reverse/db/relationship-optimisation.map.xml
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/resources/org/apache/cayenne/dbsync/reverse/db/relationship-optimisation.map.xml b/cayenne-dbsync/src/test/resources/org/apache/cayenne/dbsync/reverse/db/relationship-optimisation.map.xml
deleted file mode 100644
index e68645f..0000000
--- a/cayenne-dbsync/src/test/resources/org/apache/cayenne/dbsync/reverse/db/relationship-optimisation.map.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<data-map xmlns="http://cayenne.apache.org/schema/3.0/modelMap"
-	 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	 xsi:schemaLocation="http://cayenne.apache.org/schema/3.0/modelMap http://cayenne.apache.org/schema/3.0/modelMap.xsd"
-	 project-version="6">
-	<property name="defaultPackage" value="com.objectstyle"/>
-	<db-entity name="table1" catalog="many_to_many_test">
-		<db-attribute name="id1" type="INTEGER" isPrimaryKey="true" isMandatory="true" length="10"/>
-		<db-attribute name="table1col" type="VARCHAR" length="45"/>
-	</db-entity>
-	<db-entity name="table1_table2" catalog="many_to_many_test">
-		<db-attribute name="fk1" type="INTEGER" isPrimaryKey="true" isMandatory="true" length="10"/>
-		<db-attribute name="fk2" type="INTEGER" isPrimaryKey="true" isMandatory="true" length="10"/>
-	</db-entity>
-	<db-entity name="table2" catalog="many_to_many_test">
-		<db-attribute name="id2" type="INTEGER" isPrimaryKey="true" isMandatory="true" length="10"/>
-		<db-attribute name="table2col" type="VARCHAR" length="45"/>
-	</db-entity>
-	<obj-entity name="Table1" className="com.objectstyle.Table1" dbEntityName="table1">
-		<obj-attribute name="table1col" type="java.lang.String" db-attribute-path="table1col"/>
-	</obj-entity>
-	<obj-entity name="Table1Table2" className="com.objectstyle.Table1Table2" dbEntityName="table1_table2">
-	</obj-entity>
-	<obj-entity name="Table2" className="com.objectstyle.Table2" dbEntityName="table2">
-		<obj-attribute name="table2col" type="java.lang.String" db-attribute-path="table2col"/>
-	</obj-entity>
-	<db-relationship name="table1Table2Array" source="table1" target="table1_table2" toDependentPK="true" toMany="true">
-		<db-attribute-pair source="id1" target="fk1"/>
-	</db-relationship>
-	<db-relationship name="toTable1" source="table1_table2" target="table1" toMany="false">
-		<db-attribute-pair source="fk1" target="id1"/>
-	</db-relationship>
-	<db-relationship name="toTable2" source="table1_table2" target="table2" toMany="false">
-		<db-attribute-pair source="fk2" target="id2"/>
-	</db-relationship>
-	<db-relationship name="table1Table2Array" source="table2" target="table1_table2" toDependentPK="true" toMany="true">
-		<db-attribute-pair source="id2" target="fk2"/>
-	</db-relationship>
-	<obj-relationship name="table1Table2Array" source="Table1" target="Table1Table2" deleteRule="Deny" db-relationship-path="table1Table2Array"/>
-	<obj-relationship name="toTable1" source="Table1Table2" target="Table1" deleteRule="Nullify" db-relationship-path="toTable1"/>
-	<obj-relationship name="toTable2" source="Table1Table2" target="Table2" deleteRule="Nullify" db-relationship-path="toTable2"/>
-	<obj-relationship name="table1Table2Array" source="Table2" target="Table1Table2" deleteRule="Deny" db-relationship-path="table1Table2Array"/>
-</data-map>

http://git-wip-us.apache.org/repos/asf/cayenne/blob/ecbacdf0/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DbImportConfiguration.java
----------------------------------------------------------------------
diff --git a/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DbImportConfiguration.java b/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DbImportConfiguration.java
index d4e9fcb..5695859 100644
--- a/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DbImportConfiguration.java
+++ b/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DbImportConfiguration.java
@@ -72,6 +72,7 @@ public class DbImportConfiguration {
     private String adapter;
     private boolean usePrimitives;
     private Log logger;
+    private String namingStrategy;
 
     public Log getLogger() {
         return logger;
@@ -98,11 +99,11 @@ public class DbImportConfiguration {
     }
 
     public String getNamingStrategy() {
-        return dbLoaderConfiguration.getNamingStrategy();
+        return namingStrategy;
     }
 
     public void setNamingStrategy(String namingStrategy) {
-        dbLoaderConfiguration.setNamingStrategy(namingStrategy);
+        this.namingStrategy = namingStrategy;
     }
 
     public String getAdapter() {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/ecbacdf0/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DefaultDbImportAction.java
----------------------------------------------------------------------
diff --git a/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DefaultDbImportAction.java b/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DefaultDbImportAction.java
index 1211e64..6fd4116 100644
--- a/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DefaultDbImportAction.java
+++ b/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DefaultDbImportAction.java
@@ -283,7 +283,7 @@ public class DefaultDbImportAction implements DbImportAction {
             logger.info("Migration Complete Successfully.");
         }
 
-        DbLoader.flattenManyToManyRelationships(targetDataMap, loadedObjEntities, nameGenerator);
+        flattenManyToManyRelationships(targetDataMap, loadedObjEntities, nameGenerator);
         relationshipsSanity(targetDataMap);
         return true;
     }
@@ -300,4 +300,30 @@ public class DefaultDbImportAction implements DbImportAction {
         loader.load(dataMap, config.getDbLoaderConfig());
         return dataMap;
     }
+
+    /**
+     * Flattens many-to-many relationships in the generated model.
+     */
+    protected static void flattenManyToManyRelationships(DataMap map, Collection<ObjEntity> loadedObjEntities,
+                                                      ObjectNameGenerator objectNameGenerator) {
+        if (loadedObjEntities.isEmpty()) {
+            return;
+        }
+        Collection<ObjEntity> entitiesForDelete = new LinkedList<>();
+
+        for (ObjEntity curEntity : loadedObjEntities) {
+            ManyToManyCandidateEntity entity = ManyToManyCandidateEntity.build(curEntity);
+
+            if (entity != null) {
+                entity.optimizeRelationships(objectNameGenerator);
+                entitiesForDelete.add(curEntity);
+            }
+        }
+
+        // remove needed entities
+        for (ObjEntity curDeleteEntity : entitiesForDelete) {
+            map.removeObjEntity(curDeleteEntity.getName(), true);
+        }
+        loadedObjEntities.removeAll(entitiesForDelete);
+    }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/ecbacdf0/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/ManyToManyCandidateEntity.java
----------------------------------------------------------------------
diff --git a/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/ManyToManyCandidateEntity.java b/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/ManyToManyCandidateEntity.java
new file mode 100644
index 0000000..da658e5
--- /dev/null
+++ b/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/ManyToManyCandidateEntity.java
@@ -0,0 +1,143 @@
+/*****************************************************************
+ *   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.tools.dbimport;
+
+import org.apache.cayenne.dbsync.naming.NameBuilder;
+import org.apache.cayenne.dbsync.naming.ObjectNameGenerator;
+import org.apache.cayenne.dbsync.reverse.db.ExportedKey;
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.map.ObjRelationship;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * An ObjEntity that may be removed as a result of flattenning relationships.
+ */
+class ManyToManyCandidateEntity {
+
+    private static final Log LOG = LogFactory.getLog(ManyToManyCandidateEntity.class);
+
+    private final ObjEntity joinEntity;
+
+    private final DbRelationship dbRel1;
+    private final DbRelationship dbRel2;
+
+    private final ObjEntity entity1;
+    private final ObjEntity entity2;
+
+    private final DbRelationship reverseRelationship1;
+    private final DbRelationship reverseRelationship2;
+
+    private ManyToManyCandidateEntity(ObjEntity entityValue, List<ObjRelationship> relationships) {
+        joinEntity = entityValue;
+
+        ObjRelationship rel1 = relationships.get(0);
+        ObjRelationship rel2 = relationships.get(1);
+
+        dbRel1 = rel1.getDbRelationships().get(0);
+        dbRel2 = rel2.getDbRelationships().get(0);
+
+        reverseRelationship1 = dbRel1.getReverseRelationship();
+        reverseRelationship2 = dbRel2.getReverseRelationship();
+
+        entity1 = rel1.getTargetEntity();
+        entity2 = rel2.getTargetEntity();
+    }
+
+    /**
+     * Method check - if current entity represent many to many temporary table
+     *
+     * @return true if current entity is represent many to many table; otherwise returns false
+     */
+    public static ManyToManyCandidateEntity build(ObjEntity joinEntity) {
+        ArrayList<ObjRelationship> relationships = new ArrayList<ObjRelationship>(joinEntity.getRelationships());
+        if (relationships.size() != 2 || (relationships.get(0).getDbRelationships().isEmpty() || relationships.get(1).getDbRelationships().isEmpty())) {
+            return null;
+        }
+
+        ManyToManyCandidateEntity candidateEntity = new ManyToManyCandidateEntity(joinEntity, relationships);
+        if (candidateEntity.isManyToMany()) {
+            return candidateEntity;
+        }
+
+        return null;
+    }
+
+    private boolean isManyToMany() {
+        boolean isNotHaveAttributes = joinEntity.getAttributes().size() == 0;
+
+        return isNotHaveAttributes
+                && reverseRelationship1 != null && reverseRelationship1.isToDependentPK()
+                && reverseRelationship2 != null && reverseRelationship2.isToDependentPK()
+                && entity1 != null && entity2 != null;
+    }
+
+    private void addFlattenedRelationship(ObjectNameGenerator nameGenerator, ObjEntity srcEntity, ObjEntity dstEntity,
+                                          DbRelationship rel1, DbRelationship rel2) {
+
+        if (rel1.getSourceAttributes().isEmpty() && rel2.getTargetAttributes().isEmpty()) {
+            LOG.warn("Wrong call ManyToManyCandidateEntity.addFlattenedRelationship(... , " + srcEntity.getName()
+                    + ", " + dstEntity.getName() + ", ...)");
+
+            return;
+        }
+
+        ExportedKey key = new ExportedKey(
+                rel1.getSourceEntity().getName(),
+                rel1.getSourceAttributes().iterator().next().getName(),
+                null,
+                rel2.getTargetEntity().getName(),
+                rel2.getTargetAttributes().iterator().next().getName(),
+                null,
+                (short) 1);
+
+        ObjRelationship newRelationship = new ObjRelationship();
+        newRelationship.setName(NameBuilder
+                .builder(newRelationship, srcEntity)
+                .baseName(nameGenerator.dbRelationshipName(key, true))
+                .name());
+
+        newRelationship.setSourceEntity(srcEntity);
+        newRelationship.setTargetEntityName(dstEntity);
+
+        newRelationship.addDbRelationship(rel1);
+        newRelationship.addDbRelationship(rel2);
+
+        srcEntity.addRelationship(newRelationship);
+    }
+
+    /**
+     * Method make direct relationships between 2 entities and remove relationships to
+     * many to many entity
+     *
+     * @param nameGenerator
+     */
+    public void optimizeRelationships(ObjectNameGenerator nameGenerator) {
+        entity1.removeRelationship(reverseRelationship1.getName());
+        entity2.removeRelationship(reverseRelationship2.getName());
+
+        addFlattenedRelationship(nameGenerator, entity1, entity2, reverseRelationship1, dbRel2);
+        addFlattenedRelationship(nameGenerator, entity2, entity1, reverseRelationship2, dbRel1);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/ecbacdf0/cayenne-tools/src/test/java/org/apache/cayenne/tools/dbimport/ManyToManyCandidateEntityTest.java
----------------------------------------------------------------------
diff --git a/cayenne-tools/src/test/java/org/apache/cayenne/tools/dbimport/ManyToManyCandidateEntityTest.java b/cayenne-tools/src/test/java/org/apache/cayenne/tools/dbimport/ManyToManyCandidateEntityTest.java
new file mode 100644
index 0000000..a1260f1
--- /dev/null
+++ b/cayenne-tools/src/test/java/org/apache/cayenne/tools/dbimport/ManyToManyCandidateEntityTest.java
@@ -0,0 +1,113 @@
+/*****************************************************************
+ *   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.tools.dbimport;
+
+import org.apache.cayenne.configuration.ConfigurationNameMapper;
+import org.apache.cayenne.configuration.ConfigurationTree;
+import org.apache.cayenne.configuration.DataChannelDescriptor;
+import org.apache.cayenne.configuration.DataMapLoader;
+import org.apache.cayenne.configuration.DefaultConfigurationNameMapper;
+import org.apache.cayenne.configuration.XMLDataChannelDescriptorLoader;
+import org.apache.cayenne.configuration.XMLDataMapLoader;
+import org.apache.cayenne.di.AdhocObjectFactory;
+import org.apache.cayenne.di.Binder;
+import org.apache.cayenne.di.ClassLoaderManager;
+import org.apache.cayenne.di.DIBootstrap;
+import org.apache.cayenne.di.Injector;
+import org.apache.cayenne.di.Module;
+import org.apache.cayenne.di.spi.DefaultAdhocObjectFactory;
+import org.apache.cayenne.di.spi.DefaultClassLoaderManager;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.map.Relationship;
+import org.apache.cayenne.dbsync.naming.LegacyObjectNameGenerator;
+import org.apache.cayenne.resource.URLResource;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.net.URL;
+import java.util.ArrayList;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+public class ManyToManyCandidateEntityTest {
+
+    private DataMap map;
+
+    @Before
+    public void setUp() throws Exception {
+        Module testModule = new Module() {
+
+            public void configure(Binder binder) {
+                binder.bind(ClassLoaderManager.class).to(DefaultClassLoaderManager.class);
+                binder.bind(AdhocObjectFactory.class).to(DefaultAdhocObjectFactory.class);
+                binder.bind(DataMapLoader.class).to(XMLDataMapLoader.class);
+                binder.bind(ConfigurationNameMapper.class).to(DefaultConfigurationNameMapper.class);
+            }
+        };
+
+        Injector injector = DIBootstrap.createInjector(testModule);
+
+        // create and initialize loader instance to test
+        XMLDataChannelDescriptorLoader loader = new XMLDataChannelDescriptorLoader();
+        injector.injectMembers(loader);
+
+        String testConfigName = "relationship-optimisation";
+        URL url = getClass().getResource("cayenne-" + testConfigName + ".xml");
+
+        ConfigurationTree<DataChannelDescriptor> tree = loader.load(new URLResource(url));
+
+        map = tree.getRootNode().getDataMap(testConfigName);
+    }
+
+    @Test
+    public void testMatchingForManyToManyEntity() throws Exception {
+        ObjEntity manyToManyEntity = map.getObjEntity("Table1Table2");
+
+        assertNotNull(ManyToManyCandidateEntity.build(manyToManyEntity));
+    }
+
+    @Test
+    public void testMatchingForNotManyToManyEntity() throws Exception {
+        ObjEntity entity = map.getObjEntity("Table1");
+
+        assertNull(ManyToManyCandidateEntity.build(entity));
+    }
+
+    @Test
+    public void testOptimisationForManyToManyEntity() {
+        ObjEntity manyToManyEntity = map.getObjEntity("Table1Table2");
+
+        ManyToManyCandidateEntity.build(manyToManyEntity).optimizeRelationships(new LegacyObjectNameGenerator());
+
+        ObjEntity table1Entity = map.getObjEntity("Table1");
+        ObjEntity table2Entity = map.getObjEntity("Table2");
+
+        assertEquals(1, table1Entity.getRelationships().size());
+        assertEquals(table2Entity, new ArrayList<Relationship>(table1Entity.getRelationships()).get(0)
+                .getTargetEntity());
+
+        assertEquals(1, table2Entity.getRelationships().size());
+        assertEquals(table1Entity, new ArrayList<Relationship>(table2Entity.getRelationships()).get(0)
+                .getTargetEntity());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/ecbacdf0/cayenne-tools/src/test/resources/org/apache/cayenne/tools/dbimport/cayenne-relationship-optimisation.xml
----------------------------------------------------------------------
diff --git a/cayenne-tools/src/test/resources/org/apache/cayenne/tools/dbimport/cayenne-relationship-optimisation.xml b/cayenne-tools/src/test/resources/org/apache/cayenne/tools/dbimport/cayenne-relationship-optimisation.xml
new file mode 100644
index 0000000..d4fea49
--- /dev/null
+++ b/cayenne-tools/src/test/resources/org/apache/cayenne/tools/dbimport/cayenne-relationship-optimisation.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<domain project-version="6">
+	<map name="relationship-optimisation"/>
+</domain>

http://git-wip-us.apache.org/repos/asf/cayenne/blob/ecbacdf0/cayenne-tools/src/test/resources/org/apache/cayenne/tools/dbimport/relationship-optimisation.map.xml
----------------------------------------------------------------------
diff --git a/cayenne-tools/src/test/resources/org/apache/cayenne/tools/dbimport/relationship-optimisation.map.xml b/cayenne-tools/src/test/resources/org/apache/cayenne/tools/dbimport/relationship-optimisation.map.xml
new file mode 100644
index 0000000..e68645f
--- /dev/null
+++ b/cayenne-tools/src/test/resources/org/apache/cayenne/tools/dbimport/relationship-optimisation.map.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<data-map xmlns="http://cayenne.apache.org/schema/3.0/modelMap"
+	 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	 xsi:schemaLocation="http://cayenne.apache.org/schema/3.0/modelMap http://cayenne.apache.org/schema/3.0/modelMap.xsd"
+	 project-version="6">
+	<property name="defaultPackage" value="com.objectstyle"/>
+	<db-entity name="table1" catalog="many_to_many_test">
+		<db-attribute name="id1" type="INTEGER" isPrimaryKey="true" isMandatory="true" length="10"/>
+		<db-attribute name="table1col" type="VARCHAR" length="45"/>
+	</db-entity>
+	<db-entity name="table1_table2" catalog="many_to_many_test">
+		<db-attribute name="fk1" type="INTEGER" isPrimaryKey="true" isMandatory="true" length="10"/>
+		<db-attribute name="fk2" type="INTEGER" isPrimaryKey="true" isMandatory="true" length="10"/>
+	</db-entity>
+	<db-entity name="table2" catalog="many_to_many_test">
+		<db-attribute name="id2" type="INTEGER" isPrimaryKey="true" isMandatory="true" length="10"/>
+		<db-attribute name="table2col" type="VARCHAR" length="45"/>
+	</db-entity>
+	<obj-entity name="Table1" className="com.objectstyle.Table1" dbEntityName="table1">
+		<obj-attribute name="table1col" type="java.lang.String" db-attribute-path="table1col"/>
+	</obj-entity>
+	<obj-entity name="Table1Table2" className="com.objectstyle.Table1Table2" dbEntityName="table1_table2">
+	</obj-entity>
+	<obj-entity name="Table2" className="com.objectstyle.Table2" dbEntityName="table2">
+		<obj-attribute name="table2col" type="java.lang.String" db-attribute-path="table2col"/>
+	</obj-entity>
+	<db-relationship name="table1Table2Array" source="table1" target="table1_table2" toDependentPK="true" toMany="true">
+		<db-attribute-pair source="id1" target="fk1"/>
+	</db-relationship>
+	<db-relationship name="toTable1" source="table1_table2" target="table1" toMany="false">
+		<db-attribute-pair source="fk1" target="id1"/>
+	</db-relationship>
+	<db-relationship name="toTable2" source="table1_table2" target="table2" toMany="false">
+		<db-attribute-pair source="fk2" target="id2"/>
+	</db-relationship>
+	<db-relationship name="table1Table2Array" source="table2" target="table1_table2" toDependentPK="true" toMany="true">
+		<db-attribute-pair source="id2" target="fk2"/>
+	</db-relationship>
+	<obj-relationship name="table1Table2Array" source="Table1" target="Table1Table2" deleteRule="Deny" db-relationship-path="table1Table2Array"/>
+	<obj-relationship name="toTable1" source="Table1Table2" target="Table1" deleteRule="Nullify" db-relationship-path="toTable1"/>
+	<obj-relationship name="toTable2" source="Table1Table2" target="Table2" deleteRule="Nullify" db-relationship-path="toTable2"/>
+	<obj-relationship name="table1Table2Array" source="Table2" target="Table1Table2" deleteRule="Deny" db-relationship-path="table1Table2Array"/>
+</data-map>

http://git-wip-us.apache.org/repos/asf/cayenne/blob/ecbacdf0/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/MergerOptions.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/MergerOptions.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/MergerOptions.java
index e0aeba6..4ee133c 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/MergerOptions.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/MergerOptions.java
@@ -176,15 +176,15 @@ public class MergerOptions extends CayenneController {
             DbLoaderConfiguration config = new DbLoaderConfiguration();
             config.setFiltersConfig(filters);
 
-            DataSource dataSource  = connectionInfo.makeDataSource(getApplication().getClassLoadingService());
+            DataSource dataSource = connectionInfo.makeDataSource(getApplication().getClassLoadingService());
 
-            DataMap dbImport;
+            DataMap dbImport = new DataMap();
             try (Connection conn = dataSource.getConnection();) {
-                dbImport =  new DbLoader(conn,
+                new DbLoader(conn,
                         adapter,
                         new LoggingDbLoaderDelegate(LogFactory.getLog(DbLoader.class)),
                         new DefaultObjectNameGenerator())
-                        .load(config);
+                        .load(dbImport, config);
 
             } catch (SQLException e) {
                 throw new CayenneRuntimeException("Can't doLoad dataMap from db.", e);

http://git-wip-us.apache.org/repos/asf/cayenne/blob/ecbacdf0/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/ModelerDbImportAction.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/ModelerDbImportAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/ModelerDbImportAction.java
index b51af57..baee76a 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/ModelerDbImportAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/ModelerDbImportAction.java
@@ -103,7 +103,9 @@ public class ModelerDbImportAction implements DbImportAction {
 
             @Override
             protected DataMap load(DbImportConfiguration config, DbAdapter adapter, Connection connection) throws Exception {
-               return dbLoaderHelper.getLoader().load(config.getDbLoaderConfig());
+                DataMap dataMap = new DataMap();
+                dbLoaderHelper.getLoader().load(dataMap, config.getDbLoaderConfig());
+                return dataMap;
             }
         }.execute(config);
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/ecbacdf0/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/ModelerDbLoader.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/ModelerDbLoader.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/ModelerDbLoader.java
index b4b08b5..9f88fb4 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/ModelerDbLoader.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/ModelerDbLoader.java
@@ -62,16 +62,15 @@ class ModelerDbLoader extends DbLoader {
     }
 
     @Override
-    public DataMap load(DbLoaderConfiguration config) throws SQLException {
-        DataMap dataMap = new DataMap();
+    public void load(DataMap dataMap, DbLoaderConfiguration config) throws SQLException {
         Map<String, Procedure> procedureMap = loadProcedures(dataMap, config);
         load(dataMap, config);
         addProcedures(procedureMap);
-        return dataMap;
     }
 
     @Override
-    public void load(DataMap dataMap, DbLoaderConfiguration config) throws SQLException {
+    protected void loadDbEntities(DataMap dataMap, DbLoaderConfiguration config) throws SQLException {
+
         LOGGER.info("Schema loading...");
 
         String[] types = getTableTypes(config);

http://git-wip-us.apache.org/repos/asf/cayenne/blob/ecbacdf0/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/ReverseEngineeringController.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/ReverseEngineeringController.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/ReverseEngineeringController.java
index 4e1172b..92027f4 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/ReverseEngineeringController.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/ReverseEngineeringController.java
@@ -23,6 +23,7 @@ import org.apache.cayenne.dbimport.ReverseEngineering;
 import org.apache.cayenne.dbsync.reverse.db.DbLoader;
 import org.apache.cayenne.dbsync.reverse.db.DbLoaderConfiguration;
 import org.apache.cayenne.dbsync.reverse.FiltersConfigBuilder;
+import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.modeler.ClassLoadingService;
 import org.apache.cayenne.modeler.ProjectController;
 import org.apache.cayenne.modeler.dialog.db.model.DBModel;
@@ -111,7 +112,9 @@ public class ReverseEngineeringController extends CayenneController {
 
             try(Connection connection = dataSource.getConnection()) {
                 DbLoader dbLoader = new ModelerDbLoader(this, treeEditor, connection);
-                dbLoader.load(dbLoaderConfiguration);
+
+                // TODO: counterintuitive... we never use the DataMap that we loaded...
+                dbLoader.load(new DataMap(), dbLoaderConfiguration);
             }
 
             String mapName = projectController.getCurrentDataMap().getName();