Transaction management in EntityHome subclass
mbertelsen Jul 6, 2007 12:20 PMI have an entity with a two-field unique constraint. Due to not being able to write a validator that validates two fields of an entity at once (or am I wrong here?), I ended up subclassing the EntityHome in order to run my object-level validation before the superclass update() and persist() get called. However, because the instance entity is managed, and EntityHome uses the seam-managed persistence context and transaction management, by the time my code for validation runs, the managed entity has already been updated by JSF. So when I try to run a select query to determine if the two fields together are unique, hibernate is automatically flushing the write to the entity, which throws the EntityExistsException i'm trying to avoid.
I ended up with the following solution, and would like to know if there's a better way:
@Name("databaseServerHome")
public class DatabaseServerHome extends EntityHome<DatabaseServer> {
@In
EntityManager entityManager;
@In FacesMessages facesMessages;
@Override
public EntityManager getEntityManager() {
return entityManager;
}
@Override
@Factory("databaseServer")
public DatabaseServer getInstance() {
return super.getInstance();
}
// ... (removed for clarity)
@Override
@Rollback(ifOutcome="invalid")
public String update() {
// set flush mode to COMMIT so that validateUnique can run a query to
// check uniqueness without triggering a flush
// This is required because we have a managed entity with dirty changes
// (already applied by JSF) and now need to validate them after the
// changes have been made
getEntityManager().setFlushMode(FlushModeType.COMMIT);
if (validateUnique()) {
return super.update();
} else {
return "invalid";
}
}
protected boolean validateUnique() {
DatabaseServer ds = getInstance();
String existenceQuery = "select ds from DatabaseServer ds where ds.serverName = :serverName and ds.port = :port";
try {
DatabaseServer ds1 = (DatabaseServer) entityManager.createQuery(existenceQuery)
.setParameter("serverName", ds.getServerName()).setParameter("port", ds.getPort()).getSingleResult();
if (ds1 != null && !(ds1.getId().equals(ds.getId()))) {
facesMessages.add(FacesMessage.SEVERITY_ERROR,"You cannot have two database servers with the same Name and Port");
return false;
} else {
return true;
}
} catch (NoResultException nre) {
return true;
}
}
}
One last minor annoyance is that Seam is adding a FacesMessage that reads "Transaction Failed" when it rolls back - how do I alter this behavior?
Thanks!