Код: Выделить всё
public class ClientService {
@Transactional
public ClientDTO method1(Long userId) {
//some code
return method2(userId);
}
@Transactional
public ClientDTO method2(Long userId) {
//some code
ClientDTO client = anotherClass.getClient();
return method3(userId, ClientDTO.getId());
}
@Transactional
public ClientDTO method3(Long userId, Long clientId) {
User user = userRepository.findByIdForUpdate(userId).get();
Client client = clientRepository.findByIdForUpdate(clientId).get();
client.setUser(user);
client.setStatus(Constants.CLIENT_STATUS_CALLED));
user.setStatus(Constants.OPERATOR_STATUS_WAITING_FOR_THE_CALLED_CLIENT);
result = mapper.toDTO(client);
//some code
return result;
}
}
Было решено ввести блокировки на уровне БД. Также было решено вынести в отдельный метод транзакции фрагмент кода, задающий статусы клиенту и оператору: чтобы эта операция выполнялась максимально атомарно.
Код: Выделить всё
public interface UserRepository extends GenericRepository {
@Lock(LockModeType.PESSIMISTIC_WRITE)
Optional findByIdForUpdate(Long id);
}
public interface ClientRepository extends GenericRepository {
@Lock(LockModeType.PESSIMISTIC_READ)
Optional findByIdForUpdate(Long id);
}
public class ClientService {
//@Transactional
public ClientDTO method1(Long userId) {
//some code
return method2(userId);
}
//@Transactional
public ClientDTO method2(Long userId) {
//some code
final short tryCount = 3;
for (int i = 0; i < tryCount; i++) {
ClientDTO client = anotherClass.getClient();
try {
result = method3(userId, ClientDTO.getId());
} catch (ClientIsBusyException e) {
log.info("try again");
continue;
}
return result;
}
}
//@Transactional
public ClientDTO method3(Long userId, Long clientId) throws ClientIsBusyException {
result = clientTransactionalService.clientSetStateCalled(userId, clientId);
//some code
return result;
}
}
public class ClientTransactionalService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public ClientDTO clientSetStateCalled(Long userId, Long clientId) throws ClientIsBusyException {
User user = userRepository.findByIdForUpdate(userId).get();
if(Constants.OPERATOR_STATUS_WAITING_FOR_THE_CALLED_CLIENT.equals(user.getStatus().getId())
throw new IllegalStateException();
Client client = clientRepository.findByIdForUpdate(clientId).get();
if (client.getUser() != null && !Objects.equals(client.getUser().getId(), userId))
throw new ClientIsBusyException();
client.setUser(user);
client.setStatus(Constants.CLIENT_STATUS_CALLED));
user.setStatus(Constants.OPERATOR_STATUS_WAITING_FOR_THE_CALLED_CLIENT);
return mapper.toDTO(client);
}
}
Теперь в некоторых сценариях, когда метод1 вызывается дважды подряд, в первый раз он блокирует запись в таблице Client, но не снимает блокировку в конце метода clientSetStateCalled. При втором вызове clientRepository.findByIdForUpdate зависает, ожидая истечения срока блокировки.
У меня два вопроса:
[*]Как Hibernate управляет транзакциями, если @Transactional не установлен?
[*]Как я могу гарантировать, что блокировка таблиц будет снята на 100% с помощью метода clientSetStateCalled?
Подробнее здесь: https://stackoverflow.com/questions/793 ... ction-ends
Мобильная версия