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/07 14:50:15 UTC

[cayenne] branch STABLE-4.0 updated (ca8926b -> 60b1686)

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

abulatski pushed a change to branch STABLE-4.0
in repository https://gitbox.apache.org/repos/asf/cayenne.git.


    from ca8926b  Merge PR #365
     new ebdbcf9  CAY-2582 Double insert of manyToMany relationship mapped to Set  test case from PR #385
     new 60b1686  CAY-2582 Double insert of manyToMany relationship mapped to Set

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 RELEASE-NOTES.txt                                  |   1 +
 .../access/{ToManyMap.java => ToManySet.java}      | 125 ++++++++++++---------
 .../org/apache/cayenne/access/ToManySetFault.java  |   3 +-
 .../apache/cayenne/util/PersistentObjectSet.java   |  12 +-
 .../java/org/apache/cayenne/ManyToManyJoinIT.java  | 109 ++++++++++++++++++
 .../relationships_many_to_many_join/Author.java    |   6 +-
 .../relationships_many_to_many_join/Song.java      |   6 +-
 .../auto/_Author.java}                             |   8 +-
 .../auto/_Song.java                                |  43 +++++++
 .../cayenne/unit/di/server/CayenneProjects.java    |   1 +
 .../cayenne/unit/di/server/SchemaBuilder.java      |   2 +-
 ...=> cayenne-relationships-many-to-many-join.xml} |   2 +-
 .../relationships-many-to-many-join.map.xml        |  38 +++++++
 13 files changed, 285 insertions(+), 71 deletions(-)
 copy cayenne-server/src/main/java/org/apache/cayenne/access/{ToManyMap.java => ToManySet.java} (56%)
 create mode 100644 cayenne-server/src/test/java/org/apache/cayenne/ManyToManyJoinIT.java
 copy cayenne-commitlog/src/test/java/org/apache/cayenne/commitlog/db/E3.java => cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/Author.java (84%)
 copy cayenne-commitlog/src/test/java/org/apache/cayenne/commitlog/db/E3.java => cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/Song.java (85%)
 copy cayenne-server/src/test/java/org/apache/cayenne/testdo/{lifecycle_callbacks_order/auto/_Lifecycle.java => relationships_many_to_many_join/auto/_Author.java} (71%)
 create mode 100644 cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/auto/_Song.java
 copy cayenne-server/src/test/resources/{cayenne-cay-2032.xml => cayenne-relationships-many-to-many-join.xml} (62%)
 create mode 100644 cayenne-server/src/test/resources/relationships-many-to-many-join.map.xml


[cayenne] 01/02: CAY-2582 Double insert of manyToMany relationship mapped to Set test case from PR #385

Posted by ab...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit ebdbcf90616fef1dbf0d08a224722eea4e9c0d4c
Author: Nikita Timofeev <st...@gmail.com>
AuthorDate: Tue May 21 17:24:07 2019 +0300

    CAY-2582 Double insert of manyToMany relationship mapped to Set
     test case from PR #385
    
    (cherry picked from commit 326f1de071cc871561891557baf5879bd1ba06ce)
---
 .../java/org/apache/cayenne/ManyToManyJoinIT.java  |  51 ++++++++++
 .../relationships_many_to_many_join/Author.java    |   9 ++
 .../relationships_many_to_many_join/Song.java      |   9 ++
 .../auto/_Author.java                              |  86 ++++++++++++++++
 .../auto/_Song.java                                | 110 +++++++++++++++++++++
 .../cayenne/unit/di/server/CayenneProjects.java    |   1 +
 .../cayenne/unit/di/server/SchemaBuilder.java      |   2 +-
 .../cayenne-relationships-many-to-many-join.xml    |   7 ++
 .../relationships-many-to-many-join.map.xml        |  38 +++++++
 9 files changed, 312 insertions(+), 1 deletion(-)

