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!
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.
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:
- Use approach two for every constraint violation, so I can give an specific error for each of them..
- 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.
精彩评论