A: 

Session.merge() should do it:

obj = session.merge(obj);
I think merge() hits the database, which I was hoping to avoid. Note that session.lock(detached_object, LockMode.NONE) does not hit the database at all (if you don't change it after re-attaching it)
Rob
A: 

I'm maintaining a cache of objects across hibernate sessions

Not a direct answer but why don't you use Hibernate L2 cache for this instead of reinventing the wheel?

Pascal Thivent
I think the L2 cache would not help here because I don't know the id of the persistent entity -- I look up the entities in the cache using .equals(), so it effectively replaces a new entity with an equivalent entity that already exists. It's sort of a flyweight pattern for the database. Unfortunately, this is necessary for my application because there are tons of immutable data that can be reused when creating new entities.
Rob
@Rob Ok, I see... except for the part "I don't know the id". Are these objects kind of referential data (like, say, countries, currencies, etc)?
Pascal Thivent
Yes, they are value objects whose identity is not important. They are entities only so that their references can be shared. This allows me to reuse the same 64kb blob from millions of others. They are not anything standard like countries, currencies etc, but it's the same idea.
Rob
@Rob: In your case you have non-mutable (read-only) entities, right? Anyway, nothing prevents you from having synthetic `Entity.getId() { return this; }` so the entity will act as ID for itself. It should implement `Serializable`, `equals()` and `hashCode()`. However, I think, all persistent entities should have a key (currency code, country code). Maybe you can have your custom entity persister for these entities, then you can control what should be the ID for them. Maybe using Session interceptor can help.
dma_k