Custom converter, DI, PC, NonUniqueObjectException
ahaumer Jun 7, 2010 5:56 AMHi!
It is a sad fact that a custom converter can not make use of DI: Seam Interceptors must be bypassed,
EJB DI does not work, either. So there are only two ways to get a handle to a session bean like a DAO
SLSB inside a converter: do a JNDI lookup or use Seam Component.getInstance().
In the past couple of days I have played with custom converters in a Seam test application and found
the following problem with this scenario: A DAO retrieved by JNDI lookup or Component.getInstance()
has a different persistence context than the Seam action bean itself. So any entity converted by the converter
(e.g. an entity selected in a h:selectOneMenu) will end as detached object in the action bean.
Now when flushing the PC I get a NonUniqueObjectException if the following is true:
- The detached entity is associated with some
parent
entity, making use oftransitive persistence
as described inJava Persistence with Hibernate
, chapter 12.1 - In the action bean there is already a copy of the detached entity attached to the PC
Sadly, this is a common scenario. I often have lists of entities to be selected from in the UI
stored in the action bean, and these entities fulfill condition (2).
Think of a person manager
which allows adding one out of several roles to a person. The person
is an entity, so are the available roles. Both person and available roles are stored inside the
action bean. The UI allows the user to select one role from the list, which then is associated
with the person.
I tried various mapping options (cascade) in combination with the Hibernate saveOrUpdate() and merge()
operations, but I always got the same
problem. I tested this with JBoss AS 5.1 and Seam 2.1.2. I also have to mention that I can't use
seam managed persistence context (SMPC), as all our DAO beans also have to be usable in a non-seam
scenario (like a remote client or other clients not running inside a seam context). So I have to
rely on container managed PC, PC propagation rules and MANUAL flush mode (oh well, all the funny
things in any JEE5 application)
I have found two workarounds to this problem:
- Manually re-attach the entity delivered by the converter to the PC inside the action bean
- Manually create the custom converter and provide the DAO
Workaround (2) works like this:
Write a constructor for the converter which takes the DAO as argument
and remembers it for later use:
public RoleConverter (RoleDAO dao) { roleDAO = dao; }
Provide a factory method in the Seam action bean for the converter. In
this factory method you create the converter with the constructor mentioned
above, providing the very same DAO which is used in the action bean itself:
public RoleConverter getRoleConverter() { return new RoleConverter(roleDAO); }
Use the converter in the XHTML file like this:
<h:selectOneMenu value="#{personManager.selectedRole}" converter="#{personManager.roleConverter}" > <f:selectItems value="#{roleManager.allRoles}" /> </h:selectOneMenu>
That way, entities provided by the converter are attached to the same PC as the action bean is using
and Hibernate is happy.
I do not really like both workarounds. Both compel the designer of the UI and the backing bean to write
special code just to make the persistence layer happy. As my design always strives to separate those
layers as much as possible, I consider this a bad thing.
How do others solve this problem?
What is considered best practice
in this area, given the situation I described above?
Thank you!