"My bright idea is to create a Registry on the client that keeps track of client side objects. When an object's readResolve method is called, it can look in the registry and if it's already there, return the object from the registry instead."
This is an ancient pattern. Its used by nearly every system that does Object-DataStore mapping. I suppose the big question I would have is "why the 3rd tier"? Not logical tier - you need that - but physical tier.
My suggestion to you is to not cache the domain objects in the server - move them to the client as an assembly - monkey with them - give them back to the server - execute server side processing on them, put them back in the db. At all times you need to maintain cached copies of the original fetched values to use for optimistic locking to maintain data integrity. So you have a class, unique ID (primary key attributes generally), the fetched values, and your working values. When you are going to resave the data back into the db, you use all the original values as part of your where clause, check the row count on your update, and if its not 1, toss an exception and roll the whole mess back and start over.
You might take a look at [link|http://www.solarmetric.com|http://www.solarmetric.com] - they have a JDO package that looks pretty decent. It might help you out. Something free might be Cayenne at [link|http://www.objectstyle.org/cayenne/userguide/design/|http://www.objectsty...userguide/design/]. For this, you might need to make your middle layer look like a database to your client.