diff --git a/cayenne-server/src/test/java/org/apache/cayenne/ManyToManyJoinIT.java b/cayenne-server/src/test/java/org/apache/cayenne/ManyToManyJoinIT.java
new file mode 100644
index 0000000..0949b5e
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/ManyToManyJoinIT.java
@@ -0,0 +1,51 @@
+/*****************************************************************
+ *   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;
+
+import static org.junit.Assert.*;
+
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.testdo.relationships_many_to_many_join.Author;
+import org.apache.cayenne.testdo.relationships_many_to_many_join.Song;
+import org.apache.cayenne.unit.di.server.CayenneProjects;
+import org.apache.cayenne.unit.di.server.ServerCase;
+import org.apache.cayenne.unit.di.server.UseServerRuntime;
+import org.junit.Test;
+
+@UseServerRuntime(CayenneProjects.RELATIONSHIPS_MANY_TO_MANY_JOIN_PROJECT)
+public class ManyToManyJoinIT extends ServerCase {
+
+    @Inject
+    private ObjectContext context;
+
+    @Test
+    public void testManyToManyJoinWithFlattenedRelationship() throws Exception {
+    	Author author = context.newObject(Author.class);
+    	author.setName("Bob Dylan");
+    	
+        Song song = context.newObject(Song.class);
+        song.setName("House of the Rising Sun");
+
+        song.addToAuthors(author);
+        
+        context.commitChanges();
+        assertEquals(author, song.getAuthors().iterator().next());
+    }
+
+}
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/Author.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/Author.java
new file mode 100644
index 0000000..a50741e
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/Author.java
@@ -0,0 +1,9 @@
+package org.apache.cayenne.testdo.relationships_many_to_many_join;
+
+import org.apache.cayenne.testdo.relationships_many_to_many_join.auto._Author;
+
+public class Author extends _Author {
+
+    private static final long serialVersionUID = 1L; 
+
+}
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/Song.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/Song.java
new file mode 100644
index 0000000..8df7056
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/Song.java
@@ -0,0 +1,9 @@
+package org.apache.cayenne.testdo.relationships_many_to_many_join;
+
+import org.apache.cayenne.testdo.relationships_many_to_many_join.auto._Song;
+
+public class Song extends _Song {
+
+    private static final long serialVersionUID = 1L; 
+
+}
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/auto/_Author.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/auto/_Author.java
new file mode 100644
index 0000000..11554d9
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/auto/_Author.java
@@ -0,0 +1,86 @@
+package org.apache.cayenne.testdo.relationships_many_to_many_join.auto;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+import org.apache.cayenne.BaseDataObject;
+import org.apache.cayenne.exp.Property;
+
+/**
+ * Class _Author 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 _Author extends BaseDataObject {
+
+    private static final long serialVersionUID = 1L; 
+
+    public static final String AUTHOR_ID_PK_COLUMN = "AUTHOR_ID";
+
+    public static final Property<String> NAME = Property.create("name", String.class);
+
+    protected String name;
+
+
+    public void setName(String name) {
+        beforePropertyWrite("name", this.name, name);
+        this.name = name;
+    }
+
+    public String getName() {
+        beforePropertyRead("name");
+        return this.name;
+    }
+
+    @Override
+    public Object readPropertyDirectly(String propName) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch(propName) {
+            case "name":
+                return this.name;
+            default:
+                return super.readPropertyDirectly(propName);
+        }
+    }
+
+    @Override
+    public void writePropertyDirectly(String propName, Object val) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch (propName) {
+            case "name":
+                this.name = (String)val;
+                break;
+            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);
+        out.writeObject(this.name);
+    }
+
+    @Override
+    protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        super.readState(in);
+        this.name = (String)in.readObject();
+    }
+
+}
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/auto/_Song.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/auto/_Song.java
new file mode 100644
index 0000000..15160ca
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/auto/_Song.java
@@ -0,0 +1,110 @@
+package org.apache.cayenne.testdo.relationships_many_to_many_join.auto;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.Set;
+
+import org.apache.cayenne.BaseDataObject;
+import org.apache.cayenne.exp.Property;
+import org.apache.cayenne.testdo.relationships_many_to_many_join.Author;
+
+/**
+ * Class _Song 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 _Song extends BaseDataObject {
+
+    private static final long serialVersionUID = 1L; 
+
+    public static final String SONG_ID_PK_COLUMN = "SONG_ID";
+
+    public static final Property<String> NAME = Property.create("name", String.class);
+    public static final Property<Set<Author>> AUTHORS = Property.create("authors", Set.class);
+
+    protected String name;
+
+    protected Object authors;
+
+    public void setName(String name) {
+        beforePropertyWrite("name", this.name, name);
+        this.name = name;
+    }
+
+    public String getName() {
+        beforePropertyRead("name");
+        return this.name;
+    }
+
+    public void addToAuthors(Author obj) {
+        addToManyTarget("authors", obj, true);
+    }
+
+    public void removeFromAuthors(Author obj) {
+        removeToManyTarget("authors", obj, true);
+    }
+
+    @SuppressWarnings("unchecked")
+    public Set<Author> getAuthors() {
+        return (Set<Author>)readProperty("authors");
+    }
+
+    @Override
+    public Object readPropertyDirectly(String propName) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch(propName) {
+            case "name":
+                return this.name;
+            case "authors":
+                return this.authors;
+            default:
+                return super.readPropertyDirectly(propName);
+        }
+    }
+
+    @Override
+    public void writePropertyDirectly(String propName, Object val) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch (propName) {
+            case "name":
+                this.name = (String)val;
+                break;
+            case "authors":
+                this.authors = val;
+                break;
+            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);
+        out.writeObject(this.name);
+        out.writeObject(this.authors);
+    }
+
+    @Override
+    protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        super.readState(in);
+        this.name = (String)in.readObject();
+        this.authors = in.readObject();
+    }
+
+}
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/CayenneProjects.java b/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/CayenneProjects.java
index d55e5bf..8cda3b9 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/CayenneProjects.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/CayenneProjects.java
@@ -61,6 +61,7 @@ public class CayenneProjects {
     public static final String REFLEXIVE_PROJECT = "cayenne-reflexive.xml";
     public static final String RELATIONSHIPS_PROJECT = "cayenne-relationships.xml";
     public static final String RELATIONSHIPS_ACTIVITY_PROJECT = "cayenne-relationships-activity.xml";
+    public static final String RELATIONSHIPS_MANY_TO_MANY_JOIN_PROJECT = "cayenne-relationships-many-to-many-join.xml";
     public static final String RELATIONSHIPS_CHILD_MASTER_PROJECT = "cayenne-relationships-child-master.xml";
     public static final String RELATIONSHIPS_CLOB_PROJECT = "cayenne-relationships-clob.xml";
     public static final String RELATIONSHIPS_COLLECTION_TO_MANY_PROJECT = "cayenne-relationships-collection-to-many.xml";
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/SchemaBuilder.java b/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/SchemaBuilder.java
index ed351b4..8eeb558 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/SchemaBuilder.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/SchemaBuilder.java
@@ -73,7 +73,7 @@ public class SchemaBuilder {
 			"locking.map.xml", "soft-delete.map.xml", "empty.map.xml", "relationships.map.xml",
 			"relationships-activity.map.xml", "relationships-delete-rules.map.xml",
 			"relationships-collection-to-many.map.xml", "relationships-child-master.map.xml",
-			"relationships-clob.map.xml", "relationships-flattened.map.xml", "relationships-set-to-many.map.xml",
+			"relationships-clob.map.xml", "relationships-flattened.map.xml", "relationships-many-to-many-join.map.xml", "relationships-set-to-many.map.xml",
 			"relationships-to-many-fk.map.xml", "relationships-to-one-fk.map.xml", "return-types.map.xml",
 			"uuid.map.xml", "multi-tier.map.xml", "reflexive.map.xml", "delete-rules.map.xml",
             "lifecycle-callbacks-order.map.xml", "lifecycles.map.xml", "map-to-many.map.xml", "toone.map.xml", "meaningful-pk.map.xml",
diff --git a/cayenne-server/src/test/resources/cayenne-relationships-many-to-many-join.xml b/cayenne-server/src/test/resources/cayenne-relationships-many-to-many-join.xml
new file mode 100644
index 0000000..0121356
--- /dev/null
+++ b/cayenne-server/src/test/resources/cayenne-relationships-many-to-many-join.xml
@@ -0,0 +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-many-to-many-join"/>
+</domain>
diff --git a/cayenne-server/src/test/resources/relationships-many-to-many-join.map.xml b/cayenne-server/src/test/resources/relationships-many-to-many-join.map.xml
new file mode 100644
index 0000000..38ef803
--- /dev/null
+++ b/cayenne-server/src/test/resources/relationships-many-to-many-join.map.xml
@@ -0,0 +1,38 @@
+<?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 https://cayenne.apache.org/schema/10/modelMap.xsd"
+	 project-version="10">
+	<property name="defaultPackage" value="org.apache.cayenne.testdo.relationships_many_to_many_join"/>
+	<db-entity name="X_AUTHOR">
+		<db-attribute name="AUTHOR_ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
+		<db-attribute name="AUTHOR_NAME" type="VARCHAR" isMandatory="true" length="50"/>
+	</db-entity>
+	<db-entity name="X_SONG">
+		<db-attribute name="SONG_ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
+		<db-attribute name="SONG_NAME" type="VARCHAR" isMandatory="true" length="50"/>
+	</db-entity>
+	<db-entity name="X_SONGAUTHOR">
+		<db-attribute name="AUTHOR_ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
+		<db-attribute name="SONG_ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
+	</db-entity>
+	<obj-entity name="Author" className="org.apache.cayenne.testdo.relationships_many_to_many_join.Author" dbEntityName="X_AUTHOR">
+		<obj-attribute name="name" type="java.lang.String" db-attribute-path="AUTHOR_NAME"/>
+	</obj-entity>
+	<obj-entity name="Song" className="org.apache.cayenne.testdo.relationships_many_to_many_join.Song" dbEntityName="X_SONG">
+		<obj-attribute name="name" type="java.lang.String" db-attribute-path="SONG_NAME"/>
+	</obj-entity>
+	<db-relationship name="songAuthor" source="X_AUTHOR" target="X_SONGAUTHOR" toDependentPK="true" toMany="true">
+		<db-attribute-pair source="AUTHOR_ID" target="AUTHOR_ID"/>
+	</db-relationship>
+	<db-relationship name="songAuthor" source="X_SONG" target="X_SONGAUTHOR" toDependentPK="true" toMany="true">
+		<db-attribute-pair source="SONG_ID" target="SONG_ID"/>
+	</db-relationship>
+	<db-relationship name="song" source="X_SONGAUTHOR" target="X_SONG">
+		<db-attribute-pair source="SONG_ID" target="SONG_ID"/>
+	</db-relationship>
+	<db-relationship name="author" source="X_SONGAUTHOR" target="X_AUTHOR">
+		<db-attribute-pair source="AUTHOR_ID" target="AUTHOR_ID"/>
+	</db-relationship>
+	<obj-relationship name="authors" source="Song" target="Author" collection-type="java.util.Set" deleteRule="Cascade" db-relationship-path="songAuthor.author"/>
+</data-map>


[cayenne] 02/02: CAY-2582 Double insert of manyToMany relationship mapped to Set

Posted by ab...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 60b16868953607ad9e4eb67f5843ac93aec44376
Author: Arseni Bulatski <an...@gmail.com>
AuthorDate: Fri Jun 7 17:48:36 2019 +0300

    CAY-2582 Double insert of manyToMany relationship mapped to Set
---
 RELEASE-NOTES.txt                                  |  1 +
 .../java/org/apache/cayenne/access/ToManySet.java  | 72 +++++++++++++++++++++
 .../org/apache/cayenne/access/ToManySetFault.java  |  3 +-
 .../apache/cayenne/util/PersistentObjectSet.java   | 12 ++--
 .../java/org/apache/cayenne/ManyToManyJoinIT.java  | 66 +++++++++++++++++--
 .../relationships_many_to_many_join/Author.java    | 18 ++++++
 .../relationships_many_to_many_join/Song.java      | 18 ++++++
 .../auto/_Author.java                              | 67 ++-----------------
 .../auto/_Song.java                                | 75 ++--------------------
 .../cayenne-relationships-many-to-many-join.xml    |  5 +-
 .../relationships-many-to-many-join.map.xml        | 10 +--
 11 files changed, 194 insertions(+), 153 deletions(-)

diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index ece7e32..40100e7 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -22,6 +22,7 @@ Bug Fixes:
 CAY-2550 Modeler: ObjAttribute inspector modifies wrong columns in attribute table
 CAY-2559 Modeler: Warning dialog shows wrong information after changing target entity in dbRelationship
 CAY-2573 DI field injection is triggered when creating sql Driver
+CAY-2582 Double insert of manyToMany relationship mapped to Set
 CAY-2584 Crypto: can't use ColumnSelect with encrypted columns
 
 ----------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/ToManySet.java b/cayenne-server/src/main/java/org/apache/cayenne/access/ToManySet.java
new file mode 100644
index 0000000..6dcfbcc
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/ToManySet.java
@@ -0,0 +1,72 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.access;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.cayenne.PersistenceState;
+import org.apache.cayenne.Persistent;
+import org.apache.cayenne.util.PersistentObjectSet;
+
+public class ToManySet extends PersistentObjectSet implements Serializable {
+
+    protected ToManySet(Persistent relationshipOwner, String relationshipName) {
+        super(relationshipOwner, relationshipName);
+    }
+
+    @Override
+    protected boolean shouldAddToRemovedFromUnresolvedSet(Object object) {
+        // No point in adding a new or transient object -- these will never be fetched
+        // from the database.
+        if (object instanceof Persistent) {
+            Persistent dataObject = (Persistent) object;
+            return (dataObject.getPersistenceState() != PersistenceState.TRANSIENT)
+                    && (dataObject.getPersistenceState() != PersistenceState.NEW);
+        }
+        return true;
+    }
+
+    @Override
+    protected void postprocessAdd(Collection<?> collection) {
+        // no need for this operation for DataObjects...
+    }
+
+    @Override
+    protected void postprocessRemove(Collection<?> collection) {
+        // no need for this operation for DataObjects...
+    }
+
+    @Override
+    protected void postprocessAdd(Object addedObject) {
+        // no need for this operation for DataObjects...
+    }
+
+    @Override
+    protected void postprocessRemove(Object removedObject) {
+        // no need for this operation for DataObjects...
+    }
+
+    @Override
+    protected void updateReverse(List resolved) {
+        // no need for this operation for DataObjects...
+    }
+
+}
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/ToManySetFault.java b/cayenne-server/src/main/java/org/apache/cayenne/access/ToManySetFault.java
index 44e2e10..4114b0d 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/ToManySetFault.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/ToManySetFault.java
@@ -20,7 +20,6 @@ package org.apache.cayenne.access;
 
 import org.apache.cayenne.Fault;
 import org.apache.cayenne.Persistent;
-import org.apache.cayenne.util.PersistentObjectSet;
 
 /**
  * @since 3.0
@@ -29,7 +28,7 @@ public class ToManySetFault extends Fault {
 
     @Override
     public Object resolveFault(Persistent sourceObject, String relationshipName) {
-        return new PersistentObjectSet(sourceObject, relationshipName);
+        return new ToManySet(sourceObject, relationshipName);
     }
 
 }
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/util/PersistentObjectSet.java b/cayenne-server/src/main/java/org/apache/cayenne/util/PersistentObjectSet.java
index 0275e5a..c3e4242 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/util/PersistentObjectSet.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/util/PersistentObjectSet.java
@@ -110,6 +110,10 @@ public class PersistentObjectSet extends RelationshipFault
         this.objectSet = objectSet;
     }
 
+    protected boolean shouldAddToRemovedFromUnresolvedSet(Object object) {
+        return true;
+    }
+
     // ====================================================
     // Standard Set Methods.
     // ====================================================
@@ -323,19 +327,19 @@ public class PersistentObjectSet extends RelationshipFault
         return true;
     }
 
-    void postprocessAdd(Collection<?> collection) {
+    protected void postprocessAdd(Collection<?> collection) {
         for (Object next : collection) {
             postprocessAdd(next);
         }
     }
 
-    void postprocessRemove(Collection<?> collection) {
+    protected void postprocessRemove(Collection<?> collection) {
         for (Object next : collection) {
             postprocessRemove(next);
         }
     }
 
-    void postprocessAdd(Object addedObject) {
+    protected void postprocessAdd(Object addedObject) {
 
         // notify ObjectContext
         if (relationshipOwner.getObjectContext() != null) {
@@ -351,7 +355,7 @@ public class PersistentObjectSet extends RelationshipFault
         }
     }
 
-    void postprocessRemove(Object removedObject) {
+    protected void postprocessRemove(Object removedObject) {
 
         // notify ObjectContext
         if (relationshipOwner.getObjectContext() != null) {
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/ManyToManyJoinIT.java b/cayenne-server/src/test/java/org/apache/cayenne/ManyToManyJoinIT.java
index 0949b5e..b8b25e8 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/ManyToManyJoinIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/ManyToManyJoinIT.java
@@ -18,9 +18,13 @@
  ****************************************************************/
 package org.apache.cayenne;
 
