2010-03-22 8 views
6

Todavía estoy en el proceso de aprender hibernate/hql y tengo una pregunta que es mitad de prácticas recomendadas/comprobación de cordura media.hibernate column uniqueness question

Digamos que tengo una clase 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 
} 

Quiero hacer cumplir que cada caso de A que es salvado tiene un nombre único (de ahí la anotación @Column), pero también quiero ser capaz para manejar el caso donde ya hay una instancia A guardada que tiene ese nombre. Veo dos formas de hacer esto:

1) Puedo capturar la org.hibernate.exception.ConstraintViolationException que podría lanzarse durante la llamada a session.saveOrUpdate() e intentar manejarlo.

2) Puedo consultar las instancias existentes de A que ya tienen ese nombre en el DAO antes de llamar a session.saveOrUpdate().

Ahora me estoy inclinando por el enfoque 2, porque en el enfoque 1 no sé cómo averiguar programáticamente qué restricción se violó (hay un par de otros miembros únicos en A). Ahora mismo mi DAO.save código() se ve más o menos así:

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 
    } 
} 

¿Voy sobre esto en el camino correcto? ¿Puede Hibernate decirme programáticamente (es decir, no como una cadena de error) qué valor está violando la restricción de exclusividad? Al separar la consulta de la confirmación, ¿estoy invitando errores de seguridad de subprocesos o estoy a salvo? ¿Cómo se hace esto usualmente?

Gracias!

Respuesta

3

Creo que su segundo enfoque es el mejor.

Para poder detectar la excepción ConstraintViolation con la certeza de que este objeto en particular lo causó, deberá enjuagar la sesión inmediatamente después de la llamada a saveOrUpdate. Esto podría presentar problemas de rendimiento si necesita insertar varios de estos objetos a la vez.

Aunque estaría probando si el nombre ya existe en la tabla en cada acción de guardado, esto sería más rápido que el enjuague después de cada inserción. (Siempre puede comparar para confirmar.)

Esto también le permite estructurar su código de tal manera que podría llamar a un 'validador' desde una capa diferente. Por ejemplo, si esta propiedad única es el correo electrónico de un nuevo usuario, desde la interfaz web puede llamar al método de validación para determinar si la dirección de correo electrónico es aceptable. Si fue con la primera opción, solo sabría si el correo electrónico era aceptable después de intentar insertarlo.

+0

¿Tendré algún problema de seguridad con el segundo enfoque? Por lo que yo entiendo, la parte de "Aislamiento" de los principios de ACID DB se supone que me ayuda aquí, pero no estoy seguro de cuán compatible hibernate/hsqldb con eso. – Seth

+0

No entiendo por qué el aislamiento es importante para este caso. Probar si un nombre existe ocurriría en la misma transacción y no altera el estado de los datos en la base de datos. – Rachel

1

Enfoque 1 sería aceptable si:

  • Sólo hay una limitación en la entidad.
  • Solo hay un objeto sucio en la sesión.

Recuerde que el objeto no se puede guardar hasta que se llame al flush() o se haya comprometido la transacción.

Para un mejor reporte de error sería I:

  1. Uso enfoque de dos por cada violación de restricción, por lo que puedo dar un error específico para cada uno de ellos ..
  2. Implementar un interceptor que en caso de una restricción la excepción reintenta la transacción (un número máximo de veces) por lo que no se puede atrapar la infracción en una de las pruebas. Esto solo es necesario según el nivel de aislamiento de la transacción.