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 13:45:43 UTC

[cayenne] branch STABLE-4.1 updated (9b82228 -> 8fe18f7)

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

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


    from 9b82228  CAY-2573 DI field injection is triggered when creating sql Driver
     new 87f368e  CAY-2582 Double insert of manyToMany relationship mapped to Set
     new 5999f6a  CAY-2582 Double insert of manyToMany relationship mapped to Set  test case from PR #385
     new 8fe18f7  Update RELEASE-NOTES

The 3 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 +
 .../org/apache/cayenne/access/ToManyListFault.java |   2 +-
 .../access/{ToManyList.java => ToManySet.java}     |  50 ++-------
 .../org/apache/cayenne/access/ToManySetFault.java  |   3 +-
 .../apache/cayenne/util/PersistentObjectList.java  |   6 +-
 .../apache/cayenne/util/PersistentObjectMap.java   |   2 +-
 .../apache/cayenne/util/PersistentObjectSet.java   | 115 +++++++++++++--------
 ...nyToManyNoJoinIT.java => ManyToManyJoinIT.java} |  35 +++----
 .../relationships_many_to_many_join/Author.java    |   9 ++
 .../relationships_many_to_many_join/Song.java      |   9 ++
 .../auto/_Author.java}                             |   8 +-
 .../auto/_Song.java}                               |  40 +++----
 .../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 +++++++
 16 files changed, 186 insertions(+), 137 deletions(-)
 copy cayenne-server/src/main/java/org/apache/cayenne/access/{ToManyList.java => ToManySet.java} (57%)
 copy cayenne-server/src/test/java/org/apache/cayenne/{ManyToManyNoJoinIT.java => ManyToManyJoinIT.java} (60%)
 create mode 100644 cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/Author.java
 create mode 100644 cayenne-server/src/test/java/org/apache/cayenne/testdo/relationships_many_to_many_join/Song.java
 copy cayenne-server/src/test/java/org/apache/cayenne/testdo/{lifecycle_callbacks_order/auto/_Lifecycle.java => relationships_many_to_many_join/auto/_Author.java} (90%)
 copy cayenne-server/src/test/java/org/apache/cayenne/testdo/{relationships_child_master/auto/_Master.java => relationships_many_to_many_join/auto/_Song.java} (70%)
 copy cayenne-server/src/test/resources/{cayenne-cay-2521.xml => cayenne-relationships-many-to-many-join.xml} (86%)
 create mode 100644 cayenne-server/src/test/resources/relationships-many-to-many-join.map.xml


[cayenne] 02/03: 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.1
in repository https://gitbox.apache.org/repos/asf/cayenne.git

commit 5999f6af8e0e3acf36293f4ace2adf7efe85da2f
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 fd12258..e1e79a6 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 9017e99..ad6647c 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
@@ -74,7 +74,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] 03/03: Update RELEASE-NOTES

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.1
in repository https://gitbox.apache.org/repos/asf/cayenne.git

commit 8fe18f7f62aa56c3f96e0f884b9e2288897d98c5
Author: Arseni Bulatski <an...@gmail.com>
AuthorDate: Fri Jun 7 16:30:05 2019 +0300

    Update RELEASE-NOTES
    
    (cherry picked from commit d79b9fa976a4949cdee6bc8f6b538b7641cac0d8)
---
 RELEASE-NOTES.txt | 1 +
 1 file changed, 1 insertion(+)

diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index ff31d46..d0ac1c6 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -15,6 +15,7 @@ Date:
 Bug Fixes:
 
 CAY-2573 DI field injection is triggered when creating sql Driver
+CAY-2582 Double insert of manyToMany relationship mapped to Set
 
 ----------------------------------
 Release: 4.1.B2


[cayenne] 01/03: 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.1
in repository https://gitbox.apache.org/repos/asf/cayenne.git

commit 87f368e2bde240334a348d673ae985d0af4c8eec
Author: Nikita Timofeev <st...@gmail.com>
AuthorDate: Tue May 21 17:08:06 2019 +0300

    CAY-2582 Double insert of manyToMany relationship mapped to Set
    
    (cherry picked from commit 21167714f4400ac6d4b3a905c595e4dd0f1c4450)
---
 .../org/apache/cayenne/access/ToManyListFault.java |   2 +-
 .../java/org/apache/cayenne/access/ToManySet.java  |  72 +++++++++++++
 .../org/apache/cayenne/access/ToManySetFault.java  |   3 +-
 .../apache/cayenne/util/PersistentObjectList.java  |   6 +-
 .../apache/cayenne/util/PersistentObjectMap.java   |   2 +-
 .../apache/cayenne/util/PersistentObjectSet.java   | 115 +++++++++++++--------
 6 files changed, 150 insertions(+), 50 deletions(-)

diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/ToManyListFault.java b/cayenne-server/src/main/java/org/apache/cayenne/access/ToManyListFault.java
index 76562b0..e3fafce 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/ToManyListFault.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/ToManyListFault.java
@@ -31,6 +31,6 @@ public class ToManyListFault extends Fault {
      */
     @Override
     public Object resolveFault(Persistent sourceObject, String relationshipName) {
-        return new ToManyList(sourceObject, relationshipName);
+        return new ToManyList<>(sourceObject, relationshipName);
     }
 }
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..42c71ca
--- /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<E> extends PersistentObjectSet<E> implements Serializable {
+
+    protected ToManySet(Persistent relationshipOwner, String relationshipName) {
+        super(relationshipOwner, relationshipName);
+    }
+
+    @Override
+    protected boolean shouldAddToRemovedFromUnresolvedSet(E 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<? extends E> collection) {
+        // no need for this operation for DataObjects...
+    }
+
+    @Override
+    protected void postprocessRemove(Collection<? extends E> collection) {
+        // no need for this operation for DataObjects...
+    }
+
+    @Override
+    protected void postprocessAdd(E addedObject) {
+        // no need for this operation for DataObjects...
+    }
+
+    @Override
+    protected void postprocessRemove(E removedObject) {
+        // no need for this operation for DataObjects...
+    }
+
+    @Override
+    protected void updateReverse(List<E> 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..cb527c7 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/PersistentObjectList.java b/cayenne-server/src/main/java/org/apache/cayenne/util/PersistentObjectList.java
index 6b669a2..9f176f0 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/util/PersistentObjectList.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/util/PersistentObjectList.java
@@ -261,7 +261,7 @@ public class PersistentObjectList<E> extends RelationshipFault<E> implements Lis
             // TODO: here we assume that all objects were removed,
             // while removeAll may technically return true and remove only some objects...
             // need a smarter approach
-            postprocessRemove((E)c);
+            postprocessRemove((Collection<? extends E>)c);
             return true;
         }
 
@@ -269,7 +269,7 @@ public class PersistentObjectList<E> extends RelationshipFault<E> implements Lis
     }
 
     @Override
