From 6fb8e408003d05564ca5c47b17ddf6689947786d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?M=C3=A1rio=20Mur=C3=ADn?= <xmurin@fi.muni.cz>
Date: Mon, 29 Jul 2024 15:49:54 +0200
Subject: [PATCH] Allow for parallel User entity queries

---
 .../facade/TrainingDefinitionFacade.java      | 13 +-----
 .../facade/TrainingInstanceFacade.java        | 13 +-----
 .../repository/UserRefRepository.java         |  2 +-
 .../repository/UserRefRepositoryCustom.java   | 22 ++++++++++
 .../repository/UserRefRepositoryImpl.java     | 41 +++++++++++++++++++
 .../adaptive/service/UserService.java         | 15 ++++---
 .../training/TrainingDefinitionService.java   | 11 +----
 .../training/TrainingInstanceService.java     |  9 +---
 .../service/training/TrainingRunService.java  |  9 ++--
 9 files changed, 82 insertions(+), 53 deletions(-)
 create mode 100644 src/main/java/cz/muni/ics/kypo/training/adaptive/repository/UserRefRepositoryCustom.java
 create mode 100644 src/main/java/cz/muni/ics/kypo/training/adaptive/repository/UserRefRepositoryImpl.java

diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/facade/TrainingDefinitionFacade.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/facade/TrainingDefinitionFacade.java
index 9dff1a6b..16c76509 100644
--- a/src/main/java/cz/muni/ics/kypo/training/adaptive/facade/TrainingDefinitionFacade.java
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/facade/TrainingDefinitionFacade.java
@@ -219,12 +219,6 @@ public class TrainingDefinitionFacade {
         trainingDefinitionService.update(mappedTrainingDefinition);
     }
 
-    private User createUserRefFromDTO(UserRefDTO userToBeCreated) {
-        User user = new User();
-        user.setUserRefId(userToBeCreated.getUserRefId());
-        return user;
-    }
-
     /**
      * Clones Training Definition by id
      *
@@ -359,11 +353,8 @@ public class TrainingDefinitionFacade {
             if (actualAuthorsIds.contains(author.getUserRefId())) {
                 continue;
             }
-            try {
-                trainingDefinition.addAuthor(userService.getUserByUserRefId(author.getUserRefId()));
-            } catch (EntityNotFoundException ex) {
-                trainingDefinition.addAuthor(userService.createUserRef(createUserRefFromDTO(author)));
-            }
+            User user = userService.createOrGetUserRef(author.getUserRefId());
+            trainingDefinition.addAuthor(user);
         }
     }
 
diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/facade/TrainingInstanceFacade.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/facade/TrainingInstanceFacade.java
index 26a53056..69c07477 100644
--- a/src/main/java/cz/muni/ics/kypo/training/adaptive/facade/TrainingInstanceFacade.java
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/facade/TrainingInstanceFacade.java
@@ -197,11 +197,8 @@ public class TrainingInstanceFacade {
             if (actualOrganizersIds.contains(organizer.getUserRefId())) {
                 continue;
             }
-            try {
-                trainingInstance.addOrganizer(userService.getUserByUserRefId(organizer.getUserRefId()));
-            } catch (EntityNotFoundException ex) {
-                trainingInstance.addOrganizer(userService.createUserRef(createUserRefFromDTO(organizer)));
-            }
+            User user = userService.createOrGetUserRef(organizer.getUserRefId());
+            trainingInstance.addOrganizer(user);
         }
     }
 
@@ -218,12 +215,6 @@ public class TrainingInstanceFacade {
         return users;
     }
 
-    private User createUserRefFromDTO(UserRefDTO userToBeCreated) {
-        User user = new User();
-        user.setUserRefId(userToBeCreated.getUserRefId());
-        return user;
-    }
-
     /**
      * Deletes specific training instance based on id
      *
diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/UserRefRepository.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/UserRefRepository.java
index c44943f6..f42c7f7d 100644
--- a/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/UserRefRepository.java
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/UserRefRepository.java
@@ -13,7 +13,7 @@ import java.util.Set;
  * The JPA repository interface to manage {@link User} instances.
  */
 @Repository
