Skip to content

Commit 67ce867

Browse files
Delete result native query (#61)
Signed-off-by: Etienne Homer <[email protected]> Co-authored-by: sBouzols <[email protected]>
1 parent f6add5c commit 67ce867

File tree

10 files changed

+225
-9
lines changed

10 files changed

+225
-9
lines changed

pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
<liquibase-hibernate-package>org.gridsuite.shortcircuit.server</liquibase-hibernate-package>
4747
<mockito-inline.version>3.11.1</mockito-inline.version>
4848
<assertj.version>3.24.2</assertj.version>
49+
<db-util.version>1.0.5</db-util.version>
4950
</properties>
5051

5152
<build>
@@ -201,6 +202,12 @@
201202
<artifactId>mockwebserver</artifactId>
202203
<scope>test</scope>
203204
</dependency>
205+
<dependency>
206+
<groupId>com.vladmihalcea</groupId>
207+
<artifactId>db-util</artifactId>
208+
<version>${db-util.version}</version>
209+
<scope>test</scope>
210+
</dependency>
204211
<dependency>
205212
<groupId>junit</groupId>
206213
<artifactId>junit</artifactId>

src/main/java/org/gridsuite/shortcircuit/server/entities/FaultResultEntity.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@
2121
@Setter
2222
@NoArgsConstructor
2323
@Entity
24-
@Table(indexes = @Index(name = "result_uuid_nbLimitViolations_idx", columnList = "result_result_uuid, nbLimitViolations"))
24+
@Table(indexes = {
25+
@Index(name = "result_uuid_nbLimitViolations_idx", columnList = "result_result_uuid, nbLimitViolations"),
26+
@Index(name = "result_uuid_idx", columnList = "result_result_uuid")
27+
})
2528
public class FaultResultEntity {
2629

2730
@Id

src/main/java/org/gridsuite/shortcircuit/server/repositories/FaultResultRepository.java

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,8 @@
77
package org.gridsuite.shortcircuit.server.repositories;
88

99
import org.gridsuite.shortcircuit.server.entities.FaultResultEntity;
10-
import org.springframework.data.jpa.repository.EntityGraph;
10+
import org.springframework.data.jpa.repository.*;
1111
import org.springframework.data.jpa.repository.EntityGraph.EntityGraphType;
12-
import org.springframework.data.jpa.repository.JpaRepository;
13-
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
1412
import org.springframework.stereotype.Repository;
1513

1614
import java.util.List;
@@ -28,4 +26,24 @@ public interface FaultResultRepository extends JpaRepository<FaultResultEntity,
2826

2927
@EntityGraph(attributePaths = {"feederResults"}, type = EntityGraphType.LOAD)
3028
Set<FaultResultEntity> findAllWithFeederResultsByFaultResultUuidIn(List<UUID> faultResultsUUID);
29+
30+
@Query(value = "SELECT faultResultUuid FROM FaultResultEntity WHERE result.resultUuid = ?1")
31+
Set<UUID> findAllFaultResultUuidsByShortCircuitResultUuid(UUID resultUuid);
32+
33+
// From: https://www.baeldung.com/spring-data-jpa-deleteby
34+
// "The @Query method creates a single SQL query against the database. By comparison, the deleteBy methods execute a read query, then delete each of the items one by one."
35+
// As we need here to delete thousands of fault results, using native SQL query was required for having decent performance.
36+
@Modifying
37+
@Query(value = "DELETE FROM fault_result_entity WHERE result_result_uuid = ?1", nativeQuery = true)
38+
void deleteFaultResultsByShortCircuitResultUUid(UUID resultUuid);
39+
40+
@Modifying
41+
@Query(value = "DELETE FROM limit_violations WHERE fault_result_entity_fault_result_uuid IN ?1", nativeQuery = true)
42+
void deleteLimitViolationsByFaultResultUuids(Set<UUID> ids);
43+
44+
// We keep this method in this repository instead of FeederResultRepository to help readability as it is executed with the two queries above.
45+
@Modifying
46+
@Query(value = "DELETE FROM feeder_results WHERE fault_result_entity_fault_result_uuid IN ?1", nativeQuery = true)
47+
void deleteFeederResultsByFaultResultUuids(Set<UUID> ids);
48+
3149
}

src/main/java/org/gridsuite/shortcircuit/server/repositories/ShortCircuitAnalysisResultRepository.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
import org.gridsuite.shortcircuit.server.entities.*;
1515
import org.gridsuite.shortcircuit.server.utils.FaultResultSpecificationBuilder;
1616
import org.gridsuite.shortcircuit.server.utils.FeederResultSpecificationBuilder;
17+
import org.slf4j.Logger;
18+
import org.slf4j.LoggerFactory;
1719
import org.springframework.beans.factory.annotation.Autowired;
1820
import org.springframework.data.domain.Page;
1921
import org.springframework.data.domain.Pageable;
@@ -25,6 +27,8 @@
2527
import java.time.ZonedDateTime;
2628
import java.time.temporal.ChronoUnit;
2729
import java.util.*;
30+
import java.util.concurrent.TimeUnit;
31+
import java.util.concurrent.atomic.AtomicReference;
2832
import java.util.stream.Collectors;
2933

3034
/**
@@ -33,6 +37,7 @@
3337
@Slf4j
3438
@Repository
3539
public class ShortCircuitAnalysisResultRepository {
40+
private static final Logger LOGGER = LoggerFactory.getLogger(ShortCircuitAnalysisResultRepository.class);
3641
private final GlobalStatusRepository globalStatusRepository;
3742
private final ResultRepository resultRepository;
3843
private final FaultResultRepository faultResultRepository;
@@ -166,8 +171,19 @@ public void insert(UUID resultUuid, ShortCircuitAnalysisResult result, Map<Strin
166171

167172
@Transactional
168173
public void delete(UUID resultUuid) {
174+
AtomicReference<Long> startTime = new AtomicReference<>();
175+
startTime.set(System.nanoTime());
169176
Objects.requireNonNull(resultUuid);
170177
globalStatusRepository.deleteByResultUuid(resultUuid);
178+
deleteShortCircuitResult(resultUuid);
179+
LOGGER.info("Shortcircuit result '{}' has been deleted in {}ms", resultUuid, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime.get()));
180+
}
181+
182+
private void deleteShortCircuitResult(UUID resultUuid) {
183+
Set<UUID> faultResultUuids = faultResultRepository.findAllFaultResultUuidsByShortCircuitResultUuid(resultUuid);
184+
faultResultRepository.deleteFeederResultsByFaultResultUuids(faultResultUuids);
185+
faultResultRepository.deleteLimitViolationsByFaultResultUuids(faultResultUuids);
186+
faultResultRepository.deleteFaultResultsByShortCircuitResultUUid(resultUuid);
171187
resultRepository.deleteByResultUuid(resultUuid);
172188
}
173189

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
2+
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext" xmlns:pro="http://www.liquibase.org/xml/ns/pro" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/pro http://www.liquibase.org/xml/ns/pro/liquibase-pro-latest.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
3+
<changeSet author="homereti" id="1700734364456-1">
4+
<createIndex indexName="result_uuid_idx" tableName="fault_result_entity">
5+
<column name="result_result_uuid"/>
6+
</createIndex>
7+
</changeSet>
8+
</databaseChangeLog>

src/main/resources/db/changelog/db.changelog-master.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,7 @@ databaseChangeLog:
2626
relativeToChangelogFile: true
2727
- include:
2828
file: changesets/changelog_20231004T104042Z.xml
29-
relativeToChangelogFile: true
29+
relativeToChangelogFile: true
30+
- include:
31+
file: changesets/changelog_20231123T101034Z.xml
32+
relativeToChangelogFile: true
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/**
2+
* Copyright (c) 2023, RTE (http://www.rte-france.com)
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
6+
*/
7+
8+
package org.gridsuite.shortcircuit.server;
9+
10+
import net.ttddyy.dsproxy.listener.ChainListener;
11+
import net.ttddyy.dsproxy.listener.DataSourceQueryCountListener;
12+
import net.ttddyy.dsproxy.listener.logging.SLF4JLogLevel;
13+
import net.ttddyy.dsproxy.support.ProxyDataSource;
14+
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
15+
import org.aopalliance.intercept.MethodInterceptor;
16+
import org.aopalliance.intercept.MethodInvocation;
17+
import org.springframework.aop.framework.ProxyFactory;
18+
import org.springframework.beans.factory.config.BeanPostProcessor;
19+
import org.springframework.stereotype.Component;
20+
import org.springframework.util.ReflectionUtils;
21+
22+
import javax.sql.DataSource;
23+
import java.lang.reflect.Method;
24+
25+
@Component
26+
/**
27+
* The author of db-utils describes its library in https://vladmihalcea.com/how-to-detect-the-n-plus-one-query-problem-during-testing/
28+
* But the recommended method to select the datasource (using @bean public DataSource dataSource(DataSource originalDataSource) {...} )
29+
* doesn't work when you use the spring default profile for tests
30+
* It crashes with this exception : java.lang.IllegalStateException: Failed to load ApplicationContext : org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'dataSource': Requested bean is currently in creation: Is there an unresolvable circular reference
31+
* Instead, the underlying datasource-proxy library author recommends to use a BeanPostProcessor:
32+
* https://github.com/ttddyy/datasource-proxy-examples/blob/master/springboot-autoconfig-example/src/main/java/net/ttddyy/dsproxy/example/DatasourceProxyBeanPostProcessor.java
33+
*/
34+
public class DatasourceProxyBeanPostProcessor implements BeanPostProcessor {
35+
@Override
36+
public Object postProcessAfterInitialization(Object bean, String beanName) {
37+
if (bean instanceof DataSource && !(bean instanceof ProxyDataSource)) {
38+
// Instead of directly returning a less specific datasource bean
39+
// (e.g.: HikariDataSource -> DataSource), return a proxy object.
40+
// See following links for why:
41+
// https://stackoverflow.com/questions/44237787/how-to-use-user-defined-database-proxy-in-datajpatest
42+
// https://gitter.im/spring-projects/spring-boot?at=5983602d2723db8d5e70a904
43+
// http://blog.arnoldgalovics.com/2017/06/26/configuring-a-datasource-proxy-in-spring-boot/
44+
final ProxyFactory factory = new ProxyFactory(bean);
45+
factory.setProxyTargetClass(true);
46+
factory.addAdvice(new ProxyDataSourceInterceptor((DataSource) bean));
47+
return factory.getProxy();
48+
}
49+
return bean;
50+
}
51+
52+
@Override
53+
public Object postProcessBeforeInitialization(Object bean, String beanName) {
54+
return bean;
55+
}
56+
57+
private static class ProxyDataSourceInterceptor implements MethodInterceptor {
58+
private final DataSource dataSource;
59+
60+
public ProxyDataSourceInterceptor(final DataSource dataSource) {
61+
ChainListener listener = new ChainListener();
62+
listener.addListener(new DataSourceQueryCountListener());
63+
this.dataSource = ProxyDataSourceBuilder.create(dataSource)
64+
.multiline()
65+
.listener(listener)
66+
.logQueryBySlf4j(SLF4JLogLevel.INFO)
67+
.build();
68+
}
69+
70+
@Override
71+
public Object invoke(final MethodInvocation invocation) throws Throwable {
72+
final Method proxyMethod = ReflectionUtils.findMethod(this.dataSource.getClass(),
73+
invocation.getMethod().getName());
74+
if (proxyMethod != null) {
75+
return proxyMethod.invoke(this.dataSource, invocation.getArguments());
76+
}
77+
return invocation.proceed();
78+
}
79+
}
80+
}

src/test/java/org/gridsuite/shortcircuit/server/ShortCircuitAnalysisControllerTest.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,7 @@
5050

5151
import java.time.LocalDateTime;
5252
import java.time.temporal.ChronoUnit;
53-
import java.util.Collections;
54-
import java.util.Comparator;
55-
import java.util.List;
56-
import java.util.UUID;
53+
import java.util.*;
5754
import java.util.concurrent.CompletableFuture;
5855
import java.util.stream.Collectors;
5956

src/test/java/org/gridsuite/shortcircuit/server/TestUtils.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
import java.lang.reflect.Constructor;
1818
import java.util.List;
1919

20+
import static com.vladmihalcea.sql.SQLStatementCountValidator.*;
21+
import static com.vladmihalcea.sql.SQLStatementCountValidator.assertDeleteCount;
2022
import static org.junit.Assert.assertNull;
2123

2224
/**
@@ -62,4 +64,11 @@ public static MockedStatic<ShortCircuitAnalysis> injectShortCircuitAnalysisProvi
6264
throw ex;
6365
}
6466
}
67+
68+
public static void assertRequestsCount(long select, long insert, long update, long delete) {
69+
assertSelectCount(select);
70+
assertInsertCount(insert);
71+
assertUpdateCount(update);
72+
assertDeleteCount(delete);
73+
}
6574
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/**
2+
* Copyright (c) 2023, RTE (http://www.rte-france.com)
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
6+
*/
7+
8+
package org.gridsuite.shortcircuit.server.repositories;
9+
10+
import com.powsybl.security.LimitViolation;
11+
import com.powsybl.security.LimitViolationType;
12+
import com.powsybl.shortcircuit.*;
13+
import com.vladmihalcea.sql.SQLStatementCountValidator;
14+
import org.junit.Before;
15+
import org.junit.Test;
16+
import org.junit.runner.RunWith;
17+
import org.springframework.beans.factory.annotation.Autowired;
18+
import org.springframework.boot.test.context.SpringBootTest;
19+
import org.springframework.test.context.junit4.SpringRunner;
20+
21+
import java.util.*;
22+
23+
import static org.gridsuite.shortcircuit.server.TestUtils.assertRequestsCount;
24+
25+
/**
26+
* @author Etienne HOMER <[email protected]>
27+
*/
28+
@RunWith(SpringRunner.class)
29+
@SpringBootTest
30+
public class ShortCircuitResultRepositoryTest {
31+
32+
static final FeederResult FEEDER_RESULT_1 = new MagnitudeFeederResult("CONN_ID_1", 22.17);
33+
static final FeederResult FEEDER_RESULT_2 = new MagnitudeFeederResult("CONN_ID_2", 18.57);
34+
static final LimitViolation LIMIT_VIOLATION_1 = new LimitViolation("SUBJECT_1", LimitViolationType.HIGH_SHORT_CIRCUIT_CURRENT, 25.63, 4f, 33.54);
35+
static final LimitViolation LIMIT_VIOLATION_2 = new LimitViolation("SUBJECT_2", LimitViolationType.LOW_SHORT_CIRCUIT_CURRENT, 12.17, 2f, 10.56);
36+
37+
FaultResult fault1 = new MagnitudeFaultResult(new BusFault("VLHV1_0", "ELEMENT_ID_1"), 17.0,
38+
List.of(), List.of(LIMIT_VIOLATION_1, LIMIT_VIOLATION_2),
39+
45.3, FaultResult.Status.SUCCESS);
40+
FaultResult fault2 = new MagnitudeFaultResult(new BusFault("VLHV2_0", "ELEMENT_ID_2"), 18.0,
41+
List.of(FEEDER_RESULT_1), List.of(LIMIT_VIOLATION_1),
42+
47.3, FaultResult.Status.SUCCESS);
43+
FaultResult fault3 = new MagnitudeFaultResult(new BusFault("VLGEN_0", "ELEMENT_ID_3"), 19.0,
44+
List.of(FEEDER_RESULT_1, FEEDER_RESULT_2), List.of(),
45+
49.3, FaultResult.Status.SUCCESS);
46+
47+
private static final UUID RESULT_UUID = UUID.fromString("0c8de370-3e6c-4d72-b292-d355a97e0d5d");
48+
49+
@Autowired
50+
private ShortCircuitAnalysisResultRepository shortCircuitAnalysisResultRepository;
51+
52+
@Before
53+
public void setUp() {
54+
shortCircuitAnalysisResultRepository.deleteAll();
55+
SQLStatementCountValidator.reset();
56+
}
57+
58+
@Test
59+
public void deleteResultTest() {
60+
ShortCircuitAnalysisResult results = new ShortCircuitAnalysisResult(List.of(fault1, fault2, fault3));
61+
shortCircuitAnalysisResultRepository.insert(RESULT_UUID, results, Map.of(), "OK");
62+
SQLStatementCountValidator.reset();
63+
64+
shortCircuitAnalysisResultRepository.delete(RESULT_UUID);
65+
66+
// 5 deletes for one result :
67+
// - its global status,
68+
// - all its limitVioltions
69+
// - all its feeders
70+
// - all its faultResults
71+
// - the result itself
72+
assertRequestsCount(4, 0, 0, 5);
73+
}
74+
75+
}

0 commit comments

Comments
 (0)