2012-03-23 11 views
12

Estamos trabajando en un proyecto web Java basado en JPA 2, Hibernate, Spring 3 y JSF 2 en Tomcat 7. Estamos utilizando Oracle 11g como base de datos.Mejores prácticas de propagación Excepciones únicas de infracción a la IU

Actualmente estamos llevando a cabo un debate sobre los enfoques para poblar las infracciones de restricciones de la base de datos como mensajes fáciles de usar para la interfaz de usuario. Más o menos vemos dos formas, ambas no son realmente satisfactorias. ¿Alguien podría dar algunos consejos?

Enfoque 1 - Validar mediante programación y lanzar una excepción específica

En CountryService.java cada restricción único será validado y una excepción correspondiente se lanza. Las excepciones se manejan individualmente en un bean de respaldo.

Ventaja: Fácil de entender y mantener. Mensajes de usuario específicos posibles.

Desventaja: Una gran cantidad de código solo para tener buenos mensajes. Básicamente todas las Restricciones de DB se escriben de nuevo en la aplicación. Muchas consultas: carga de db innecesaria.

@Service("countryService") 
public class CountryServiceImpl implements CountryService { 

    @Inject 
    private CountryRepository countryRepository; 

    @Override 
    public Country saveCountry(Country country) throws NameUniqueViolationException, IsoCodeUniqueViolationException, UrlUniqueViolationException { 
     if (!isUniqueNameInDatabase(country)) { 
      throw new NameUniqueViolationException(); 
     } 
     if (!isUniqueUrl(country)) { 
      throw new UrlUniqueViolationException(); 
     } 
     if (!isUniqueIsoCodeInDatabase(country)) { 
      throw new IsoCodeUniqueViolationException(); 
     } 
     return countryRepository.save(country); 
    } 
} 

En bean de respaldo de la vista que manejan las excepciones:

@Component 
@Scope(value = "view") 
public class CountryBean { 

    private Country country; 

    @Inject 
    private CountryService countryService; 

    public void saveCountryAction() { 
     try { 
      countryService.saveCountry(country); 
     } catch (NameUniqueViolationException e) { 
      FacesContext.getCurrentInstance().addMessage("name", new FacesMessage("A country with the same name already exists.")); 
     } catch (IsoCodeUniqueViolationException e) { 
      FacesContext.getCurrentInstance().addMessage("isocode", new FacesMessage("A country with the same isocode already exists.")); 
     } catch (UrlUniqueViolationException e) { 
      FacesContext.getCurrentInstance().addMessage("url", new FacesMessage("A country with the same url already exists.")); 
     } catch (DataIntegrityViolationException e) { 
      // update: in case of concurrent modfications. should not happen often 
      FacesContext.getCurrentInstance().addMessage(null, new FacesMessage("The country could not be saved.")); 
     } 
    } 
} 

Enfoque 2 - Deje que la base de datos de detección de violaciónes restricción

Advantage: No Código de placa de la caldera. No hay consultas innecesarias a db. Sin duplicación de la lógica de restricción de datos.

Desventaja: Dependencias de los nombres de restricción en DB, por lo que no es posible generar un esquema a través de hibernación. Mecanismo necesario para vincular los mensajes a los componentes de entrada (por ejemplo, para resaltar).

public class DataIntegrityViolationExceptionsAdvice { 
    public void afterThrowing(DataIntegrityViolationException ex) throws DataIntegrityViolationException { 

     // extract the affected database constraint name: 
     String constraintName = null; 
     if ((ex.getCause() != null) && (ex.getCause() instanceof ConstraintViolationException)) { 
      constraintName = ((ConstraintViolationException) ex.getCause()).getConstraintName(); 
     } 

     // create a detailed message from the constraint name if possible 
     String message = ConstraintMsgKeyMappingResolver.map(constraintName); 
     if (message != null) { 
      throw new DetailedConstraintViolationException(message, ex); 
     } 
     throw ex; 
    } 
} 
+0

Aún confía en la base de datos para detectar violaciones de restricciones en el Método 1, en caso de que un usuario guarde un país duplicado después de la verificación única pero antes de guardar un segundo usuario. – ken

+0

somos conscientes del problema concurrente aquí. no es obligatorio para nuestros casos de uso.es suficiente, si en el 90% de los casos el mensaje es específico. si en casos excepcionales, un mensaje más genérico es desencadenado por db no importa. – fischermatte

+0

actualicé el enfoque 1 – fischermatte

Respuesta

9

¡El enfoque 1 no funcionará en un escenario concurrente! - Siempre hay un cambio en que alguien inserte un nuevo registro en la base de datos después de que lo haya marcado, pero antes de agregar el registro de la base de datos. (excepto que utiliza el nivel de aislamiento serializable, pero esto es muy poco probable)

Por lo tanto, debe manejar las excepciones de violación de restricciones DB. Pero recomiendo atrapar la excepción de la base de datos que indica la violación única y arrojar un significado más completo como sugirió en el Método 1.

+0

como le escribí a Ken, el problema concurrente no importa. en esos casos excepcionales, se mostrará un mensaje más genérico (se activará la violación desencadenada por db). enfoque actualizado 1. – fischermatte

+0

De todos modos, esto no cambiará mi enfoque sugerido. – Ralph

+0

Lo que no me gusta es que normalmente durante el desarrollo hibernate generará los nombres de restricciones. Con el enfoque 2 necesitamos mantener los nombres por separado a través de scripts adicionales. – fischermatte

8

Esto también puede ser una opción, y puede ser menos costoso porque solo hace los controles para un detalle excepción si no se puede guardar absoluta:

try { 
    return countryRepository.save(country); 
} 
catch (DataIntegrityViolationException ex) { 
    if (!isUniqueNameInDatabase(country)) { 
     throw new NameUniqueViolationException(); 
    } 
    if (!isUniqueUrl(country)) { 
     throw new UrlUniqueViolationException(); 
    } 
    if (!isUniqueIsoCodeInDatabase(country)) { 
     throw new IsoCodeUniqueViolationException(); 
    } 
    throw ex; 
} 
+2

Thx, definitivamente mejor que el enfoque 1! El único inconveniente que veo es que manualmente debes enjuagar después de llamar a 'countryRepository.save (country)'. – fischermatte

0

Para evitar repetitivo trato DataIntegrityViolationException en ExceptionInfoHandler, la búsqueda de DB limitaciones apariciones en el mensaje de causa raíz y la convierten en mensajes i18n a través de un mapa. Vea el código aquí: https://stackoverflow.com/a/42422568/548473