You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by ab...@apache.org on 2019/06/28 12:02:42 UTC

[cayenne] branch master updated: CAY-2579 Review and possibly relax usage of readonly flag of ObjRelationship

This is an automated email from the ASF dual-hosted git repository.

abulatski pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git


The following commit(s) were added to refs/heads/master by this push:
     new bccd538  CAY-2579 Review and possibly relax usage of readonly flag of ObjRelationship
bccd538 is described below

commit bccd53853cb5903b2c50c7f7aabb547ca40757aa
Author: Arseni Bulatski <an...@gmail.com>
AuthorDate: Fri Jun 28 11:10:56 2019 +0300

    CAY-2579 Review and possibly relax usage of readonly flag of ObjRelationship
---
 RELEASE-NOTES.txt                                  |   1 +
 .../org/apache/cayenne/map/ObjRelationship.java    |  22 ++---
 .../apache/cayenne/FlattenedRelationshipsIT.java   | 108 +++++++++++++++++++--
 .../org/apache/cayenne/map/ObjRelationshipIT.java  |  25 ++---
 .../testdo/relationships_flattened/Entity1.java    |  28 ++++++
 .../testdo/relationships_flattened/Entity2.java    |  28 ++++++
 .../testdo/relationships_flattened/Entity3.java    |  28 ++++++
 .../auto/{_FlattenedTest5.java => _Entity1.java}   |  55 +++--------
 .../relationships_flattened/auto/_Entity2.java     |  66 +++++++++++++
 .../relationships_flattened/auto/_Entity3.java     |  66 +++++++++++++
 .../auto/_FlattenedCircular.java                   |   3 -
 .../auto/_FlattenedTest1.java                      |  19 +++-
 .../auto/_FlattenedTest2.java                      |   3 -
 .../auto/_FlattenedTest3.java                      |   7 +-
 .../auto/_FlattenedTest5.java                      |   3 -
 .../resources/cayenne-relationships-flattened.xml  |   2 +
 .../test/resources/relationships-flattened.map.xml |  33 ++++++-
 .../editor/dbentity/DbRelationshipTableModel.java  |   1 -
 18 files changed, 401 insertions(+), 97 deletions(-)

diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index 8f0ecc7..89bf59d 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -34,6 +34,7 @@ CAY-2563 Deprecate old scalarQuery methods in SQLSelect
 CAY-2568 Class Generation: Superclass Package setting persistence
 CAY-2569 Custom 'Naming Strategy' in Cayenne Modeler
 CAY-2570 Use MySQL adapter for latest versions of MariaDB
+CAY-2579 Review and possibly relax usage of readonly flag of ObjRelationship
 
 Bug Fixes:
 
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/map/ObjRelationship.java b/cayenne-server/src/main/java/org/apache/cayenne/map/ObjRelationship.java
index 96bc178..ed4ecd1 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/map/ObjRelationship.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/map/ObjRelationship.java
@@ -19,6 +19,12 @@
 
 package org.apache.cayenne.map;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
 import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.configuration.ConfigurationNode;
 import org.apache.cayenne.configuration.ConfigurationNodeVisitor;
@@ -29,12 +35,6 @@ import org.apache.cayenne.util.ToStringBuilder;
 import org.apache.cayenne.util.Util;
 import org.apache.cayenne.util.XMLEncoder;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.ListIterator;
-
 /**
  * Describes an association between two Java classes mapped as source and target
  * ObjEntity. Maps to a path of DbRelationships.
@@ -272,7 +272,6 @@ public class ObjRelationship extends Relationship implements ConfigurationNode {
 
         dbRelationships.add(dbRel);
 
-        this.recalculateReadOnlyValue();
         this.recalculateToManyValue();
     }
 
@@ -283,7 +282,6 @@ public class ObjRelationship extends Relationship implements ConfigurationNode {
     public void removeDbRelationship(DbRelationship dbRel) {
         refreshFromDeferredPath();
         if (dbRelationships.remove(dbRel)) {
-            this.recalculateReadOnlyValue();
             this.recalculateToManyValue();
         }
     }
@@ -419,7 +417,6 @@ public class ObjRelationship extends Relationship implements ConfigurationNode {
      */
     public boolean isReadOnly() {
         refreshFromDeferredPath();
-        recalculateReadOnlyValue();
         return readOnly;
     }
 
@@ -591,7 +588,7 @@ public class ObjRelationship extends Relationship implements ConfigurationNode {
             return null;
         }
 
-        ObjEntity entity = (ObjEntity) getSourceEntity();
+        ObjEntity entity = getSourceEntity();
         if (entity == null) {
             throw new CayenneRuntimeException("Can't resolve DbRelationships, null source ObjEntity");
         }
