Skip to content

Commit 2c1dd52

Browse files
committed
Modifications applications: split long running transaction before/after the computations
This requires to reorganize the code and the transactions the fully load the data, hence the changes in NetworkModificationRepository with the new intermediate methods. Add testInsertCompositeModifications for symmetry with other code paths duplicate/move/ in the switch/case of ModificationController ?action=...
1 parent 74ff1e2 commit 2c1dd52

File tree

4 files changed

+132
-15
lines changed

4 files changed

+132
-15
lines changed

src/main/java/org/gridsuite/modification/server/repositories/NetworkModificationRepository.java

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,11 @@ public List<ModificationEntity> saveModifications(UUID groupUuid, List<Modificat
115115
@Transactional // To have all create in the same transaction (atomic)
116116
// TODO Remove transaction when errors will no longer be sent to the front
117117
public List<ModificationEntity> saveModificationInfos(UUID groupUuid, List<ModificationInfos> modifications) {
118+
return saveModificationInfosNonTransactional(groupUuid, modifications);
119+
}
120+
121+
private List<ModificationEntity> saveModificationInfosNonTransactional(UUID groupUuid,
122+
List<ModificationInfos> modifications) {
118123
List<ModificationEntity> entities = modifications.stream().map(ModificationEntity::fromDTO).toList();
119124

120125
return saveModificationsNonTransactional(groupUuid, entities);
@@ -165,6 +170,20 @@ private List<ModificationEntity> saveModificationsNonTransactional(UUID groupUui
165170
@Transactional
166171
// TODO Remove transaction when errors will no longer be sent to the front
167172
public List<ModificationEntity> moveModifications(UUID destinationGroupUuid, UUID originGroupUuid, List<UUID> modificationsToMoveUUID, UUID referenceModificationUuid) {
173+
return moveModificationsNonTransactional(destinationGroupUuid, originGroupUuid, modificationsToMoveUUID, referenceModificationUuid);
174+
}
175+
176+
@Transactional
177+
public List<ModificationEntity> moveModificationsFullDto(UUID destinationGroupUuid, UUID originGroupUuid, List<UUID> modificationsToMoveUUID, UUID referenceModificationUuid) {
178+
List<ModificationEntity> movedModifications = moveModificationsNonTransactional(destinationGroupUuid, originGroupUuid, modificationsToMoveUUID, referenceModificationUuid);
179+
// Force load subentities/collections, needed later when the transaction is closed
180+
// to avoid LazyInitialisationException. Maybe better to refactor to return the dto ?
181+
// And refactor to more efficiently load the data (avoid 1+N) ?
182+
movedModifications.forEach(ModificationEntity::toModificationInfos);
183+
return movedModifications;
184+
}
185+
186+
private List<ModificationEntity> moveModificationsNonTransactional(UUID destinationGroupUuid, UUID originGroupUuid, List<UUID> modificationsToMoveUUID, UUID referenceModificationUuid) {
168187
// read origin group and modifications
169188
ModificationGroupEntity originModificationGroupEntity = getModificationGroup(originGroupUuid);
170189
List<ModificationEntity> originModificationEntities = originModificationGroupEntity.getModifications()
@@ -443,7 +462,7 @@ public ModificationInfos getModificationInfos(ModificationEntity modificationEnt
443462
return modificationEntity.toModificationInfos();
444463
}
445464

446-
public List<ModificationEntity> getModificationsEntities(List<UUID> groupUuids, boolean onlyStashed) {
465+
private List<ModificationEntity> getModificationsEntitiesNonTransactional(List<UUID> groupUuids, boolean onlyStashed) {
447466
Stream<ModificationEntity> entityStream = groupUuids.stream().flatMap(this::getModificationEntityStream);
448467
if (onlyStashed) {
449468
return entityStream.filter(m -> m.getStashed() == onlyStashed).toList();
@@ -452,6 +471,21 @@ public List<ModificationEntity> getModificationsEntities(List<UUID> groupUuids,
452471
}
453472
}
454473

474+
//TODO ? should be @Transactional(readOnly = true)
475+
public List<ModificationEntity> getModificationsEntities(List<UUID> groupUuids, boolean onlyStashed) {
476+
return getModificationsEntitiesNonTransactional(groupUuids, onlyStashed);
477+
}
478+
479+
@Transactional(readOnly = true)
480+
public List<ModificationEntity> getModificationsEntitiesFullDto(List<UUID> groupUuids, boolean onlyStashed) {
481+
List<ModificationEntity> modificationsEntities = getModificationsEntitiesNonTransactional(groupUuids, onlyStashed);
482+
// Force load subentities/collections, needed later when the transaction is closed
483+
// to avoid LazyInitialisationException. Maybe better to refactor to return the dto ?
484+
// And refactor to more efficiently load the data (avoid 1+N) ?
485+
modificationsEntities.forEach(ModificationEntity::toModificationInfos);
486+
return modificationsEntities;
487+
}
488+
455489
@Transactional(readOnly = true)
456490
public ModificationInfos getModificationInfo(UUID modificationUuid) {
457491
return getModificationInfos(getModificationEntity(modificationUuid));
@@ -525,6 +559,10 @@ public Integer getModificationsCount(@NonNull UUID groupUuid, boolean stashed) {
525559

526560
@Transactional(readOnly = true)
527561
public List<ModificationInfos> getModificationsInfos(@NonNull List<UUID> uuids) {
562+
return getModificationsInfosNonTransactional(uuids);
563+
}
564+
565+
private List<ModificationInfos> getModificationsInfosNonTransactional(List<UUID> uuids) {
528566
// Spring-data findAllById doc says: the order of elements in the result is not guaranteed
529567
Map<UUID, ModificationEntity> entities = modificationRepository.findAllById(uuids)
530568
.stream()
@@ -553,6 +591,10 @@ public List<ModificationInfos> getBasicNetworkModificationsFromComposite(@NonNul
553591

554592
@Transactional(readOnly = true)
555593
public List<ModificationInfos> getCompositeModificationsInfos(@NonNull List<UUID> uuids) {
594+
return getCompositeModificationsInfosNonTransactional(uuids);
595+
}
596+
597+
private List<ModificationInfos> getCompositeModificationsInfosNonTransactional(@NonNull List<UUID> uuids) {
556598
List<ModificationInfos> entities = new ArrayList<>();
557599
uuids.forEach(uuid -> {
558600
List<UUID> foundEntities = modificationRepository.findModificationIdsByCompositeModificationId(uuid);
@@ -568,6 +610,10 @@ public List<ModificationInfos> getCompositeModificationsInfos(@NonNull List<UUID
568610

569611
@Transactional(readOnly = true)
570612
public List<ModificationInfos> getActiveModificationsInfos(@NonNull UUID groupUuid) {
613+
return getActiveModificationsInfosNonTransactional(groupUuid);
614+
}
615+
616+
private List<ModificationInfos> getActiveModificationsInfosNonTransactional(UUID groupUuid) {
571617
return getModificationEntityStream(groupUuid).filter(m -> !m.getStashed()).map(this::getModificationInfos).toList();
572618
}
573619

@@ -812,4 +858,16 @@ private void deleteTabularModificationSubModifications(TabularModificationEntity
812858
throw new UnsupportedOperationException(String.format("No sub-modifications deletion for modification type: %s", tabularModificationType));
813859
}
814860
}
861+
862+
@Transactional
863+
public List<ModificationEntity> saveDuplicateModifications(@NonNull UUID targetGroupUuid, UUID originGroupUuid, @NonNull List<UUID> modificationsUuids) {
864+
List<ModificationInfos> modificationInfos = originGroupUuid != null ? getActiveModificationsInfosNonTransactional(originGroupUuid) : getModificationsInfosNonTransactional(modificationsUuids);
865+
return saveModificationInfosNonTransactional(targetGroupUuid, modificationInfos);
866+
}
867+
868+
@Transactional
869+
public List<ModificationEntity> saveCompositeModifications(@NonNull UUID targetGroupUuid, @NonNull List<UUID> modificationsUuids) {
870+
List<ModificationInfos> modificationInfos = getCompositeModificationsInfosNonTransactional(modificationsUuids);
871+
return saveModificationInfosNonTransactional(targetGroupUuid, modificationInfos);
872+
}
815873
}

src/main/java/org/gridsuite/modification/server/service/NetworkModificationService.java

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -191,9 +191,6 @@ public void restoreNetworkModifications(UUID groupUuid, @NonNull List<UUID> modi
191191
networkModificationRepository.getModificationsCount(groupUuid, false));
192192
}
193193

194-
// No transactional because we need to save modification in DB also in case of error
195-
// Transaction made in 'saveModifications' method
196-
// TODO Add transaction when errors will no longer be sent to the front
197194
public NetworkModificationsResult createNetworkModification(@NonNull UUID groupUuid, @NonNull ModificationInfos modificationInfo, @NonNull List<ModificationApplicationContext> applicationContexts) {
198195
List<ModificationEntity> modificationEntities = networkModificationRepository.saveModificationInfos(groupUuid, List.of(modificationInfo));
199196

@@ -237,7 +234,6 @@ public Network cloneNetworkVariant(UUID networkUuid,
237234
return network;
238235
}
239236

240-
@Transactional
241237
public NetworkModificationResult buildVariant(@NonNull UUID networkUuid, @NonNull BuildInfos buildInfos) {
242238
// Apply all modifications belonging to the modification groups uuids in buildInfos
243239
List<ModificationApplicationGroup> modificationGroupsInfos = new ArrayList<>();
@@ -246,7 +242,8 @@ public NetworkModificationResult buildVariant(@NonNull UUID networkUuid, @NonNul
246242
Set<UUID> modificationsToExclude = buildInfos.getModificationUuidsToExclude().get(groupUuid);
247243
List<ModificationEntity> modifications = List.of();
248244
try {
249-
modifications = networkModificationRepository.getModificationsEntities(List.of(groupUuid), false)
245+
// FullDto needed for toModificationInfos() after the modifications have been applied
246+
modifications = networkModificationRepository.getModificationsEntitiesFullDto(List.of(groupUuid), false)
250247
.stream()
251248
.filter(m -> modificationsToExclude == null || !modificationsToExclude.contains(m.getId()))
252249
.filter(m -> !m.getStashed())
@@ -287,12 +284,12 @@ public void deleteNetworkModifications(UUID groupUuid, List<UUID> modificationsU
287284
}
288285
}
289286

290-
@Transactional
291287
public NetworkModificationsResult moveModifications(@NonNull UUID destinationGroupUuid, @NonNull UUID originGroupUuid, UUID beforeModificationUuid,
292288
@NonNull List<UUID> modificationsToMoveUuids, @NonNull List<ModificationApplicationContext> applicationContexts,
293289
boolean applyModifications) {
294290
// update origin/destinations groups to cut and paste all modificationsToMove
295-
List<ModificationEntity> modificationEntities = networkModificationRepository.moveModifications(destinationGroupUuid, originGroupUuid, modificationsToMoveUuids, beforeModificationUuid);
291+
// FullDto needed for toModificationInfos() after the modifications have been applied
292+
List<ModificationEntity> modificationEntities = networkModificationRepository.moveModificationsFullDto(destinationGroupUuid, originGroupUuid, modificationsToMoveUuids, beforeModificationUuid);
296293

297294
List<Optional<NetworkModificationResult>> result = applyModifications && !modificationEntities.isEmpty() ? applyModifications(destinationGroupUuid, modificationEntities, applicationContexts) : List.of();
298295
return new NetworkModificationsResult(modificationEntities.stream().map(ModificationEntity::getId).toList(), result);
@@ -333,23 +330,19 @@ private Optional<NetworkModificationResult> applyModifications(UUID networkUuid,
333330
return Optional.empty();
334331
}
335332

336-
@Transactional
337333
public NetworkModificationsResult duplicateModifications(@NonNull UUID targetGroupUuid, UUID originGroupUuid, @NonNull List<UUID> modificationsUuids, @NonNull List<ModificationApplicationContext> applicationContexts) {
338334
if (originGroupUuid != null && !modificationsUuids.isEmpty()) { // Duplicate modifications from a group or from a list only
339335
throw new NetworkModificationServerException(DUPLICATION_ARGUMENT_INVALID);
340336
}
341-
List<ModificationInfos> modificationInfos = originGroupUuid != null ? networkModificationRepository.getActiveModificationsInfos(originGroupUuid) : networkModificationRepository.getModificationsInfos(modificationsUuids);
342-
List<ModificationEntity> duplicateModifications = networkModificationRepository.saveModificationInfos(targetGroupUuid, modificationInfos);
337+
List<ModificationEntity> duplicateModifications = networkModificationRepository.saveDuplicateModifications(targetGroupUuid, originGroupUuid, modificationsUuids);
343338
return new NetworkModificationsResult(
344339
duplicateModifications.stream().map(ModificationEntity::getId).toList(),
345340
applyModifications(targetGroupUuid, duplicateModifications, applicationContexts)
346341
);
347342
}
348343

349-
@Transactional
350344
public NetworkModificationsResult insertCompositeModifications(@NonNull UUID targetGroupUuid, @NonNull List<UUID> modificationsUuids, @NonNull List<ModificationApplicationContext> applicationContexts) {
351-
List<ModificationInfos> modificationInfos = networkModificationRepository.getCompositeModificationsInfos(modificationsUuids);
352-
List<ModificationEntity> modificationEntities = networkModificationRepository.saveModificationInfos(targetGroupUuid, modificationInfos);
345+
List<ModificationEntity> modificationEntities = networkModificationRepository.saveCompositeModifications(targetGroupUuid, modificationsUuids);
353346
return new NetworkModificationsResult(modificationEntities.stream().map(ModificationEntity::getId).toList(), applyModifications(targetGroupUuid, modificationEntities, applicationContexts));
354347
}
355348

src/test/java/org/gridsuite/modification/server/service/BuildTest.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,11 @@ void testIndexationAfterBuild(final MockWebServer server) {
362362
.couplingDevices(Arrays.asList(CouplingDeviceInfos.builder().busbarSectionId1("vl9_1_1").busbarSectionId2("vl9_2_1").build()))
363363
.build()));
364364
// add new Load
365+
// Need an entity with a lazyloaded subentity/collection to test that all required
366+
// data has been loaded during the initial completed transaction before applying modifications:
367+
// LoadCreationInfos and other modifications in this test do the job because it has the free properties Collection.
368+
// Is there a good way to add in this test that we have 2 short transactions
369+
// instead of one long idle transaction ?
365370
equipmentsToAdd.add(ModificationEntity.fromDTO(LoadCreationInfos.builder()
366371
.equipmentId("newLoad")
367372
.equipmentName("newLoad")

src/test/java/org/gridsuite/modification/server/service/ModificationIndexationTest.java

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,11 @@ void testDuplicateModifications() {
182182
Create first modification then apply it on group 1
183183
*/
184184
String newEquipmentId = "newLoad";
185+
// Need an entity with a lazyloaded subentity/collection to test that all required
186+
// data has been loaded during the completed transaction before applying modifications:
187+
// LoadCreationInfos does the job because it has the free properties Collection.
188+
// Is there a good way to add in this test that we have 2 short transactions
189+
// instead of one long idle transaction ?
185190
LoadCreationInfos loadCreationInfos = createLoadCreationInfos(newEquipmentId);
186191
UUID groupUuid1 = UUID.randomUUID();
187192
List<ModificationEntity> entities = modificationRepository.saveModifications(groupUuid1, List.of(ModificationEntity.fromDTO(loadCreationInfos)));
@@ -225,10 +230,14 @@ void testMoveModifications() {
225230
Create first modification then apply it on group 1
226231
*/
227232
String newEquipmentId = "newLoad";
233+
// Need an entity with a lazyloaded subentity/collection to test that all required
234+
// data has been loaded during the completed transaction before applying modifications:
235+
// LoadCreationInfos does the job because it has the free properties Collection.
236+
// Is there a good way to add in this test that we have 2 short transactions
237+
// instead of one long idle transaction ?
228238
LoadCreationInfos loadCreationInfos = createLoadCreationInfos(newEquipmentId);
229239
UUID groupUuid1 = UUID.randomUUID();
230240
List<ModificationEntity> entities = modificationRepository.saveModifications(groupUuid1, List.of(ModificationEntity.fromDTO(loadCreationInfos)));
231-
232241
NetworkModificationResult result = networkModificationApplicator.applyModifications(new ModificationApplicationGroup(groupUuid1, entities, reportInfos), networkInfos);
233242
assertNotNull(result);
234243

@@ -264,6 +273,58 @@ void testMoveModifications() {
264273
modificationApplicationInfos.forEach(applicationInfo -> assertEquals(newEquipmentId, applicationInfo.getCreatedEquipmentIds().iterator().next()));
265274
}
266275

276+
@Test
277+
void testInsertCompositeModifications() {
278+
/*
279+
Create first modification then apply it on group 1
280+
*/
281+
String newEquipmentId = "newLoad";
282+
// Need an entity with a lazyloaded subentity/collection to test that all required
283+
// data has been loaded during the completed transaction before applying modifications:
284+
// LoadCreationInfos does the job because it has the free properties Collection.
285+
// Is there a good way to add in this test that we have 2 short transactions
286+
// instead of one long idle transaction ?
287+
LoadCreationInfos loadCreationInfos = createLoadCreationInfos(newEquipmentId);
288+
UUID groupUuid1 = UUID.randomUUID();
289+
List<ModificationEntity> entities = modificationRepository.saveModifications(groupUuid1, List.of(ModificationEntity.fromDTO(loadCreationInfos)));
290+
291+
NetworkModificationResult result = networkModificationApplicator.applyModifications(new ModificationApplicationGroup(groupUuid1, entities, reportInfos), networkInfos);
292+
assertNotNull(result);
293+
294+
// Create the composite modification to pass later to ?action=insert
295+
UUID compositeUuid = networkModificationService.createNetworkCompositeModification(
296+
entities.stream().map(ModificationEntity::getId).toList()
297+
);
298+
299+
// Need to remove the listener created in the last modifications application
300+
((NetworkImpl) networkInfos.getNetwork()).getListeners().clear();
301+
302+
/*
303+
Insert as composite this modification to group 2, variant 2
304+
*/
305+
UUID groupUuid2 = UUID.randomUUID();
306+
NetworkModificationsResult modificationsResult = networkModificationService.insertCompositeModifications(
307+
groupUuid2,
308+
List.of(compositeUuid),
309+
List.of(new ModificationApplicationContext(networkInfos.getNetworkUuuid(), variant2, UUID.randomUUID(), UUID.randomUUID()))
310+
);
311+
312+
/*
313+
check results in database and in elasticsearch
314+
*/
315+
List<UUID> expectedModificationUuids = List.of(entities.getFirst().getId(), modificationsResult.modificationUuids().getFirst());
316+
List<UUID> expectedGroupUuids = List.of(groupUuid1, groupUuid2);
317+
318+
List<ModificationApplicationEntity> modificationApplicationEntities = modificationApplicationRepository.findAll();
319+
List<ModificationApplicationInfos> modificationApplicationInfos = IterableUtils.toList(modificationApplicationInfosRepository.findAll());
320+
321+
assertThat(modificationApplicationEntities.stream().map(m -> m.getModification().getId()).toList()).usingRecursiveComparison().isEqualTo(expectedModificationUuids);
322+
assertThat(modificationApplicationInfos.stream().map(ModificationApplicationInfos::getModificationUuid).toList()).usingRecursiveComparison().isEqualTo(expectedModificationUuids);
323+
324+
assertThat(modificationApplicationInfos.stream().map(ModificationApplicationInfos::getGroupUuid).toList()).usingRecursiveComparison().isEqualTo(expectedGroupUuids);
325+
modificationApplicationInfos.forEach(applicationInfo -> assertEquals(newEquipmentId, applicationInfo.getCreatedEquipmentIds().iterator().next()));
326+
}
327+
267328
@Test
268329
void testDeleteModifications() {
269330
/*

0 commit comments

Comments
 (0)