-    public boolean retainAll(Collection c) {
+    public boolean retainAll(Collection<?> c) {
         // TODO: handle object graoh change notifications on object removals...
         return resolvedObjectList().retainAll(c);
     }
@@ -408,7 +408,7 @@ public class PersistentObjectList<E> extends RelationshipFault<E> implements Lis
      * @return whether object should be added to {@link #removedFromUnresolved} during
      *         removal
      */
-    protected boolean shouldAddToRemovedFromUnresolvedList(Object object) {
+    protected boolean shouldAddToRemovedFromUnresolvedList(E object) {
         return true;
     }
 
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/util/PersistentObjectMap.java b/cayenne-server/src/main/java/org/apache/cayenne/util/PersistentObjectMap.java
index b46a5e2..deb9725 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/util/PersistentObjectMap.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/util/PersistentObjectMap.java
@@ -93,7 +93,7 @@ public class PersistentObjectMap extends RelationshipFault implements Map, Value
 
     public Object setValue(Object value) throws CayenneRuntimeException {
         resolvedObjectMap();
-        return setValueDirectly(objectMap);
+        return setValueDirectly(value);
     }
 
     public Object setValueDirectly(Object value) throws CayenneRuntimeException {
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..6ead6be 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
@@ -32,15 +32,15 @@ import org.apache.cayenne.ValueHolder;
 /**
  * @since 3.0
  */
-public class PersistentObjectSet extends RelationshipFault 
-    implements Set, ValueHolder, PersistentObjectCollection {
+public class PersistentObjectSet<E> extends RelationshipFault<E>
+    implements Set<E>, ValueHolder<Object>, PersistentObjectCollection<E> {
 
     // wrapped objects set
-    protected Set objectSet;
+    protected Set<E> objectSet;
 
     // track additions/removals in unresolved...
-    protected Set addedToUnresolved;
-    protected Set removedFromUnresolved;
+    protected Set<E> addedToUnresolved;
+    protected Set<E> removedFromUnresolved;
 
     // exists for the benefit of manual serialization schemes such as the one in Hessian.
     private PersistentObjectSet() {
@@ -53,6 +53,7 @@ public class PersistentObjectSet extends RelationshipFault
     /**
      * Returns whether this list is not yet resolved and requires a fetch.
      */
+    @Override
     public boolean isFault() {
 
         if (objectSet != null) {
@@ -62,7 +63,7 @@ public class PersistentObjectSet extends RelationshipFault
         // object may be in an inconsistent state during construction time
         // synchronize??
         else if (isTransientParent()) {
-            objectSet = new HashSet();
+            objectSet = new HashSet<>();
             return false;
         }
         else {
@@ -73,19 +74,21 @@ public class PersistentObjectSet extends RelationshipFault
     /**
      * Turns itself into a fault, thus forcing a refresh on the next access.
      */
+    @Override
     public void invalidate() {
         setObjectSet(null);
     }
 
+    @Override
     public Object setValueDirectly(Object value) throws CayenneRuntimeException {
-        Object old = this.objectSet;
+        Set<E> old = this.objectSet;
 
         if (value == null || value instanceof Set) {
-            setObjectSet((Set) value);
+            setObjectSet((Set<E>) value);
         } else if (value instanceof Collection) {
             // we can wrap non-set collections on the fly - this is needed for prefetch handling...
             // although it seems to be breaking the contract for 'setValueDirectly' ???
-            setObjectSet(new HashSet((Collection) value));
+            setObjectSet(new HashSet<>((Collection<E>) value));
         } else {
             throw new CayenneRuntimeException("Value must be a list, got: %s", value.getClass().getName());
         }
@@ -93,20 +96,22 @@ public class PersistentObjectSet extends RelationshipFault
         return old;
     }
 
-    public Object getValue() throws CayenneRuntimeException {
+    @Override
+    public Set<E> getValue() throws CayenneRuntimeException {
         return resolvedObjectSet();
     }
 
-    public Object getValueDirectly() throws CayenneRuntimeException {
+    @Override
+    public Set<E> getValueDirectly() throws CayenneRuntimeException {
         return objectSet;
     }
 
     public Object setValue(Object value) throws CayenneRuntimeException {
         resolvedObjectSet();
-        return setValueDirectly(objectSet);
+        return setValueDirectly(value);
     }
 
-    public void setObjectSet(Set objectSet) {
+    public void setObjectSet(Set<E> objectSet) {
         this.objectSet = objectSet;
     }
 
@@ -114,7 +119,8 @@ public class PersistentObjectSet extends RelationshipFault
     // Standard Set Methods.
     // ====================================================
 
-    public boolean add(Object o) {
+    @Override
+    public boolean add(E o) {
         if ((isFault()) ? addLocal(o) : objectSet.add(o)) {
             postprocessAdd(o);
             return true;
@@ -123,7 +129,8 @@ public class PersistentObjectSet extends RelationshipFault
         return false;
     }
 
-    public boolean addAll(Collection c) {
+    @Override
+    public boolean addAll(Collection<? extends E> c) {
         if (resolvedObjectSet().addAll(c)) {
             // TODO: here we assume that all objects were added, while addAll may
             // technically return true and add only some objects... need a smarter
@@ -136,16 +143,19 @@ public class PersistentObjectSet extends RelationshipFault
         return false;
     }
 
+    @Override
     public void clear() {
-        Set resolved = resolvedObjectSet();
+        Set<E> resolved = resolvedObjectSet();
         postprocessRemove(resolved);
         resolved.clear();
     }
 
+    @Override
     public boolean contains(Object o) {
         return resolvedObjectSet().contains(o);
     }
 
+    @Override
     public boolean containsAll(Collection c) {
         return resolvedObjectSet().containsAll(c);
     }
@@ -168,38 +178,46 @@ public class PersistentObjectSet extends RelationshipFault
         return 53 + resolvedObjectSet().hashCode();
     }
 
+    @Override
     public boolean isEmpty() {
         return resolvedObjectSet().isEmpty();
     }
 
-    public Iterator iterator() {
+    @Override
+    public Iterator<E> iterator() {
         return resolvedObjectSet().iterator();
     }
 
+    @SuppressWarnings("unchecked")
+    @Override
     public boolean remove(Object o) {
-        if ((isFault()) ? removeLocal(o) : objectSet.remove(o)) {
-            postprocessRemove(o);
+        E object = (E) o;
+        if ((isFault()) ? removeLocal(object) : objectSet.remove(o)) {
+            postprocessRemove(object);
             return true;
         }
 
         return false;
     }
 
-    public boolean removeAll(Collection c) {
+    @SuppressWarnings("unchecked")
+    @Override
+    public boolean removeAll(Collection<?> c) {
         if (resolvedObjectSet().removeAll(c)) {
             // TODO: here we assume that all objects were removed, while removeAll may
             // technically return true and remove only some objects... need a smarter
             // approach
-            postprocessRemove(c);
+            postprocessRemove((Collection<? extends E>) c);
             return true;
         }
 
         return false;
     }
 
-    public boolean retainAll(Collection c) {
-    	Collection toRemove = new HashSet(resolvedObjectSet().size());
-    	for (Object object : resolvedObjectSet()) {
+    @Override
+    public boolean retainAll(Collection<?> c) {
+    	Collection<E> toRemove = new HashSet<>(resolvedObjectSet().size());
+    	for (E object : resolvedObjectSet()) {
 			if (!c.contains(object)) {
 				toRemove.add(object);
 			}
@@ -212,15 +230,18 @@ public class PersistentObjectSet extends RelationshipFault
         return result;
     }
 
+    @Override
     public int size() {
         return resolvedObjectSet().size();
     }
 
+    @Override
     public Object[] toArray() {
         return resolvedObjectSet().toArray();
     }
 
-    public Object[] toArray(Object[] a) {
+    @Override
+    public <T> T[] toArray(T[] a) {
         return resolvedObjectSet().toArray(a);
     }
 
@@ -232,7 +253,7 @@ public class PersistentObjectSet extends RelationshipFault
     /**
      * Returns internal objects list resolving it if needed.
      */
-    protected Set resolvedObjectSet() {
+    protected Set<E> resolvedObjectSet() {
         if (isFault()) {
 
             synchronized (this) {
@@ -240,8 +261,8 @@ public class PersistentObjectSet extends RelationshipFault
                 // now that we obtained the lock, check
                 // if another thread just resolved the list
                 if (isFault()) {
-                    List localList = resolveFromDB();
-                    this.objectSet = new HashSet(localList);
+                    List<E> localList = resolveFromDB();
+                    this.objectSet = new HashSet<>(localList);
                 }
             }
         }
@@ -255,7 +276,7 @@ public class PersistentObjectSet extends RelationshipFault
     }
     
     @Override
-    protected void mergeLocalChanges(List resolved) {
+    protected void mergeLocalChanges(List<E> resolved) {
 
         // only merge if an object is in an uncommitted state
         // any other state means that our local tracking
@@ -270,7 +291,7 @@ public class PersistentObjectSet extends RelationshipFault
             // do not include transient objects...
             if (addedToUnresolved != null) {
 
-                for (Object next : addedToUnresolved) {
+                for (E next : addedToUnresolved) {
 
                     if (next instanceof Persistent) {
                         Persistent dataObject = (Persistent) next;
@@ -290,14 +311,14 @@ public class PersistentObjectSet extends RelationshipFault
         clearLocalChanges();
     }
 
-    boolean addLocal(Object object) {
+    boolean addLocal(E object) {
 
         if (removedFromUnresolved != null) {
             removedFromUnresolved.remove(object);
         }
 
         if (addedToUnresolved == null) {
-            addedToUnresolved = new HashSet();
+            addedToUnresolved = new HashSet<>();
         }
 
         addedToUnresolved.add(object);
@@ -307,35 +328,37 @@ public class PersistentObjectSet extends RelationshipFault
         return true;
     }
 
-    boolean removeLocal(Object object) {
+    boolean removeLocal(E object) {
         if (addedToUnresolved != null) {
             addedToUnresolved.remove(object);
         }
 
         if (removedFromUnresolved == null) {
-            removedFromUnresolved = new HashSet();
+            removedFromUnresolved = new HashSet<>();
         }
 
-        removedFromUnresolved.add(object);
+        if (shouldAddToRemovedFromUnresolvedSet(object)) {
+            removedFromUnresolved.add(object);
+        }
 
         // this is really meaningless, since we don't know
         // if an object was present in the list
         return true;
     }
 
-    void postprocessAdd(Collection<?> collection) {
-        for (Object next : collection) {
+    protected void postprocessAdd(Collection<? extends E> collection) {
+        for (E next : collection) {
             postprocessAdd(next);
         }
     }
 
-    void postprocessRemove(Collection<?> collection) {
-        for (Object next : collection) {
+    protected void postprocessRemove(Collection<? extends E> collection) {
+        for (E next : collection) {
             postprocessRemove(next);
         }
     }
 
-    void postprocessAdd(Object addedObject) {
+    protected void postprocessAdd(E addedObject) {
 
         // notify ObjectContext
         if (relationshipOwner.getObjectContext() != null) {
@@ -351,7 +374,7 @@ public class PersistentObjectSet extends RelationshipFault
         }
     }
 
-    void postprocessRemove(Object removedObject) {
+    protected void postprocessRemove(E removedObject) {
 
         // notify ObjectContext
         if (relationshipOwner.getObjectContext() != null) {
@@ -367,12 +390,17 @@ public class PersistentObjectSet extends RelationshipFault
         }
     }
 
+    protected boolean shouldAddToRemovedFromUnresolvedSet(E object) {
+        return true;
+    }
+
     @Override
     public String toString() {
         return (objectSet != null) ? objectSet.toString() : "[<unresolved>]";
     }
 
-    public void addDirectly(Object target) {
+    @Override
+    public void addDirectly(E target) {
         if (isFault()) {
             addLocal(target);
         }
@@ -381,7 +409,8 @@ public class PersistentObjectSet extends RelationshipFault
         }
     }
 
-    public void removeDirectly(Object target) {
+    @Override
+    public void removeDirectly(E target) {
         if (isFault()) {
             removeLocal(target);
         }