7
votes

I have next spring rest controller for handle my exception:

@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public @ResponseBody
ExceptionResponseDTO hiberAnnotationExceptionHandler(ConstraintViolationException exception) {
    return  new ExceptionResponseDTO("Entity is null or we are out of range!", exception.getMessage(), ExceptionMapper.INSTANCE.exceptionToExceptionDTO(exception));
}

My entity class:

@Entity
public class Device {

    @Id
    @Column(name="name", nullable = false)
    private String Name;

    @Column(name = "send_date")
    @NotNull
    private Date sendDate;
}

I try to emulate ConstraintViolationException, so in my controller I use next code:

Device d = new Device();
d.setName("sdsdsd");
d.setSendDate(null);
deviceRepository.save(d);

And as result I receive next exception:

[dispatcherServlet]:? - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Error while committing the transaction] with root cause javax.validation.ConstraintViolationException: Validation failed for classes [com.entity.Device] during update time for groups [javax.validation.groups.Default, ] List of constraint violations:[ ConstraintViolationImpl{interpolatedMessage='may not be null', propertyPath=sendDate, rootBeanClass=class com.entity.Device, messageTemplate='{javax.validation.constraints.NotNull.message}'} ]

So as you can see from stacktrace I receive TransactionSystemException first and because of this my ExceptionHandler method (hiberAnnotationExceptionHandler) doesn't call. So my question is how to emulate this exception (ConstraintViolationException)? Thanks in advance.

2

2 Answers

4
votes

Reason for TransactionSystemException

  1. Hibernate entity manager is responsible for throwing all hibernate exceptions

If you go inside the code AbstractEntityManagerImpl.convert() method, you will see that by default it's not handling any specific exception like ConstraintViolation instead it just throws and wraps in PersistenceException.

  1. JPA transaction manager catches the above exception if you are calling your code inside @Transaction and converts to TransactionSystemException,you can see the code of spring JPA transaction manager class="org.springframework.orm.jpa.JpaTransactionManager"

Solution for correctly resolving your exception

  1. First register a JPA dialect which can intercept those HibernateExceptions and wrap into specific spring exceptions like this in your transaction manager bean.
 <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory"/>
    <property name="jpaDialect" ref="jpaDialect"/>
</bean>

<bean id="jpaDialect" class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/>

HibernateJpaDialect catches those exception and converts to spring specific exceptions like this

if (ex instanceof ConstraintViolationException) {
          ConstraintViolationException jdbcEx = (ConstraintViolationException) ex;
          return new DataIntegrityViolationException(ex.getMessage()  + "; SQL [" + jdbcEx.getSQL() +
                  "]; constraint [" + jdbcEx.getConstraintName() + "]", ex);
      }
  1. You can also register that your entitymanager is using hibernate jpaVendorAdapter to be complete that your using hibernate everywhere.
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
      <property name="packagesToScan" value="com.pack.model" />
        <property name="persistenceUnitManager" ref="persistenceUnitManager"/>
        <property name="persistenceUnitName" value="entityManager"/>
        <property name="jpaVendorAdapter" ref="jpaVendorAdapter"/>
    </bean>    <bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"/>
  1. Now in your controller you can expect dataIntegrityException which came from hibernate dialect which is thrown because of ConstraintViolationException

@ExceptionHandler(DataIntegrityViolationException.class)

-1
votes

Try to reproduce next steps:

  • Add to your WebConfiguration file
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
    return new MethodValidationPostProcessor();
}
  • mark your Controller with @Validated

  • Mark your paramater in Controller's method with

@Pattern(regexp = "\\d+") @RequestParam String param

  • Then just call that method with parameter like "Some string without digits".


If you want to use that Entity. Do something like

@Valid @RequestBody Device device 

in your method. And you can skip all previous steps.