Spring 启动 @ControllerAdvice + @Transactional 未按预期工作
Spring Boot @ControllerAdvice + @Transactional not working as expected
我在投注网站上工作,因此交易非常重要。
我使用 @ControllerAdvice
创建了一个 ExceptionHandler 来捕获业务层的所有异常。
@ControllerAdvice
public class HttpExceptionHandler {
private String getStackTrace(final Throwable throwable) {
final StringWriter stringWriter = new StringWriter()
final PrintWriter printWriter = new PrintWriter(stringWriter, true)
throwable.printStackTrace(printWriter)
stringWriter.getBuffer().toString()
}
private List<String> filterStackTrace(final String stackTrace) {
def stack = stackTrace.split('\n\t')
stack.findAll({ it.contains('com.dsindigo.trading') })
}
private ResponseEntity<HttpErrorResponse> buildResponse(final Exception ex, final String message) {
HttpErrorResponse error = new HttpErrorResponse(
stack: filterStackTrace(getStackTrace(ex)),
message: message
)
new ResponseEntity<HttpErrorResponse>(error, HttpStatus.BAD_REQUEST)
}
@ExceptionHandler(UsernameAlreadyExistsException)
ResponseEntity<HttpErrorResponse> catchUsernameAlreadyExists(final UsernameAlreadyExistsException ex) {
buildResponse(ex, HttpErrorMessages.USERNAME_ALREADY_EXISTS)
}
@ExceptionHandler(EmailAlreadyExistsException)
ResponseEntity<HttpErrorResponse> catchEmailAlreadyExists(final EmailAlreadyExistsException ex) {
buildResponse(ex, HttpErrorMessages.EMAIL_ALREADY_EXISTS)
}
//More exceptions...
@ExceptionHandler(Exception)
ResponseEntity<HttpErrorResponse> catchAny(final Exception ex) {
buildResponse(ex, HttpErrorMessages.UNKNOWN)
}
}
所以基本上它会捕获异常(例如 UsernameAlreadyExistsException
)并创建一个 JSON 包含自定义消息和堆栈跟踪(用于调试目的)的响应。
这是服务抛出自定义异常的示例:
@Service
class UserServiceImpl implements UserService {
// @Autowired stuff ...
@Override
@Transactional
UserDTO save(UserDTO user) {
UserDTO current = findOneByUsername(user.username)
if (current != null)
throw new UsernameAlreadyExistsException()
current = findOneByEmail(user.email)
if (current != null)
throw new EmailAlreadyExistsException()
ConfigurationDTO configuration = configurationService.findOne();
user.active = false
user.password = bCryptPasswordEncoder.encode(user.password)
user.balance = configuration.initialBalance
User entity = mapper.map(user, User)
entity = userRepository.save(entity)
user = mapper.map(entity, UserDTO)
transactionService.createForUser(user.id, INITIAL_CHIPS_ID, configuration.initialBalance)
TokenDTO token = tokenService.createForUser(user.id)
emailService.sendRegisterMessage(user.email, token.token)
user
}
}
问题是,当我在没有 @Transactional
的情况下抛出自定义异常时,异常处理程序会执行正确的方法,但添加 @Transactional
总是会执行一般的 Exception
方法。
我是不是漏掉了什么?
这些声明:
@ExceptionHandler(UsernameAlreadyExistsException)
应该是这样的:
@ExceptionHandler(UsernameAlreadyExistsException.class)
此外,请确保您的异常扩展了 RuntimeException。如果异常在您的代码中的其他任何地方(包括 Spring 的拦截器!)被捕获和处理,它将不会被 ControllerAdvice 处理。
我在投注网站上工作,因此交易非常重要。
我使用 @ControllerAdvice
创建了一个 ExceptionHandler 来捕获业务层的所有异常。
@ControllerAdvice
public class HttpExceptionHandler {
private String getStackTrace(final Throwable throwable) {
final StringWriter stringWriter = new StringWriter()
final PrintWriter printWriter = new PrintWriter(stringWriter, true)
throwable.printStackTrace(printWriter)
stringWriter.getBuffer().toString()
}
private List<String> filterStackTrace(final String stackTrace) {
def stack = stackTrace.split('\n\t')
stack.findAll({ it.contains('com.dsindigo.trading') })
}
private ResponseEntity<HttpErrorResponse> buildResponse(final Exception ex, final String message) {
HttpErrorResponse error = new HttpErrorResponse(
stack: filterStackTrace(getStackTrace(ex)),
message: message
)
new ResponseEntity<HttpErrorResponse>(error, HttpStatus.BAD_REQUEST)
}
@ExceptionHandler(UsernameAlreadyExistsException)
ResponseEntity<HttpErrorResponse> catchUsernameAlreadyExists(final UsernameAlreadyExistsException ex) {
buildResponse(ex, HttpErrorMessages.USERNAME_ALREADY_EXISTS)
}
@ExceptionHandler(EmailAlreadyExistsException)
ResponseEntity<HttpErrorResponse> catchEmailAlreadyExists(final EmailAlreadyExistsException ex) {
buildResponse(ex, HttpErrorMessages.EMAIL_ALREADY_EXISTS)
}
//More exceptions...
@ExceptionHandler(Exception)
ResponseEntity<HttpErrorResponse> catchAny(final Exception ex) {
buildResponse(ex, HttpErrorMessages.UNKNOWN)
}
}
所以基本上它会捕获异常(例如 UsernameAlreadyExistsException
)并创建一个 JSON 包含自定义消息和堆栈跟踪(用于调试目的)的响应。
这是服务抛出自定义异常的示例:
@Service
class UserServiceImpl implements UserService {
// @Autowired stuff ...
@Override
@Transactional
UserDTO save(UserDTO user) {
UserDTO current = findOneByUsername(user.username)
if (current != null)
throw new UsernameAlreadyExistsException()
current = findOneByEmail(user.email)
if (current != null)
throw new EmailAlreadyExistsException()
ConfigurationDTO configuration = configurationService.findOne();
user.active = false
user.password = bCryptPasswordEncoder.encode(user.password)
user.balance = configuration.initialBalance
User entity = mapper.map(user, User)
entity = userRepository.save(entity)
user = mapper.map(entity, UserDTO)
transactionService.createForUser(user.id, INITIAL_CHIPS_ID, configuration.initialBalance)
TokenDTO token = tokenService.createForUser(user.id)
emailService.sendRegisterMessage(user.email, token.token)
user
}
}
问题是,当我在没有 @Transactional
的情况下抛出自定义异常时,异常处理程序会执行正确的方法,但添加 @Transactional
总是会执行一般的 Exception
方法。
我是不是漏掉了什么?
这些声明:
@ExceptionHandler(UsernameAlreadyExistsException)
应该是这样的:
@ExceptionHandler(UsernameAlreadyExistsException.class)
此外,请确保您的异常扩展了 RuntimeException。如果异常在您的代码中的其他任何地方(包括 Spring 的拦截器!)被捕获和处理,它将不会被 ControllerAdvice 处理。