2010-03-22 9 views
6

Sono ancora in fase di apprendimento di hibernate/hql e ho una domanda che è metà delle migliori pratiche di controllo/metà sanità mentale.hibernate column unqueness question

Diciamo che ho una classe 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 
} 

voglio far rispettare che ogni istanza di un che viene salvato ha un nome univoco (da qui l'annotazione @Column), ma voglio anche essere in grado per gestire il caso in cui è già presente un'istanza A salvata con quel nome. Vedo due modi per farlo:

1) Posso rilevare org.hibernate.exception.ConstraintViolationException che potrebbe essere generata durante la chiamata session.SaveOrUpdate() e provare a gestirla.

2) Posso interrogare per istanze esistenti di A che hanno già quel nome nel DAO prima di chiamare session.saveOrUpdate().

In questo momento sono inclinato verso l'approccio 2, perché nell'approccio 1 non so come determinare a livello di codice quale vincolo è stato violato (ci sono un paio di altri membri univoci in A). In questo momento il mio DAO.save codice() appare grosso modo così:

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

Sto andando su questo nel modo giusto? Può ibernare dirmi programmaticamente (cioè non come una stringa di errore) quale valore sta violando il vincolo di unicità? Separando la query dal commit, sto invitando errori di sicurezza del thread o sono sicuro? Come si fa di solito questo?

Grazie!

risposta

3

Penso che il tuo secondo approccio sia il migliore.

Per essere in grado di intercettare l'eccezione ConstraintViolation con la certezza che questo particolare oggetto l'ha provocata, è necessario svuotare la sessione immediatamente dopo la chiamata per salvareOrUpdate. Questo potrebbe introdurre problemi di prestazioni se è necessario inserire un numero di questi oggetti alla volta.

Anche se si testerebbe se il nome esiste già nella tabella in ogni azione di salvataggio, questo sarà comunque più veloce dello svuotamento dopo ogni inserimento. (Puoi sempre fare un benchmark per confermare.)

Ciò consente inoltre di strutturare il codice in modo da poter chiamare un "validatore" da un livello diverso. Ad esempio, se questa proprietà univoca è l'email di un nuovo utente, dall'interfaccia Web è possibile chiamare il metodo di convalida per determinare se l'indirizzo di posta elettronica è accettabile. Se hai optato per la prima opzione, sapresti solo se l'email è accettabile dopo aver provato ad inserirla.

+0

Avrò problemi di sicurezza del thread con il secondo approccio? Per quanto ho capito, la parte "Isolation" dei principi ACID DB dovrebbe aiutarmi qui, ma non sono sicuro di quanto hibernate/hsqldb sia compatibile con questo. – Seth

+0

Non capisco perché l'isolamento sia importante per questo caso. Verificare se un nome esiste avverrebbe nella stessa transazione e non altera lo stato dei dati nel database. – Rachel

1

Approccio 1 sarebbe stato ok se:

  • C'è solo un vincolo nell'entità.
  • C'è solo un oggetto sporco nella sessione.

Ricordare che l'oggetto non può essere salvato finché non viene chiamato flush() o la transazione è stata eseguita.

Per una migliore segnalazione degli errori avrei:

  1. Usa per ogni violazione di vincolo approccio in due, in modo da poter dare un errore specifico per ciascuno di loro ..
  2. implementare un intercettore che in caso di un vincolo l'eccezione riprova la transazione (un numero massimo di volte) in modo che la violazione non possa essere rilevata in uno dei test. Questo è necessario solo in base al livello di isolamento della transazione.