@@ -628,7 +625,7 @@ public class ObjRelationship extends Relationship implements ConfigurationNode {
 
             if (dbRelationshipPath != null) {
 
-                ObjEntity entity = (ObjEntity) getSourceEntity();
+                ObjEntity entity = getSourceEntity();
                 if (entity == null) {
                     throw new CayenneRuntimeException("Can't resolve DbRelationships, null source ObjEntity");
                 }
@@ -650,7 +647,6 @@ public class ObjRelationship extends Relationship implements ConfigurationNode {
             }
 
             recalculateToManyValue();
-            recalculateReadOnlyValue();
     }
 
     /**
@@ -675,7 +671,9 @@ public class ObjRelationship extends Relationship implements ConfigurationNode {
     /**
      * Recalculates a new readonly value based on the underlying
      * DbRelationships.
+     * @deprecated since 4.2
      */
+    @Deprecated
     public void recalculateReadOnlyValue() {
         // not flattened, always read/write
         if (dbRelationships.size() < 2) {
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/FlattenedRelationshipsIT.java b/cayenne-server/src/test/java/org/apache/cayenne/FlattenedRelationshipsIT.java
index 6317729..1e065dc 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/FlattenedRelationshipsIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/FlattenedRelationshipsIT.java
@@ -19,6 +19,8 @@
 
 package org.apache.cayenne;
 
+import java.util.List;
+
 import org.apache.cayenne.access.DataContext;
 import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.exp.Expression;
@@ -28,10 +30,13 @@ import org.apache.cayenne.query.SQLTemplate;
 import org.apache.cayenne.query.SelectQuery;
 import org.apache.cayenne.test.jdbc.DBHelper;
 import org.apache.cayenne.test.jdbc.TableHelper;
+import org.apache.cayenne.testdo.relationships_flattened.Entity1;
+import org.apache.cayenne.testdo.relationships_flattened.Entity3;
 import org.apache.cayenne.testdo.relationships_flattened.FlattenedCircular;
 import org.apache.cayenne.testdo.relationships_flattened.FlattenedTest1;
 import org.apache.cayenne.testdo.relationships_flattened.FlattenedTest2;
 import org.apache.cayenne.testdo.relationships_flattened.FlattenedTest3;
+import org.apache.cayenne.testdo.relationships_flattened.FlattenedTest4;
 import org.apache.cayenne.testdo.relationships_flattened.FlattenedTest5;
 import org.apache.cayenne.unit.di.server.CayenneProjects;
 import org.apache.cayenne.unit.di.server.ServerCase;
@@ -40,9 +45,6 @@ import org.apache.cayenne.validation.ValidationResult;
 import org.junit.Before;
 import org.junit.Test;
 
-import java.sql.Types;
-import java.util.List;
-
 import static org.junit.Assert.*;
 
 /**
@@ -200,7 +202,7 @@ public class FlattenedRelationshipsIT extends ServerCase {
 
         // test 1: qualify on flattened attribute
         Expression qual1 = ExpressionFactory.matchExp("ft3Array.name", "t031");
-        SelectQuery query1 = new SelectQuery(FlattenedTest1.class, qual1);
+        SelectQuery query1 = new SelectQuery<>(FlattenedTest1.class, qual1);
         List<?> objects1 = context.performQuery(query1);
 
         assertEquals(1, objects1.size());
@@ -208,7 +210,7 @@ public class FlattenedRelationshipsIT extends ServerCase {
 
         // test 2: qualify on flattened relationship
         Expression qual2 = ExpressionFactory.matchExp("ft3Array", obj131);
-        SelectQuery query2 = new SelectQuery(FlattenedTest1.class, qual2);
+        SelectQuery query2 = new SelectQuery<>(FlattenedTest1.class, qual2);
         List<?> objects2 = context.performQuery(query2);
 
         assertEquals(1, objects2.size());
@@ -231,7 +233,7 @@ public class FlattenedRelationshipsIT extends ServerCase {
 
         context.invalidateObjects(ft1, ft2, ft3);
 
-        SelectQuery q = new SelectQuery(FlattenedTest3.class);
+        SelectQuery q = new SelectQuery<>(FlattenedTest3.class);
         q.setQualifier(ExpressionFactory.matchExp("name", "FT3Name"));
         List<?> results = context1.performQuery(q);
 
@@ -247,7 +249,7 @@ public class FlattenedRelationshipsIT extends ServerCase {
         createFlattenedTestDataSet();
 
         // fetch
-        List<?> ft3s = context.performQuery(new SelectQuery(FlattenedTest3.class));
+        List<?> ft3s = context.performQuery(new SelectQuery<>(FlattenedTest3.class));
         assertEquals(1, ft3s.size());
         FlattenedTest3 ft3 = (FlattenedTest3) ft3s.get(0);
 
@@ -266,14 +268,14 @@ public class FlattenedRelationshipsIT extends ServerCase {
         createFlattenedTestDataSet();
 
         // fetch
-        List<?> ft3s = context.performQuery(new SelectQuery(FlattenedTest3.class));
+        List<?> ft3s = context.performQuery(new SelectQuery<>(FlattenedTest3.class));
         assertEquals(1, ft3s.size());
         FlattenedTest3 ft3 = (FlattenedTest3) ft3s.get(0);
 
         assertTrue(ft3.readPropertyDirectly("toFT1") instanceof Fault);
 
         // refetch
-        context.performQuery(new SelectQuery(FlattenedTest3.class));
+        context.performQuery(new SelectQuery<>(FlattenedTest3.class));
         assertTrue(ft3.readPropertyDirectly("toFT1") instanceof Fault);
     }
 
@@ -329,4 +331,92 @@ public class FlattenedRelationshipsIT extends ServerCase {
         assertEquals(1, ObjectSelect.query(FlattenedTest5.class).selectCount(context));
     }
 
+    @Test
+    public void testSetFlattenedRelationship() {
+        FlattenedTest1 flattenedTest1 = context.newObject(FlattenedTest1.class);
+        flattenedTest1.setName("f1");
+        FlattenedTest3 flattenedTest3 = context.newObject(FlattenedTest3.class);
+        flattenedTest3.setName("f3");
+        flattenedTest3.setToFT1(flattenedTest1);
+
+        context.commitChanges();
+
+        List<FlattenedTest3> flattenedTest3s = ObjectSelect.query(FlattenedTest3.class)
+                .prefetch(FlattenedTest3.TO_FT1.disjoint())
+                .select(context);
+        assertEquals(1, flattenedTest3s.size());
+        assertEquals("f3", flattenedTest3s.get(0).getName());
+        assertEquals("f1", flattenedTest3s.get(0).getToFT1().getName());
+    }
+
+    @Test
+    public void testSecondToOneReverseToFk() {
+        FlattenedTest1 flattenedTest1 = context.newObject(FlattenedTest1.class);
+        flattenedTest1.setName("f1");
+        FlattenedTest4 flattenedTest4 = context.newObject(FlattenedTest4.class);
+        flattenedTest4.setName("f4");
+        flattenedTest1.addToFt4ArrayFor1(flattenedTest4);
+        context.commitChanges();
+    }
+
+    @Test
+    public void testSecondToOneToFk() {
+        Entity1 entity1 = context.newObject(Entity1.class);
+        Entity3 entity3 = context.newObject(Entity3.class);
+        entity1.setToEntity3(entity3);
+        context.commitChanges();
+
+        List<Entity1> entity1s = ObjectSelect.query(Entity1.class)
+                .prefetch(Entity1.TO_ENTITY3.disjoint())
+                .select(context);
+        assertEquals(1, entity1s.size());
+        assertNotNull(entity1s.get(0).getToEntity3());
+    }
+
+    @Test
+    public void testSetFlattenedCircular() {
+        FlattenedCircular flattenedCircular1 = context.newObject(FlattenedCircular.class);
+        FlattenedCircular flattenedCircular2 = context.newObject(FlattenedCircular.class);
+        flattenedCircular1.addToSide1s(flattenedCircular2);
+        context.commitChanges();
+
+        List<FlattenedCircular> flattenedCirculars = ObjectSelect.query(FlattenedCircular.class)
+                .prefetch(FlattenedCircular.SIDE1S.disjoint())
+                .select(context);
+
+        assertEquals(2, flattenedCirculars.size());
+    }
+
+    @Test
+    public void testFt1ToFt5Flattened() {
+        FlattenedTest1 flattenedTest1 = context.newObject(FlattenedTest1.class);
+        flattenedTest1.setName("f1");
+        FlattenedTest5 flattenedTest5 = context.newObject(FlattenedTest5.class);
+        flattenedTest5.setName("f5");
+        flattenedTest1.addToFt5Array(flattenedTest5);
+        context.commitChanges();
+
+        List<FlattenedTest1> flattenedTest1s = ObjectSelect.query(FlattenedTest1.class)
+                .prefetch(FlattenedTest1.FT5ARRAY.disjoint())
+                .select(context);
+        assertEquals(1, flattenedTest1s.size());
+        assertEquals(1, flattenedTest1s.get(0).getFt5Array().size());
+    }
+
+    @Test
+    public void testFt5ToFt1Flattened() {
+        FlattenedTest5 flattenedTest5 = context.newObject(FlattenedTest5.class);
+        flattenedTest5.setName("f5");
+        FlattenedTest1 flattenedTest1 = context.newObject(FlattenedTest1.class);
+        flattenedTest1.setName("f1");
+        flattenedTest5.setToFT1(flattenedTest1);
+        context.commitChanges();
+
+        List<FlattenedTest5> flattenedTest5s = ObjectSelect.query(FlattenedTest5.class)
+                .prefetch(FlattenedTest5.TO_FT1.disjoint())
+                .select(context);
+        assertEquals(1, flattenedTest5s.size());
+        assertEquals("f5", flattenedTest5s.get(0).getName());
+        assertEquals("f1", flattenedTest5s.get(0).getToFT1().getName());
+    }
 }
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/map/ObjRelationshipIT.java b/cayenne-server/src/test/java/org/apache/cayenne/map/ObjRelationshipIT.java
index 2c5073f..40a28c3 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/map/ObjRelationshipIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/map/ObjRelationshipIT.java
@@ -19,11 +19,14 @@
 
 package org.apache.cayenne.map;
 
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.net.URL;
+import java.util.Collections;
+import java.util.List;
+
 import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.configuration.ConfigurationNodeVisitor;
-import org.apache.cayenne.configuration.DataChannelDescriptor;
 import org.apache.cayenne.configuration.DataMapLoader;
-import org.apache.cayenne.configuration.DataNodeDescriptor;
 import org.apache.cayenne.configuration.EmptyConfigurationNodeVisitor;
 import org.apache.cayenne.configuration.server.ServerRuntime;
 import org.apache.cayenne.di.Inject;
@@ -38,19 +41,7 @@ import org.apache.cayenne.util.XMLEncoder;
 import org.junit.Before;
 import org.junit.Test;
 
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.net.URL;
-import java.util.Collections;
-import java.util.List;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.*;
 
 @UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
 public class ObjRelationshipIT extends ServerCase {
@@ -463,7 +454,6 @@ public class ObjRelationshipIT extends ServerCase {
         relationship.addDbRelationship(r3);
 
         assertTrue(relationship.isFlattened());
-        assertTrue(relationship.isReadOnly());
         assertTrue(relationship.isToMany());
 
     }
@@ -488,7 +478,6 @@ public class ObjRelationshipIT extends ServerCase {
         relationship.addDbRelationship(r2);
 
         assertTrue(relationship.isFlattened());
-        assertTrue(relationship.isReadOnly());
         assertTrue(relationship.isToMany());
     }
 
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_flattened/Entity1.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_flattened/Entity1.java
new file mode 100644
index 0000000..cecb687
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_flattened/Entity1.java
@@ -0,0 +1,28 @@
+/*****************************************************************
+ *   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
+ *
+ *    https://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.testdo.relationships_flattened;
+
+import org.apache.cayenne.testdo.relationships_flattened.auto._Entity1;
+
+public class Entity1 extends _Entity1 {
+
+    private static final long serialVersionUID = 1L; 
+
+}
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_flattened/Entity2.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_flattened/Entity2.java
new file mode 100644
index 0000000..9c4735e
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_flattened/Entity2.java
@@ -0,0 +1,28 @@
+/*****************************************************************
+ *   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
+ *
+ *    https://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.testdo.relationships_flattened;
+
+import org.apache.cayenne.testdo.relationships_flattened.auto._Entity2;
+
+public class Entity2 extends _Entity2 {
+
+    private static final long serialVersionUID = 1L; 
+
+}
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_flattened/Entity3.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_flattened/Entity3.java
new file mode 100644
index 0000000..7323ff2
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_flattened/Entity3.java
@@ -0,0 +1,28 @@
+/*****************************************************************
+ *   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
+ *
+ *    https://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.testdo.relationships_flattened;
+
+import org.apache.cayenne.testdo.relationships_flattened.auto._Entity3;
+
+public class Entity3 extends _Entity3 {
+
+    private static final long serialVersionUID = 1L; 
+
+}
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_flattened/auto/_FlattenedTest5.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_flattened/auto/_Entity1.java
similarity index 50%
copy from cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_flattened/auto/_FlattenedTest5.java
copy to cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_flattened/auto/_Entity1.java
index b5a3924..394116e 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_flattened/auto/_FlattenedTest5.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_flattened/auto/_Entity1.java
@@ -5,49 +5,33 @@ import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 
 import org.apache.cayenne.BaseDataObject;
-import org.apache.cayenne.exp.ExpressionFactory;
 import org.apache.cayenne.exp.property.EntityProperty;
-import org.apache.cayenne.exp.property.NumericProperty;
 import org.apache.cayenne.exp.property.PropertyFactory;
-import org.apache.cayenne.exp.property.StringProperty;
-import org.apache.cayenne.testdo.relationships_flattened.FlattenedTest1;
+import org.apache.cayenne.testdo.relationships_flattened.Entity3;
 
 /**
- * Class _FlattenedTest5 was generated by Cayenne.
+ * Class _Entity1 was generated by Cayenne.
  * It is probably a good idea to avoid changing this class manually,
  * since it may be overwritten next time code is regenerated.
  * If you need to make any customizations, please use subclass.
  */
-public abstract class _FlattenedTest5 extends BaseDataObject {
+public abstract class _Entity1 extends BaseDataObject {
 
     private static final long serialVersionUID = 1L; 
 
-    public static final NumericProperty<Integer> FT5_ID_PK_PROPERTY = PropertyFactory.createNumeric(ExpressionFactory.dbPathExp("FT5_ID"), Integer.class);
-    public static final String FT5_ID_PK_COLUMN = "FT5_ID";
+    public static final String ID_PK_COLUMN = "ID";
 
-    public static final StringProperty<String> NAME = PropertyFactory.createString("name", String.class);
-    public static final EntityProperty<FlattenedTest1> TO_FT1 = PropertyFactory.createEntity("toFT1", FlattenedTest1.class);
+    public static final EntityProperty<Entity3> TO_ENTITY3 = PropertyFactory.createEntity("toEntity3", Entity3.class);
 
-    protected String name;
 
-    protected Object toFT1;
+    protected Object toEntity3;
 
-    public void setName(String name) {
-        beforePropertyWrite("name", this.name, name);
-        this.name = name;
+    public void setToEntity3(Entity3 toEntity3) {
+        setToOneTarget("toEntity3", toEntity3, true);
     }
 
-    public String getName() {
-        beforePropertyRead("name");
-        return this.name;
-    }
-
-    public void setToFT1(FlattenedTest1 toFT1) {
-        setToOneTarget("toFT1", toFT1, true);
-    }
-
-    public FlattenedTest1 getToFT1() {
-        return (FlattenedTest1)readProperty("toFT1");
+    public Entity3 getToEntity3() {
+        return (Entity3)readProperty("toEntity3");
     }
 
     @Override
@@ -57,10 +41,8 @@ public abstract class _FlattenedTest5 extends BaseDataObject {
         }
 
         switch(propName) {
-            case "name":
-                return this.name;
-            case "toFT1":
-                return this.toFT1;
+            case "toEntity3":
+                return this.toEntity3;
             default:
                 return super.readPropertyDirectly(propName);
         }
@@ -73,11 +55,8 @@ public abstract class _FlattenedTest5 extends BaseDataObject {
         }
 
         switch (propName) {
-            case "name":
-                this.name = (String)val;
-                break;
-            case "toFT1":
-                this.toFT1 = val;
+            case "toEntity3":
+                this.toEntity3 = val;
                 break;
             default:
                 super.writePropertyDirectly(propName, val);
@@ -95,15 +74,13 @@ public abstract class _FlattenedTest5 extends BaseDataObject {
     @Override
     protected void writeState(ObjectOutputStream out) throws IOException {
         super.writeState(out);
-        out.writeObject(this.name);
-        out.writeObject(this.toFT1);
+        out.writeObject(this.toEntity3);
     }
 
     @Override
     protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
         super.readState(in);
-        this.name = (String)in.readObject();
-        this.toFT1 = in.readObject();
+        this.toEntity3 = in.readObject();
     }
 
 }
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_flattened/auto/_Entity2.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_flattened/auto/_Entity2.java
new file mode 100644
index 0000000..db36198
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_flattened/auto/_Entity2.java
@@ -0,0 +1,66 @@
+package org.apache.cayenne.testdo.relationships_flattened.auto;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+import org.apache.cayenne.BaseDataObject;
+
+/**
+ * Class _Entity2 was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _Entity2 extends BaseDataObject {
+
+    private static final long serialVersionUID = 1L; 
+
+    public static final String ID_PK_COLUMN = "ID";
+
+
+
+
+    @Override
+    public Object readPropertyDirectly(String propName) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch(propName) {
+            default:
+                return super.readPropertyDirectly(propName);
+        }
+    }
+
+    @Override
+    public void writePropertyDirectly(String propName, Object val) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch (propName) {
+            default:
+                super.writePropertyDirectly(propName, val);
+        }
+    }
+
+    private void writeObject(ObjectOutputStream out) throws IOException {
+        writeSerialized(out);
+    }
+
+    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        readSerialized(in);
+    }
+
+    @Override
+    protected void writeState(ObjectOutputStream out) throws IOException {
+        super.writeState(out);
+    }
+
+    @Override
+    protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        super.readState(in);
+    }
+
+}
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_flattened/auto/_Entity3.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_flattened/auto/_Entity3.java
new file mode 100644
index 0000000..b1cdf17
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_flattened/auto/_Entity3.java
@@ -0,0 +1,66 @@
+package org.apache.cayenne.testdo.relationships_flattened.auto;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+import org.apache.cayenne.BaseDataObject;
+
+/**
+ * Class _Entity3 was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _Entity3 extends BaseDataObject {
+
+    private static final long serialVersionUID = 1L; 
+
+    public static final String ID_PK_COLUMN = "ID";
+
+
+
+
+    @Override
+    public Object readPropertyDirectly(String propName) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch(propName) {
+            default:
+                return super.readPropertyDirectly(propName);
+        }
+    }
+
+    @Override
+    public void writePropertyDirectly(String propName, Object val) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch (propName) {
+            default:
+                super.writePropertyDirectly(propName, val);
+        }
+    }
+
+    private void writeObject(ObjectOutputStream out) throws IOException {
+        writeSerialized(out);
+    }
+
+    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        readSerialized(in);
+    }
+
+    @Override
+    protected void writeState(ObjectOutputStream out) throws IOException {
+        super.writeState(out);
+    }
+
+    @Override
+    protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        super.readState(in);
+    }
+
+}
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_flattened/auto/_FlattenedCircular.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_flattened/auto/_FlattenedCircular.java
index a5d5471..54b5145 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_flattened/auto/_FlattenedCircular.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_flattened/auto/_FlattenedCircular.java
@@ -6,9 +6,7 @@ import java.io.ObjectOutputStream;
 import java.util.List;
 
 import org.apache.cayenne.BaseDataObject;
-import org.apache.cayenne.exp.ExpressionFactory;
 import org.apache.cayenne.exp.property.ListProperty;
-import org.apache.cayenne.exp.property.NumericProperty;
 import org.apache.cayenne.exp.property.PropertyFactory;
 import org.apache.cayenne.testdo.relationships_flattened.FlattenedCircular;
 
@@ -22,7 +20,6 @@ public abstract class _FlattenedCircular extends BaseDataObject {
 
     private static final long serialVersionUID = 1L; 
 
-    public static final NumericProperty<Integer> ID_PK_PROPERTY = PropertyFactory.createNumeric(ExpressionFactory.dbPathExp("ID"), Integer.class);
     public static final String ID_PK_COLUMN = "ID";
 
     public static final ListProperty<FlattenedCircular> SIDE1S = PropertyFactory.createList("side1s", FlattenedCircular.class);
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_flattened/auto/_FlattenedTest1.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_flattened/auto/_FlattenedTest1.java
index cf46d2c..10a25e4 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_flattened/auto/_FlattenedTest1.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_flattened/auto/_FlattenedTest1.java
@@ -6,9 +6,7 @@ import java.io.ObjectOutputStream;
 import java.util.List;
 
 import org.apache.cayenne.BaseDataObject;
-import org.apache.cayenne.exp.ExpressionFactory;
 import org.apache.cayenne.exp.property.ListProperty;
-import org.apache.cayenne.exp.property.NumericProperty;
 import org.apache.cayenne.exp.property.PropertyFactory;
 import org.apache.cayenne.exp.property.StringProperty;
 import org.apache.cayenne.testdo.relationships_flattened.FlattenedTest2;
@@ -26,7 +24,6 @@ public abstract class _FlattenedTest1 extends BaseDataObject {
 
     private static final long serialVersionUID = 1L; 
 
-    public static final NumericProperty<Integer> FT1_ID_PK_PROPERTY = PropertyFactory.createNumeric(ExpressionFactory.dbPathExp("FT1_ID"), Integer.class);
     public static final String FT1_ID_PK_COLUMN = "FT1_ID";
 
     public static final StringProperty<String> NAME = PropertyFactory.createString("name", String.class);
@@ -67,6 +64,14 @@ public abstract class _FlattenedTest1 extends BaseDataObject {
         return (List<FlattenedTest2>)readProperty("ft2Array");
     }
 
+    public void addToFt3Array(FlattenedTest3 obj) {
+        addToManyTarget("ft3Array", obj, true);
+    }
+
+    public void removeFromFt3Array(FlattenedTest3 obj) {
+        removeToManyTarget("ft3Array", obj, true);
+    }
+
     @SuppressWarnings("unchecked")
     public List<FlattenedTest3> getFt3Array() {
         return (List<FlattenedTest3>)readProperty("ft3Array");
@@ -85,6 +90,14 @@ public abstract class _FlattenedTest1 extends BaseDataObject {
         return (List<FlattenedTest3>)readProperty("ft3OverComplex");
     }
 
+    public void addToFt4ArrayFor1(FlattenedTest4 obj) {
+        addToManyTarget("ft4ArrayFor1", obj, true);
+    }
+
+    public void removeFromFt4ArrayFor1(FlattenedTest4 obj) {
+        removeToManyTarget("ft4ArrayFor1", obj, true);
+    }
+
     @SuppressWarnings("unchecked")
     public List<FlattenedTest4> getFt4ArrayFor1() {
         return (List<FlattenedTest4>)readProperty("ft4ArrayFor1");
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_flattened/auto/_FlattenedTest2.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_flattened/auto/_FlattenedTest2.java
index 4498d64..61c1ca5 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_flattened/auto/_FlattenedTest2.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_flattened/auto/_FlattenedTest2.java
@@ -6,10 +6,8 @@ import java.io.ObjectOutputStream;
 import java.util.List;
 
 import org.apache.cayenne.BaseDataObject;
-import org.apache.cayenne.exp.ExpressionFactory;
 import org.apache.cayenne.exp.property.EntityProperty;
 import org.apache.cayenne.exp.property.ListProperty;
-import org.apache.cayenne.exp.property.NumericProperty;
 import org.apache.cayenne.exp.property.PropertyFactory;
 import org.apache.cayenne.exp.property.StringProperty;
 import org.apache.cayenne.testdo.relationships_flattened.FlattenedTest1;
@@ -25,7 +23,6 @@ public abstract class _FlattenedTest2 extends BaseDataObject {
 
     private static final long serialVersionUID = 1L; 
 
-    public static final NumericProperty<Integer> FT2_ID_PK_PROPERTY = PropertyFactory.createNumeric(ExpressionFactory.dbPathExp("FT2_ID"), Integer.class);
     public static final String FT2_ID_PK_COLUMN = "FT2_ID";
 
     public static final StringProperty<String> NAME = PropertyFactory.createString("name", String.class);
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_flattened/auto/_FlattenedTest3.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_flattened/auto/_FlattenedTest3.java
index 494a03d..836fb9f 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_flattened/auto/_FlattenedTest3.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_flattened/auto/_FlattenedTest3.java
@@ -6,10 +6,8 @@ import java.io.ObjectOutputStream;
 import java.util.List;
 
 import org.apache.cayenne.BaseDataObject;
-import org.apache.cayenne.exp.ExpressionFactory;
 import org.apache.cayenne.exp.property.EntityProperty;
 import org.apache.cayenne.exp.property.ListProperty;
-import org.apache.cayenne.exp.property.NumericProperty;
 import org.apache.cayenne.exp.property.PropertyFactory;
 import org.apache.cayenne.exp.property.StringProperty;
 import org.apache.cayenne.testdo.relationships_flattened.FlattenedTest1;
@@ -26,7 +24,6 @@ public abstract class _FlattenedTest3 extends BaseDataObject {
 
     private static final long serialVersionUID = 1L; 
 
-    public static final NumericProperty<Integer> FT3_ID_PK_PROPERTY = PropertyFactory.createNumeric(ExpressionFactory.dbPathExp("FT3_ID"), Integer.class);
     public static final String FT3_ID_PK_COLUMN = "FT3_ID";
 
     public static final StringProperty<String> NAME = PropertyFactory.createString("name", String.class);
@@ -63,6 +60,10 @@ public abstract class _FlattenedTest3 extends BaseDataObject {
         return (List<FlattenedTest4>)readProperty("ft4Array");
     }
 
+    public void setToFT1(FlattenedTest1 toFT1) {
+        setToOneTarget("toFT1", toFT1, true);
+    }
+
     public FlattenedTest1 getToFT1() {
         return (FlattenedTest1)readProperty("toFT1");
     }
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_flattened/auto/_FlattenedTest5.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_flattened/auto/_FlattenedTest5.java
index b5a3924..0014d42 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_flattened/auto/_FlattenedTest5.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_flattened/auto/_FlattenedTest5.java
@@ -5,9 +5,7 @@ import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 
 import org.apache.cayenne.BaseDataObject;
-import org.apache.cayenne.exp.ExpressionFactory;
 import org.apache.cayenne.exp.property.EntityProperty;
-import org.apache.cayenne.exp.property.NumericProperty;
 import org.apache.cayenne.exp.property.PropertyFactory;
 import org.apache.cayenne.exp.property.StringProperty;
 import org.apache.cayenne.testdo.relationships_flattened.FlattenedTest1;
@@ -22,7 +20,6 @@ public abstract class _FlattenedTest5 extends BaseDataObject {
 
     private static final long serialVersionUID = 1L; 
 
-    public static final NumericProperty<Integer> FT5_ID_PK_PROPERTY = PropertyFactory.createNumeric(ExpressionFactory.dbPathExp("FT5_ID"), Integer.class);
     public static final String FT5_ID_PK_COLUMN = "FT5_ID";
 
     public static final StringProperty<String> NAME = PropertyFactory.createString("name", String.class);
diff --git a/cayenne-server/src/test/resources/cayenne-relationships-flattened.xml b/cayenne-server/src/test/resources/cayenne-relationships-flattened.xml
index c89d35e..cbfaf3e 100644
--- a/cayenne-server/src/test/resources/cayenne-relationships-flattened.xml
+++ b/cayenne-server/src/test/resources/cayenne-relationships-flattened.xml
@@ -1,5 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <domain xmlns="http://cayenne.apache.org/schema/10/domain"
+	 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	 xsi:schemaLocation="http://cayenne.apache.org/schema/10/domain https://cayenne.apache.org/schema/10/domain.xsd"
 	 project-version="10">
 	<map name="relationships-flattened"/>
 </domain>
diff --git a/cayenne-server/src/test/resources/relationships-flattened.map.xml b/cayenne-server/src/test/resources/relationships-flattened.map.xml
index b62e3f3..eb63e63 100644
--- a/cayenne-server/src/test/resources/relationships-flattened.map.xml
+++ b/cayenne-server/src/test/resources/relationships-flattened.map.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <data-map xmlns="http://cayenne.apache.org/schema/10/modelMap"
 	 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	 xsi:schemaLocation="http://cayenne.apache.org/schema/10/modelMap http://cayenne.apache.org/schema/10/modelMap.xsd"
+	 xsi:schemaLocation="http://cayenne.apache.org/schema/10/modelMap https://cayenne.apache.org/schema/10/modelMap.xsd"
 	 project-version="10">
 	<property name="defaultPackage" value="org.apache.cayenne.testdo.relationships_flattened"/>
 	<db-entity name="COMPLEX_JOIN">
@@ -15,6 +15,17 @@
 		<db-attribute name="FT5_FK" type="INTEGER" isMandatory="true"/>
 		<db-attribute name="PK" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
 	</db-entity>
+	<db-entity name="ENTITY1">
+		<db-attribute name="ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
+	</db-entity>
+	<db-entity name="ENTITY2">
+		<db-attribute name="ENTITY1_ID" type="INTEGER"/>
+		<db-attribute name="ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
+	</db-entity>
+	<db-entity name="ENTITY3">
+		<db-attribute name="ENTITY2_ID" type="INTEGER"/>
+		<db-attribute name="ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
+	</db-entity>
 	<db-entity name="FLATTENED_CIRCULAR">
 		<db-attribute name="ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
 	</db-entity>
@@ -29,12 +40,12 @@
 	<db-entity name="FLATTENED_TEST_2">
 		<db-attribute name="FT1_ID" type="INTEGER"/>
 		<db-attribute name="FT2_ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
-		<db-attribute name="NAME" type="VARCHAR" isMandatory="true" length="100"/>
+		<db-attribute name="NAME" type="VARCHAR" length="100"/>
 	</db-entity>
 	<db-entity name="FLATTENED_TEST_3">
 		<db-attribute name="FT2_ID" type="INTEGER"/>
 		<db-attribute name="FT3_ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
-		<db-attribute name="NAME" type="VARCHAR" isMandatory="true" length="100"/>
+		<db-attribute name="NAME" type="VARCHAR" length="100"/>
 	</db-entity>
 	<db-entity name="FLATTENED_TEST_4">
 		<db-attribute name="FT3_ID" type="INTEGER"/>
@@ -45,6 +56,9 @@
 		<db-attribute name="FT5_ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
 		<db-attribute name="NAME" type="VARCHAR" isMandatory="true" length="100"/>
 	</db-entity>
+	<obj-entity name="Entity1" className="org.apache.cayenne.testdo.relationships_flattened.Entity1" dbEntityName="ENTITY1"/>
+	<obj-entity name="Entity2" className="org.apache.cayenne.testdo.relationships_flattened.Entity2" dbEntityName="ENTITY2"/>
+	<obj-entity name="Entity3" className="org.apache.cayenne.testdo.relationships_flattened.Entity3" dbEntityName="ENTITY3"/>
 	<obj-entity name="FlattenedCircular" className="org.apache.cayenne.testdo.relationships_flattened.FlattenedCircular" dbEntityName="FLATTENED_CIRCULAR"/>
 	<obj-entity name="FlattenedTest1" className="org.apache.cayenne.testdo.relationships_flattened.FlattenedTest1" dbEntityName="FLATTENED_TEST_1">
 		<obj-attribute name="name" type="java.lang.String" db-attribute-path="NAME"/>
@@ -73,6 +87,18 @@
 	<db-relationship name="toFT5" source="COMPLEX_JOIN2" target="FLATTENED_TEST_5">
 		<db-attribute-pair source="FT5_FK" target="FT5_ID"/>
 	</db-relationship>
+	<db-relationship name="toEntity2" source="ENTITY1" target="ENTITY2">
+		<db-attribute-pair source="ID" target="ENTITY1_ID"/>
+	</db-relationship>
+	<db-relationship name="toEntity3" source="ENTITY2" target="ENTITY3">
+		<db-attribute-pair source="ID" target="ENTITY2_ID"/>
+	</db-relationship>
+	<db-relationship name="toEntity1" source="ENTITY2" target="ENTITY1">
+		<db-attribute-pair source="ENTITY1_ID" target="ID"/>
+	</db-relationship>
+	<db-relationship name="toEntity2" source="ENTITY3" target="ENTITY2">
+		<db-attribute-pair source="ENTITY2_ID" target="ID"/>
+	</db-relationship>
 	<db-relationship name="side1s" source="FLATTENED_CIRCULAR" target="FLATTENED_CIRCULAR_JOIN" toDependentPK="true" toMany="true">
 		<db-attribute-pair source="ID" target="SIDE2_ID"/>
 	</db-relationship>
@@ -115,6 +141,7 @@
 	<db-relationship name="complexJoin2" source="FLATTENED_TEST_5" target="COMPLEX_JOIN2">
 		<db-attribute-pair source="FT5_ID" target="FT5_FK"/>
 	</db-relationship>
+	<obj-relationship name="toEntity3" source="Entity1" target="Entity3" deleteRule="Nullify" db-relationship-path="toEntity2.toEntity3"/>
 	<obj-relationship name="side1s" source="FlattenedCircular" target="FlattenedCircular" db-relationship-path="side1s.side1"/>
 	<obj-relationship name="side2s" source="FlattenedCircular" target="FlattenedCircular" db-relationship-path="side2s.side2"/>
 	<obj-relationship name="ft2Array" source="FlattenedTest1" target="FlattenedTest2" db-relationship-path="ft2Array"/>
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbentity/DbRelationshipTableModel.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbentity/DbRelationshipTableModel.java
index f355cb9..cb27cad 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbentity/DbRelationshipTableModel.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/dbentity/DbRelationshipTableModel.java
@@ -188,7 +188,6 @@ public class DbRelationshipTableModel extends CayenneTableModel<DbRelationship>
                 .findObjRelationshipsForDbRelationship(mediator, relationship);
         for(ObjRelationship objRelationship : objRelationshipsForDbRelationship) {
             objRelationship.recalculateToManyValue();
-            objRelationship.recalculateReadOnlyValue();
         }
     }