From 222e5915ed714fff5e5549ff28a1fa7718d4c71c Mon Sep 17 00:00:00 2001 From: Viktor Seifert Date: Tue, 7 Mar 2023 18:32:33 +0100 Subject: [PATCH] RED-6310: Moved code to create user-preferences to a separate class so that the calling code can handle a persistence exception (cherry picked from commit 45bd8e600328ce85c8457525b605da2c16dd70c8) --- ...ficationPreferencesPersistenceService.java | 55 +++++++++++++++---- .../NotificationPreferencesRepository.java | 3 + 2 files changed, 46 insertions(+), 12 deletions(-) diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/NotificationPreferencesPersistenceService.java b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/NotificationPreferencesPersistenceService.java index 57b270993..d6605b8ec 100644 --- a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/NotificationPreferencesPersistenceService.java +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/NotificationPreferencesPersistenceService.java @@ -10,6 +10,8 @@ import java.util.stream.Collectors; import javax.transaction.Transactional; import org.springframework.beans.BeanUtils; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; import com.iqser.red.service.persistence.management.v1.processor.entity.notification.NotificationPreferencesEntity; @@ -18,15 +20,20 @@ import com.iqser.red.service.persistence.management.v1.processor.service.persist import com.iqser.red.service.persistence.service.v1.api.model.notification.NotificationPreferences; import com.iqser.red.service.persistence.service.v1.api.model.notification.NotificationType; +import lombok.AccessLevel; import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; @Service @RequiredArgsConstructor +@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) public class NotificationPreferencesPersistenceService { - private final NotificationPreferencesRepository notificationPreferencesRepository; + NotificationPreferencesRepository notificationPreferencesRepository; - private final NotificationRepository notificationRepository; + NonThreadSafeNotificationPreferencesRepositoryWrapper notificationPreferencesRepositoryWrapper; + + NotificationRepository notificationRepository; @Transactional @@ -64,18 +71,17 @@ public class NotificationPreferencesPersistenceService { } - @Transactional + // This method intentionally does not have a @Transactional annotation, since it needs to handle an underlying transaction exception. public NotificationPreferencesEntity getOrCreateNotificationPreferences(String userId) { - return notificationPreferencesRepository.findByUserId(userId).orElseGet(() -> { - - var notificationPreference = new NotificationPreferencesEntity(); - notificationPreference.setUserId(userId); - notificationPreference.setEmailNotificationsEnabled(false); - notificationPreference.setInAppNotificationsEnabled(true); - notificationPreference.setInAppNotifications(Arrays.stream(NotificationType.values()).map(Enum::name).collect(Collectors.toList())); - return notificationPreferencesRepository.save(notificationPreference); - }); + try { + // The method called here will fail if it is called concurrently (more than 1 thread), since it will always try to create + // the desired entity. But the exception only means, that the entity has been created by another thread. + // In that case we can just fetch the data from the db. + return notificationPreferencesRepositoryWrapper.getOrCreateNotificationPreferences(userId); + } catch (DataIntegrityViolationException ex) { + return notificationPreferencesRepository.getByUserId(userId); + } } @@ -91,4 +97,29 @@ public class NotificationPreferencesPersistenceService { return notificationPreferencesRepository.findAll(); } + + @Component + @RequiredArgsConstructor + @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) + private static class NonThreadSafeNotificationPreferencesRepositoryWrapper { + + NotificationPreferencesRepository notificationPreferencesRepository; + + + @Transactional(Transactional.TxType.REQUIRES_NEW) + public NotificationPreferencesEntity getOrCreateNotificationPreferences(String userId) { + + return notificationPreferencesRepository.findByUserId(userId).orElseGet(() -> { + + var notificationPreference = new NotificationPreferencesEntity(); + notificationPreference.setUserId(userId); + notificationPreference.setEmailNotificationsEnabled(false); + notificationPreference.setInAppNotificationsEnabled(true); + notificationPreference.setInAppNotifications(Arrays.stream(NotificationType.values()).map(Enum::name).collect(Collectors.toList())); + return notificationPreferencesRepository.save(notificationPreference); + }); + } + + } + } diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/repository/NotificationPreferencesRepository.java b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/repository/NotificationPreferencesRepository.java index f151527e4..4340130f8 100644 --- a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/repository/NotificationPreferencesRepository.java +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/repository/NotificationPreferencesRepository.java @@ -11,6 +11,9 @@ public interface NotificationPreferencesRepository extends JpaRepository findByUserId(String userId); + NotificationPreferencesEntity getByUserId(String userId); + + void deleteByUserId(String userId); }