-public interface UserRefRepository extends JpaRepository<User, Long>, QuerydslPredicateExecutor<User> {
+public interface UserRefRepository extends JpaRepository<User, Long>, QuerydslPredicateExecutor<User>, UserRefRepositoryCustom {
 
     /**
      * Find all users by userRefIds.
diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/UserRefRepositoryCustom.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/UserRefRepositoryCustom.java
new file mode 100644
index 00000000..158c5286
--- /dev/null
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/UserRefRepositoryCustom.java
@@ -0,0 +1,22 @@
+package cz.muni.ics.kypo.training.adaptive.repository;
+
+import cz.muni.ics.kypo.training.adaptive.domain.User;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.repository.query.Param;
+
+import javax.transaction.Transactional;
+
+
+public interface UserRefRepositoryCustom {
+
+    /**
+     * Insert user reference if it does not exist in the database.
+     *
+     * @param userRefId the user reference id
+     * @return the number of rows affected
+     */
+    @Modifying
+    @Transactional
+    User createOrGet(@Param("userRefId") Long userRefId);
+
+}
diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/UserRefRepositoryImpl.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/UserRefRepositoryImpl.java
new file mode 100644
index 00000000..4324a206
--- /dev/null
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/UserRefRepositoryImpl.java
@@ -0,0 +1,41 @@
+package cz.muni.ics.kypo.training.adaptive.repository;
+
+import cz.muni.ics.kypo.training.adaptive.domain.User;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Repository;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Root;
+
+@Repository
+public class UserRefRepositoryImpl implements UserRefRepositoryCustom {
+
+    private static final Logger LOG = LoggerFactory.getLogger(UserRefRepositoryImpl.class);
+
+    @PersistenceContext
+    private EntityManager entityManager;
+
+    @Override
+    public User createOrGet(Long userRefId) {
+        int rowsAffected = entityManager.createNativeQuery("INSERT INTO \"user\" (user_ref_id) VALUES (:userRefId)" +
+                        "ON CONFLICT DO NOTHING")
+                .setParameter("userRefId", userRefId)
+                .executeUpdate();
+
+        if (rowsAffected != 0) {
+            LOG.info("User with user_ref_id {} created", userRefId);
+        }
+
+        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
+        CriteriaQuery<User> criteriaQuery = criteriaBuilder.createQuery(User.class);
+        Root<User> root = criteriaQuery.from(User.class);
+        criteriaBuilder.and(criteriaBuilder.equal(root.get("userRefId"), userRefId));
+        return entityManager.createQuery(criteriaQuery).getSingleResult();
+    }
+
+
+}
diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/service/UserService.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/service/UserService.java
index 6e78c324..0a8165f7 100644
--- a/src/main/java/cz/muni/ics/kypo/training/adaptive/service/UserService.java
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/service/UserService.java
@@ -45,15 +45,14 @@ public class UserService {
     }
 
     /**
-     * Create new user reference
+     * If user reference with given user id does not exist, it is created and returned.
+     * Otherwise, the existing one is returned.
      *
-     * @param userToCreate user reference to be created
-     * @return created {@link User}
+     * @param userRefId id of the referenced user
+     * @return user reference with given referenced id
      */
-    @TransactionalWO
-    public User createUserRef(User userToCreate) {
-        User user = userRefRepository.save(userToCreate);
-        LOG.info("User ref with user_ref_id: {} created.", user.getUserRefId());
-        return user;
+    public User createOrGetUserRef(Long userRefId) {
+        return userRefRepository.createOrGet(userRefId);
     }
+    
 }
diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/service/training/TrainingDefinitionService.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/service/training/TrainingDefinitionService.java
index 2648a26c..5005ff31 100644
--- a/src/main/java/cz/muni/ics/kypo/training/adaptive/service/training/TrainingDefinitionService.java
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/service/training/TrainingDefinitionService.java
@@ -456,14 +456,7 @@ public class TrainingDefinitionService {
     }
 
     private void addLoggedInUserToTrainingDefinitionAsAuthor(TrainingDefinition trainingDefinition) {
-        Long loggedInId = userManagementServiceApi.getLoggedInUserRefId();
-        Optional<User> user = userRefRepository.findUserByUserRefId(loggedInId);
-        if (user.isPresent()) {
-            trainingDefinition.addAuthor(user.get());
-        } else {
-            User newUser = new User(loggedInId);
-            userRefRepository.saveAndFlush(newUser);
-            trainingDefinition.addAuthor(newUser);
-        }
+        User user = userRefRepository.createOrGet(userManagementServiceApi.getLoggedInUserRefId());
+        trainingDefinition.addAuthor(user);
     }
 }
diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/service/training/TrainingInstanceService.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/service/training/TrainingInstanceService.java
index 7aebe488..67d73f06 100644
--- a/src/main/java/cz/muni/ics/kypo/training/adaptive/service/training/TrainingInstanceService.java
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/service/training/TrainingInstanceService.java
@@ -214,13 +214,8 @@ public class TrainingInstanceService {
     }
 
     private void addLoggedInUserAsOrganizerToTrainingInstance(TrainingInstance trainingInstance) {
-        Optional<User> authorOfTrainingInstance = organizerRefRepository.findUserByUserRefId(userManagementServiceApi.getLoggedInUserRefId());
-        if (authorOfTrainingInstance.isPresent()) {
-            trainingInstance.addOrganizer(authorOfTrainingInstance.get());
-        } else {
-            User user = new User(userManagementServiceApi.getLoggedInUserRefId());
-            trainingInstance.addOrganizer(organizerRefRepository.save(user));
-        }
+        User userRef = organizerRefRepository.createOrGet(userManagementServiceApi.getLoggedInUserRefId());
+        trainingInstance.addOrganizer(userRef);
     }
 
     /**
diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/service/training/TrainingRunService.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/service/training/TrainingRunService.java
index fd77628a..b4b955ae 100644
--- a/src/main/java/cz/muni/ics/kypo/training/adaptive/service/training/TrainingRunService.java
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/service/training/TrainingRunService.java
@@ -590,12 +590,9 @@ public class TrainingRunService {
         TrainingRun newTrainingRun = new TrainingRun();
         newTrainingRun.setCurrentPhase(currentPhase);
 
-        Optional<User> userRefOpt = participantRefRepository.findUserByUserRefId(participantRefId);
-        if (userRefOpt.isPresent()) {
-            newTrainingRun.setParticipantRef(userRefOpt.get());
-        } else {
-            newTrainingRun.setParticipantRef(participantRefRepository.save(new User(userManagementServiceApi.getLoggedInUserRefId())));
-        }
+        User userRef = participantRefRepository.createOrGet(participantRefId);
+        newTrainingRun.setParticipantRef(userRef);
+
         newTrainingRun.setState(TRState.RUNNING);
         newTrainingRun.setTrainingInstance(trainingInstance);
         newTrainingRun.setStartTime(startTime);
-- 
GitLab