@Transactional public void testTransactionRetry() { try { SimpleTestEntity ent1 = new SimpleTestEntity(); ent1.setName("testTransactionRetry"); ent1 = dao.save(ent1); if (transactionRetryInvocations == 0) { throw new TransientDataAccessException("bla bla") { private static final long serialVersionUID = 1L; }; } } finally { transactionRetryInvocations++; } }
@Transactional public void testTransactionRetry() { try { TestEntity ent1 = new TestEntity(); ent1.setName("testTransactionRetry"); ent1 = dao.save(ent1); if (transactionRetryInvocations == 0) { throw new TransientDataAccessException("bla bla") { private static final long serialVersionUID = 1L; }; } } finally { transactionRetryInvocations++; } }
@Override public void accept(Event<ResourceNotification> event) { LOGGER.info("Resource notification event received: {}", event); ResourceNotification notification = event.getData(); RetryUtil.withDefaultRetries() .retry(() -> { ResourceNotification notificationPersisted; switch (notification.getType()) { case CREATE: notificationPersisted = cloudResourcePersisterService.persist(notification); break; case UPDATE: notificationPersisted = cloudResourcePersisterService.update(notification); break; case DELETE: notificationPersisted = cloudResourcePersisterService.delete(notification); break; default: throw new IllegalArgumentException("Unsupported notification type: " + notification.getType()); } notificationPersisted.getPromise().onNext(new ResourcePersisted()); }) .checkIfRecoverable(e -> e instanceof TransientDataAccessException) .ifNotRecoverable(e -> notification.getPromise().onError(e)).run(); }
/** * Returns all the exceptions for which a retry is useful * * @return - Map containing all retryable exceptions for the {@link BinaryExceptionClassifier} */ private static Map<Class<? extends Throwable>, Boolean> getSqlRetryAbleExceptions() { Map<Class<? extends Throwable>, Boolean> retryableExceptions = new HashMap<>(); retryableExceptions.put(SQLTransientException.class, true); retryableExceptions.put(SQLRecoverableException.class, true); retryableExceptions.put(TransientDataAccessException.class, true); retryableExceptions.put(SQLNonTransientConnectionException.class, true); return retryableExceptions; }
@Transactional(isolation = Isolation.SERIALIZABLE) @Retryable(value = TransientDataAccessException.class, maxAttempts = 5) public VirtualPaymentResult virtualPayment(@NonNull ECKey keySender, @NonNull ECKey keyReceiver, long amount, long requestNonce) throws InvalidNonceException, UserNotFoundException, InvalidAmountException, InsufficientFunds, InvalidRequestException { // Sender and receiver must be different entities if (keySender.getPublicKeyAsHex().equals(keyReceiver.getPublicKeyAsHex())) throw new InvalidRequestException("The sender and receiver cannot be the same entities"); final Account sender = accountRepository.findByClientPublicKey(keySender.getPubKey()); if (sender == null) throw new UserNotFoundException(keySender.getPublicKeyAsHex()); // Abort if the nonce is not fresh, we allow only higher nonces than in the database // The nonce is generated by the client as unix epoch time in milliseconds. // This prevents possible replay attacks: // If we receive the same nonce as in the database, the request was probably sent two times // If the nonce in the database is larger than the request, we probably got an old request sent again. if (requestNonce <= sender.nonce()) throw new InvalidNonceException("Invalid nonce. Request already processed?"); // Fail if amount is invalid if (amount < 1) throw new InvalidAmountException("Invalid amount. Must be 1 or larger."); // Check for sufficient funds if (amount > sender.virtualBalance()) throw new InsufficientFunds("Insufficient funds, only " + sender.virtualBalance() + " satoshis available"); // Get receiver from database final Account receiver = accountRepository.findByClientPublicKey(keyReceiver.getPubKey()); if (receiver == null) throw new UserNotFoundException(keyReceiver.getPublicKeyAsHex()); // Do the transfer final long senderOldBalance = sender.virtualBalance(); final long receiverOldBalance = receiver.virtualBalance(); sender.virtualBalance(senderOldBalance - amount); receiver.virtualBalance(receiverOldBalance + amount); // Guarantee that this request is only processed once sender.nonce(requestNonce); accountRepository.save(sender); accountRepository.save(receiver); // Return the new balances and the keys for sender and receiver that can be used for signing return new VirtualPaymentResult(sender.virtualBalance(), sender.serverPrivateKey(), receiver.virtualBalance(), receiver.serverPrivateKey()); }