CF9 ORM Quick Tip - Removing all items from a collection defined as a structure
Posted At : January 6, 2010 11:20 AM | Posted By : Bob Silverberg
Related Categories: ColdFusion, CF ORM Integration
I wrote a ColdFusion ORM Quick Tip awhile back on removing all items from a collection, which a number of folks were kind enough to comment on and provide even better solutions. I was recently faced with the same problem, but this time I wanted to remove all items from a collection that was defined as a structure (last time it was an array). My initial code generated an error, so I had to get creative. I'm putting this out there to see if anyone has any better ideas for doing this.
For this example, let's assume we have a User object and a Department object. The User object is defined like this:
2 property name="UserId" fieldtype="id" generator="native";
3 property name="UserName";
4}
The Department has a collection of Users, like so:
2 property name="DeptId" fieldtype="id" generator="native";
3 property name="Name";
4 property name="Users" fieldtype="one-to-many" type="struct"
5 cfc="User" fkcolumn="DeptId" singularname="User"
6 structkeycolumn="UserName" structkeytype="string";
7}
Let's also say that Department 1 currently has 3 Users assigned to it, call them User1, User2 and User3. Now, we want to write some code that will empty the Department's collection of Users. A first attempt might look something like:
2for (user in Users) {
3 Department.removeUser(user);
4}
Unfortunately, this code throws a java.util.ConcurrentModificationException error. A bit of Googling led me to believe that this is an issue with the way Hibernate is manipulating the collection, which is a Java HashMap. It sounds like when you're iterating over a HashMap you must ask the iterator to remove an item, rather than removing it yourself. This sounds like a bug in Hibernate, but I'd be very surprised if there were such an obvious bug in Hibernate, so maybe it has something to do with the way that Hibernate has been integrated into CF. Either way, it simply doesn't work, so I needed to come up with another way of removing all of the items from the collection.
As with my last post on the topic, the simplest way to achieve this is to call the setter for the collection and pass in an empty struct:
The way that I ended up doing it was to use a while loop, checking whether the collection has any remaining items, and to remove the first item in the collection inside the loop. It looks like this:
2while (Department.hasUser()) {
3 user = listFirst(structKeyList(Users));
4 Department.removeUser(user);
5}
As discussed in the last post, I prefer the latter approach. The first seems a bit hacky to me, although both of them require that you program to the implementation of the collection (you are writing code expecting the collection to be a structure). Also as discussed previously you could make that a bit less painful by moving the code into the actual object itself. For example, in the Department object, add the following method:
2 var Users = this.getUsers();
3 while (this.hasUser()) {
4 this.removeUser(listFirst(structKeyList(Users)));
5 }
6}
Now you can call removeAllUsers() from outside of the object (e.g., from a Service) and nothing other than the object will have to know that your collection has been implemented as a structure.
What other methods can people think of for achieving this?
This is not a bug at all, and certainly not with Hibernate. It is well-known and well-documented behavior applying to all Java (and .NET) collections.
@Justice:I guess I either misread something or what I was reading was incorrect. I don't have any experience writing Java code, but the posts I found when Googling the issue seemed to suggest that it was fine to remove an item from a collection when iterating over it, as long as you asked the Iterator itself to remove it. Here's an example from Stack Overflow: http://stackoverflow.com/questions/602636/concurre.... Note the answer from Robin about halfway down.
CF7 allows this to happen without a problem. CF9 blows up with the java.util.ConcurrentModificationException error.
I don't know Java hardly at all, but in older versions of CF, this was an allowed action.