views:

340

answers:

2

I'm still in the process of learning hibernate/hql and I have a question that's half best practices question/half sanity check.

Let's say I have a class A:

@Entity
public class A
{
    @Id @GeneratedValue(strategy=GenerationType.AUTO)
    private Long id;

    @Column(unique=true)
    private String name = "";

    //getters, setters, etc. omitted for brevity
}

I want to enforce that every instance of A that gets saved has a unique name (hence the @Column annotation), but I also want to be able to handle the case where there's already an A instance saved that has that name. I see two ways of doing this:

1) I can catch the org.hibernate.exception.ConstraintViolationException that could be thrown during the session.saveOrUpdate() call and try to handle it.

2) I can query for existing instances of A that already have that name in the DAO before calling session.saveOrUpdate().

Right now I'm leaning towards approach 2, because in approach 1 I don't know how to programmatically figure out which constraint was violated (there are a couple of other unique members in A). Right now my DAO.save() code looks roughly like this:

public void save(A a) throws DataAccessException, NonUniqueNameException
{
    Session session = sessionFactory.getCurrentSession();

    try
    {
        session.beginTransaction();

        Query query = null;

        //if id isn't null, make sure we don't count this object as a duplicate
        if(obj.getId() == null)
        {
            query = session.createQuery("select count(a) from A a where a.name = :name").setParameter("name", obj.getName());
        }
        else
        {
            query = session.createQuery("select count(a) from A a where a.name = :name " + 
                "and a.id != :id").setParameter("name", obj.getName()).setParameter("name", obj.getName());
        }

        Long numNameDuplicates = (Long)query.uniqueResult();
        if(numNameDuplicates > 0)
            throw new NonUniqueNameException();

        session.saveOrUpdate(a);
        session.getTransaction().commit();
    }
    catch(RuntimeException e)
    {
            session.getTransaction().rollback();
            throw new DataAccessException(e); //my own class
    }
}

Am I going about this in the right way? Can hibernate tell me programmatically (i.e. not as an error string) which value is violating the uniqueness constraint? By separating the query from the commit, am I inviting thread-safety errors, or am I safe? How is this usually done?

Thanks!

+1  A: 

Approach 1 would be ok if:

  • There is only one constraint in the entity.
  • There is only one dirty object in the session.

Remember that the object may not be saved until flush() is called or the transaction commited.

For best error reporting I would:

  1. Use approach two for every constraint violation, so I can give an specific error for each of them..
  2. Implement an interceptor that in case of an constraint exception retries the transaction (a max number of times) so the violation can't be caught in one of the tests. This is only needed depending on the transaction isolation level.
Andres Rodriguez
+1  A: 

I think that your second approach is best.

To be able to catch the ConstraintViolation exception with any certainty that this particular object caused it, you would need to flush the session immediately after the call to saveOrUpdate. This could introduce performance problems if you need to insert a number of these objects at a time.

Even though you would be testing if the name already exists in the table on every save action, this would still be faster than flushing after every insert. (You could always benchmark to confirm.)

This also allows you to structure your code in such a way that you could call a 'validator' from a different layer. For example, if this unique property is the email of a new user, from the web interface you can call the validation method to determine if the email address is acceptable. If you went with the first option, you would only know if the email was acceptable after trying to insert it.

Rachel
Will I have any thread safety issues with the second approach? As far as I understand, the "Isolation" part of the ACID DB principles is supposed to help me here, but I'm not sure how compliant hibernate/hsqldb is with that.
Seth
I don't understand why isolation is important for this case. Testing if a name exists would happen in the same transaction and does not alter the state of the data in the database.
Rachel