-import static org.junit.Assert.*;
+import java.util.concurrent.atomic.AtomicInteger;
 
+import org.apache.cayenne.access.DataDomain;
+import org.apache.cayenne.configuration.server.ServerRuntime;
 import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.graph.GraphChangeHandler;
+import org.apache.cayenne.graph.GraphDiff;
 import org.apache.cayenne.testdo.relationships_many_to_many_join.Author;
 import org.apache.cayenne.testdo.relationships_many_to_many_join.Song;
 import org.apache.cayenne.unit.di.server.CayenneProjects;
@@ -28,24 +32,78 @@ import org.apache.cayenne.unit.di.server.ServerCase;
 import org.apache.cayenne.unit.di.server.UseServerRuntime;
 import org.junit.Test;
 
+import static org.junit.Assert.assertEquals;
+
 @UseServerRuntime(CayenneProjects.RELATIONSHIPS_MANY_TO_MANY_JOIN_PROJECT)
 public class ManyToManyJoinIT extends ServerCase {
 
     @Inject
+    private ServerRuntime serverRuntime;
+
+    @Inject
     private ObjectContext context;
 
     @Test
-    public void testManyToManyJoinWithFlattenedRelationship() throws Exception {
+    public void testManyToManyJoinWithFlattenedRelationship() {
     	Author author = context.newObject(Author.class);
     	author.setName("Bob Dylan");
-    	
+
         Song song = context.newObject(Song.class);
         song.setName("House of the Rising Sun");
 
         song.addToAuthors(author);
-        
+
         context.commitChanges();
         assertEquals(author, song.getAuthors().iterator().next());
     }
 
+    @Test
+    public void testManyToManyJoinWithFlattenedRelationshipEvents() {
+        DataDomain domain = serverRuntime.getDataDomain();
+        DataChannelFilter f1 = new MockDataChannelFilter() {
+            @Override
+            public GraphDiff onSync(
+                    ObjectContext originatingContext,
+                    GraphDiff changes,
+                    int syncType,
+                    DataChannelFilterChain filterChain) {
+
+                GraphDiff response = filterChain.onSync(
+                        originatingContext,
+                        changes,
+                        syncType);
+                final AtomicInteger count = new AtomicInteger(0);
+                changes.apply(new GraphChangeHandler() {
+                    @Override
+                    public void nodeIdChanged(Object nodeId, Object newId) {}
+
+                    @Override
+                    public void nodeCreated(Object nodeId) {}
+
+                    @Override
+                    public void nodeRemoved(Object nodeId) {}
+
+                    @Override
+                    public void nodePropertyChanged(Object nodeId, String property, Object oldValue, Object newValue) {}
+
+                    @Override
+                    public void arcCreated(Object nodeId, Object targetNodeId, Object arcId) {
+                        count.incrementAndGet();
+                    }
+
+                    @Override
+                    public void arcDeleted(Object nodeId, Object targetNodeId, Object arcId) {}
+                });
+                assertEquals(1, count.get());
+                return response;
+            }
+        };
+        domain.addFilter(f1);
+        Author author = context.newObject(Author.class);
+        author.setName("Bob Dylan");
+        Song song = context.newObject(Song.class);
+        song.setName("House of the Rising Sun");
+        song.addToAuthors(author);
+        context.commitChanges();
+    }
 }
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/Author.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/Author.java
index a50741e..e516b70 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/Author.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/Author.java
@@ -1,3 +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
+ *
+ *    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.testdo.relationships_many_to_many_join;
 
 import org.apache.cayenne.testdo.relationships_many_to_many_join.auto._Author;
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/Song.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/Song.java
index 8df7056..abf7b8a 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/Song.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/Song.java
@@ -1,3 +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
+ *
+ *    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.testdo.relationships_many_to_many_join;
 
 import org.apache.cayenne.testdo.relationships_many_to_many_join.auto._Song;
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/auto/_Author.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/auto/_Author.java
index 11554d9..999a0a6 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/auto/_Author.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/auto/_Author.java
@@ -1,10 +1,6 @@
 package org.apache.cayenne.testdo.relationships_many_to_many_join.auto;
 
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-
-import org.apache.cayenne.BaseDataObject;
+import org.apache.cayenne.CayenneDataObject;
 import org.apache.cayenne.exp.Property;
 
 /**
@@ -13,7 +9,7 @@ import org.apache.cayenne.exp.Property;
  * since it may be overwritten next time code is regenerated.
  * If you need to make any customizations, please use subclass.
  */
-public abstract class _Author extends BaseDataObject {
+public abstract class _Author extends CayenneDataObject {
 
     private static final long serialVersionUID = 1L; 
 
@@ -21,66 +17,11 @@ public abstract class _Author extends BaseDataObject {
 
     public static final Property<String> NAME = Property.create("name", String.class);
 
-    protected String name;
-
-
     public void setName(String name) {
-        beforePropertyWrite("name", this.name, name);
-        this.name = name;
+        writeProperty("name", name);
     }
-
     public String getName() {
-        beforePropertyRead("name");
-        return this.name;
-    }
-
-    @Override
-    public Object readPropertyDirectly(String propName) {
-        if(propName == null) {
-            throw new IllegalArgumentException();
-        }
-
-        switch(propName) {
-            case "name":
-                return this.name;
-            default:
-                return super.readPropertyDirectly(propName);
-        }
-    }
-
-    @Override
-    public void writePropertyDirectly(String propName, Object val) {
-        if(propName == null) {
-            throw new IllegalArgumentException();
-        }
-
-        switch (propName) {
-            case "name":
-                this.name = (String)val;
-                break;
-            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);
-        out.writeObject(this.name);
-    }
-
-    @Override
-    protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
-        super.readState(in);
-        this.name = (String)in.readObject();
+        return (String)readProperty("name");
     }
 
 }
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/auto/_Song.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/auto/_Song.java
index 15160ca..c639c9f 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/auto/_Song.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/auto/_Song.java
@@ -1,11 +1,8 @@
 package org.apache.cayenne.testdo.relationships_many_to_many_join.auto;
 
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
 import java.util.Set;
 
-import org.apache.cayenne.BaseDataObject;
+import org.apache.cayenne.CayenneDataObject;
 import org.apache.cayenne.exp.Property;
 import org.apache.cayenne.testdo.relationships_many_to_many_join.Author;
 
@@ -15,7 +12,7 @@ import org.apache.cayenne.testdo.relationships_many_to_many_join.Author;
  * since it may be overwritten next time code is regenerated.
  * If you need to make any customizations, please use subclass.
  */
-public abstract class _Song extends BaseDataObject {
+public abstract class _Song extends CayenneDataObject {
 
     private static final long serialVersionUID = 1L; 
 
@@ -24,87 +21,23 @@ public abstract class _Song extends BaseDataObject {
     public static final Property<String> NAME = Property.create("name", String.class);
     public static final Property<Set<Author>> AUTHORS = Property.create("authors", Set.class);
 
-    protected String name;
-
-    protected Object authors;
-
     public void setName(String name) {
-        beforePropertyWrite("name", this.name, name);
-        this.name = name;
+        writeProperty("name", name);
     }
-
     public String getName() {
-        beforePropertyRead("name");
-        return this.name;
+        return (String)readProperty("name");
     }
 
     public void addToAuthors(Author obj) {
         addToManyTarget("authors", obj, true);
     }
-
     public void removeFromAuthors(Author obj) {
         removeToManyTarget("authors", obj, true);
     }
-
     @SuppressWarnings("unchecked")
     public Set<Author> getAuthors() {
         return (Set<Author>)readProperty("authors");
     }
 
-    @Override
-    public Object readPropertyDirectly(String propName) {
-        if(propName == null) {
-            throw new IllegalArgumentException();
-        }
-
-        switch(propName) {
-            case "name":
-                return this.name;
-            case "authors":
-                return this.authors;
-            default:
-                return super.readPropertyDirectly(propName);
-        }
-    }
-
-    @Override
-    public void writePropertyDirectly(String propName, Object val) {
-        if(propName == null) {
-            throw new IllegalArgumentException();
-        }
-
-        switch (propName) {
-            case "name":
-                this.name = (String)val;
-                break;
-            case "authors":
-                this.authors = val;
-                break;
-            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);
-        out.writeObject(this.name);
-        out.writeObject(this.authors);
-    }
-
-    @Override
-    protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
-        super.readState(in);
-        this.name = (String)in.readObject();
-        this.authors = in.readObject();
-    }
 
 }
diff --git a/cayenne-server/src/test/resources/cayenne-relationships-many-to-many-join.xml b/cayenne-server/src/test/resources/cayenne-relationships-many-to-many-join.xml
index 0121356..d93fe1c 100644
--- a/cayenne-server/src/test/resources/cayenne-relationships-many-to-many-join.xml
+++ b/cayenne-server/src/test/resources/cayenne-relationships-many-to-many-join.xml
@@ -1,7 +1,4 @@
 <?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">
+<domain project-version="9">
 	<map name="relationships-many-to-many-join"/>
 </domain>
diff --git a/cayenne-server/src/test/resources/relationships-many-to-many-join.map.xml b/cayenne-server/src/test/resources/relationships-many-to-many-join.map.xml
index 38ef803..07da692 100644
--- a/cayenne-server/src/test/resources/relationships-many-to-many-join.map.xml
+++ b/cayenne-server/src/test/resources/relationships-many-to-many-join.map.xml
@@ -1,8 +1,8 @@
 <?xml version="1.0" encoding="utf-8"?>
-<data-map xmlns="http://cayenne.apache.org/schema/10/modelMap"
+<data-map xmlns="http://cayenne.apache.org/schema/9/modelMap"
 	 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	 xsi:schemaLocation="http://cayenne.apache.org/schema/10/modelMap https://cayenne.apache.org/schema/10/modelMap.xsd"
-	 project-version="10">
+	 xsi:schemaLocation="http://cayenne.apache.org/schema/9/modelMap https://cayenne.apache.org/schema/9/modelMap.xsd"
+	 project-version="9">
 	<property name="defaultPackage" value="org.apache.cayenne.testdo.relationships_many_to_many_join"/>
 	<db-entity name="X_AUTHOR">
 		<db-attribute name="AUTHOR_ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
@@ -28,10 +28,10 @@
 	<db-relationship name="songAuthor" source="X_SONG" target="X_SONGAUTHOR" toDependentPK="true" toMany="true">
 		<db-attribute-pair source="SONG_ID" target="SONG_ID"/>
 	</db-relationship>
-	<db-relationship name="song" source="X_SONGAUTHOR" target="X_SONG">
+	<db-relationship name="song" source="X_SONGAUTHOR" target="X_SONG" toMany="false">
 		<db-attribute-pair source="SONG_ID" target="SONG_ID"/>
 	</db-relationship>
-	<db-relationship name="author" source="X_SONGAUTHOR" target="X_AUTHOR">
+	<db-relationship name="author" source="X_SONGAUTHOR" target="X_AUTHOR" toMany="false">
 		<db-attribute-pair source="AUTHOR_ID" target="AUTHOR_ID"/>
 	</db-relationship>
 	<obj-relationship name="authors" source="Song" target="Author" collection-type="java.util.Set" deleteRule="Cascade" db-relationship-path="songAuthor.author"/>