Spring DataIntegrityViolationException

Spring DataIntegrityViolationException

*1. Visão geral *

Neste artigo, discutiremos o Spring* org.springframework.dao.DataIntegrityViolationException *- essa é uma exceção de dados genérica normalmente lançada pelo mecanismo de conversão de exceção do Spring ao lidar com exceções de persistência de nível inferior. O artigo discutirá as causas mais comuns dessa exceção, juntamente com a solução para cada uma.

Leitura adicional:

https://www..com/spring-data-java-8 [Suporte ao Spring Data Java 8]

Um guia rápido e prático para o suporte ao Java 8 no Spring Data.

https://www..com/spring-data-annotations [Anotações de dados do Spring]

Aprenda sobre as anotações mais importantes que precisamos para lidar com a persistência usando o projeto Spring Data

https://www..com/spring-data-rest-relationships [Trabalhando com relacionamentos no Spring Data REST]

Um guia prático para trabalhar com relacionamentos de entidades no Spring Data REST.

===* 2. DataIntegrityViolationException e tradução de exceção de primavera *

O mecanismo de conversão de exceção do Spring pode ser aplicado de forma transparente a todos os beans anotados com _ @ Repository_ - definindo um bean de pós-processador de conversão de exceção no Bean:

<bean id="persistenceExceptionTranslationPostProcessor"
   class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/>

Ou em Java:

@Configuration
public class PersistenceHibernateConfig{
   @Bean
   public PersistenceExceptionTranslationPostProcessor exceptionTranslation(){
      return new PersistenceExceptionTranslationPostProcessor();
   }
}

O mecanismo de conversão de exceção também é ativado por padrão no modelo de persistência mais antigo disponível no Spring - o HibernateTemplate, JpaTemplate, etc.

===* 3. Onde é DataIntegrityViolationException Thrown *

=====* 3.1 DataIntegrityViolationException com Hibernate *

Quando o Spring é configurado com o Hibernate, a exception é lançada na camada de conversão de exceção fornecida pelo Spring - SessionFactoryUtils - convertHibernateAccessException.

Há três exceções possíveis do Hibernate que podem causar o lançamento de DataIntegrityViolationException:

  • org.hibernate.exception.ConstraintViolationException

  • org.hibernate.PropertyValueException *org.hibernate.exception.DataException

=====* 3.2 DataIntegrityViolationException With JPA *

Quando o Spring é configurado com o JPA como seu provedor de persistência, o DataIntegrityViolationException é lançado, semelhante ao Hibernate, na camada de conversão de exceção - ou seja, em EntityManagerFactoryUtils - convertJpaAccessExceptionIfPossible.

Há uma única exceção JPA que pode acionar uma DataIntegrityViolationException a ser lançada - a javax.persistence.EntityExistsException.

===* 4. Causa: org.hibernate.exception.ConstraintViolationException *

Essa é de longe a causa mais comum de DataIntegrityViolationException sendo lançada - o Hibernate ConstraintViolationException indica que a operação violou uma restrição de integridade do banco de dados.

Considere o seguinte exemplo - para mapeamento Um para Um por meio de uma coluna de chave estrangeira explícita entre as entidades Parent e Child - as seguintes operações devem falhar:

@Test(expected = DataIntegrityViolationException.class)
public void whenChildIsDeletedWhileParentStillHasForeignKeyToIt_thenDataException() {
   Child childEntity = new Child();
   childService.create(childEntity);

   Parent parentEntity = new Parent(childEntity);
   service.create(parentEntity);

   childService.delete(childEntity);
}

A entidade Parent possui uma chave estrangeira para a entidade Child - portanto, excluir o filho quebraria a restrição de chave estrangeira no pai - o que resulta em uma ConstraintViolationException - envolvida pelo Spring no DataIntegrityViolationException:

org.springframework.dao.DataIntegrityViolationException:
could not execute statement; SQL [n/a]; constraint [null];
nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
    at o.s.orm.h.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:138)
Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement

Para resolver isso, o Parent deve ser excluído primeiro:

@Test
public void whenChildIsDeletedAfterTheParent_thenNoExceptions() {
   Child childEntity = new Child();
   childService.create(childEntity);

   Parent parentEntity = new Parent(childEntity);
   service.create(parentEntity);

   service.delete(parentEntity);
   childService.delete(childEntity);
}

===* 5. Causa: org.hibernate.PropertyValueException *

