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-java-8 [Leia mais] →
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-annotations [Leia mais] →
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.
https://www..com/spring-data-rest-relationships [Leia mais] →
===* 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á.