Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

hibernate column uniqueness question

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!

like image 631
Seth Avatar asked Mar 22 '10 21:03

Seth


People also ask

How do I make unique columns in JPA?

A unique constraint can be either a column constraint or a table constraint. At the table level, we can define unique constraints across multiple columns. JPA allows us to define unique constraints in our code using @Column(unique=true) and @UniqueConstraint.

What is @column unique true?

unique in @Column is used only if you let your JPA provider create the database for you - it will create the unique constraint on the specified column. But if you already have the database, or you alter it once created, then unique doesn't have any effect.

What is unique true in hibernate?

As said before, @Column(unique = true) is a shortcut to UniqueConstraint when it is only a single field. From the example you gave, there is a huge difference between both. @Column(unique = true) @ManyToOne(optional = false, fetch = FetchType.

Which of the following annotations can be used to specify unique Rdbms constraints in entity classes?

@UniqueConstraint annotation in Java.


2 Answers

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.

like image 127
Rachel Avatar answered Sep 19 '22 10:09

Rachel


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.
like image 39
Andres Rodriguez Avatar answered Sep 20 '22 10:09

Andres Rodriguez