class HerdErrorInformationExceptionHandler {

    @ExceptionHandler(value = {JpaSystemException.class, PersistenceException.class})
    @ResponseBody
    public ErrorInformation handlePersistenceException(Exception exception, HttpServletResponse response)
    {
        // Persistence exceptions typically wrap the cause which is what we're interested in to know what specific problem happened so get the root
        // exception.
        Throwable throwable = getRootCause(exception);

        if (isDataTruncationException(throwable))
        {
            // This is because the data being inserted was too large for a specific column in the database. When this happens, it will be due to a bad request.
            // Data truncation exceptions are thrown when we insert data that is too big for the column definition in MySQL.
            // On the other hand, Oracle throws only a generic JDBC exception, but has an error code we can check.
            // An alternative to using this database specific approach would be to define column lengths on the entities (e.g. @Column(length = 50))
            // which should throw a consistent exception by JPA that could be caught here. The draw back to using this approach is that need to custom
            // configure all column widths for all fields and keep that in sync with our DDL.
            return getErrorInformationAndSetStatus(HttpStatus.BAD_REQUEST, throwable, response);
        }
        else if (isCausedByConstraintViolationException(exception))
        {
            // A constraint violation exception will not typically be the root exception, but some exception in the chain. It is thrown when we try
            // to perform a database operation that violated a constraint (e.g. trying to delete a record that still has references to foreign keys
            // that exist, trying to insert duplicate keys, etc.). We are using ExceptionUtils to see if it exists somewhere in the chain.
            return getErrorInformationAndSetStatus(HttpStatus.BAD_REQUEST, new Exception("A constraint has been violated. Reason: " + throwable.getMessage()),
                response);
        }
        else
        {
            // For all other persistence exceptions, something is wrong that we weren't expecting so we'll return this as an internal server error.
            logError("A persistence error occurred.", exception);
            return getErrorInformationAndSetStatus(HttpStatus.INTERNAL_SERVER_ERROR, throwable == null ? new Exception("General Error") : throwable, response);
        }
    }

}