Essa é uma das causas mais comuns de DataIntegrityViolationException - no Hibernate, isso se resumirá a uma entidade persistente com um problema. A entidade possui uma propriedade nula definida com uma restrição* not-null , ou uma associação da entidade pode fazer referência a uma *instância transitória não salva .

Por exemplo, a entidade a seguir possui uma propriedade name não nula -

@Entity
public class Foo {
   ...

   @Column(nullable = false)
   private String name;

   ...
}

Se o teste a seguir tentar manter a entidade com um valor nulo para name:

@Test(expected = DataIntegrityViolationException.class)
public void whenInvalidEntityIsCreated_thenDataException() {
   fooService.create(new Foo());
}

Uma restrição de integração ao banco de dados é violada e, portanto, a DataIntegrityViolationException é lançada:

org.springframework.dao.DataIntegrityViolationException:
not-null property references a null or transient value:
org..spring.persistence.model.Foo.name;
nested exception is org.hibernate.PropertyValueException:
not-null property references a null or transient value:
org..spring.persistence.model.Foo.name
    at o.s.orm.h.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:160)
...
Caused by: org.hibernate.PropertyValueException:
not-null property references a null or transient value:
org..spring.persistence.model.Foo.name
    at o.h.e.i.Nullability.checkNullability(Nullability.java:103)
...

*6. Causa: org.hibernate.exception.DataException *

Um DataException do Hibernate indica uma instrução SQL inválida - algo estava errado com a instrução ou os dados, nesse contexto específico. Por exemplo, usando a entidade Foo de antes, o seguinte acionaria essa exceção:

@Test(expected = DataIntegrityViolationException.class)
public final void whenEntityWithLongNameIsCreated_thenDataException() {
   service.create(new Foo(randomAlphabetic(2048)));
}

A exceção real para persistir o objeto com um valor longo de name é:

org.springframework.dao.DataIntegrityViolationException:
could not execute statement; SQL [n/a];
nested exception is org.hibernate.exception.DataException: could not execute statement
   at o.s.o.h.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:143)
...
Caused by: org.hibernate.exception.DataException: could not execute statement
    at o.h.e.i.SQLExceptionTypeDelegate.convert(SQLExceptionTypeDelegate.java:71)

Neste exemplo em particular, a solução é especificar o tamanho máximo do nome:

@Column(nullable = false, length = 4096)

===* 7. Causa: javax.persistence.EntityExistsException *

Da mesma forma que no Hibernate, a exceção JPA EntityExistsException também será agrupada pela tradução de exceção da primavera em um DataIntegrityViolationException. A única diferença é que a própria JPA já é de alto nível, o que torna essa exceção da JPA a única causa potencial de violações da integridade dos dados.

===* 8. Potencialmente DataIntegrityViolationException *

Em alguns casos em que a DataIntegrityViolationException pode ser esperada, outra exceção pode ser lançada - um desses casos é se um validador JSR-303, como hibernate-validator 4 ou 5, existir no caminho de classe.

Nesse caso, se a entidade a seguir persistir com um valor nulo para name, ela* não falhará mais com uma violação de integridade de dados *acionada pela camada de persistência:

@Entity
public class Foo {
    ...
    @Column(nullable = false)
    @NotNull
    private String name;

    ...
}

Isso ocorre porque a execução não chegará à camada de persistência - ela falhará antes disso com um javax.validation.ConstraintViolationException:

javax.validation.ConstraintViolationException:
Validation failed for classes [org..spring.persistence.model.Foo]
during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[ ConstraintViolationImpl{
    interpolatedMessage='may not be null', propertyPath=name,
    rootBeanClass=class org..spring.persistence.model.Foo,
    messageTemplate='{javax.validation.constraints.NotNull.message}'}
]
    at o.h.c.b.BeanValidationEventListener.validate(BeanValidationEventListener.java:159)
    at o.h.c.b.BeanValidationEventListener.onPreInsert(BeanValidationEventListener.java:94)

===* 9. Conclusões *

No final deste artigo, devemos ter um mapa claro para navegar pela variedade de causas e problemas que podem levar a uma DataIntegrityViolationException no Spring, além de uma boa compreensão de como corrigir todos esses problemas.

A implementação de todos os exemplos de exceções pode ser encontrada em the github project - este é um projeto baseado em Eclipse, portanto, deve ser fácil importar e executar como está.