From 7fd7f0fbbf1fe7c9103634a3e3d08648f8d1e7ad Mon Sep 17 00:00:00 2001 From: "Pascal ABAZIOU [532dadac-2832-40c7-b4e5-933dfeff4687]" Date: Wed, 5 Apr 2023 17:56:18 +0200 Subject: [PATCH 01/14] =?UTF-8?q?N+!=20Query=20problem=20:=20controle=20de?= =?UTF-8?q?=20pr=C3=A9sence=20des=20annotations=20Spring=20Query=20et=20En?= =?UTF-8?q?tityGraph?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AvoidNPlusOneQueryProblemCheck.java | 73 +++++++++++++++++++ .../files/AvoidNPlusOneQueryProblemBad.java | 15 ++++ .../files/AvoidNPlusOneQueryProblemGood.java | 27 +++++++ ...NPlusOneQueryProblemNotRepositoryGood.java | 17 +++++ .../AvoidOnePlusOneQueryProblemTest.java | 39 ++++++++++ 5 files changed, 171 insertions(+) create mode 100644 java-plugin/src/main/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemCheck.java create mode 100644 java-plugin/src/test/files/AvoidNPlusOneQueryProblemBad.java create mode 100644 java-plugin/src/test/files/AvoidNPlusOneQueryProblemGood.java create mode 100644 java-plugin/src/test/files/AvoidNPlusOneQueryProblemNotRepositoryGood.java create mode 100644 java-plugin/src/test/java/fr/greencodeinitiative/java/checks/AvoidOnePlusOneQueryProblemTest.java diff --git a/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemCheck.java b/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemCheck.java new file mode 100644 index 000000000..dfd03b97a --- /dev/null +++ b/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemCheck.java @@ -0,0 +1,73 @@ +package fr.greencodeinitiative.java.checks; + +import java.util.Arrays; +import java.util.List; + +import org.sonar.check.Priority; +import org.sonar.check.Rule; +import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; +import org.sonar.plugins.java.api.tree.*; + +@Rule(key = "EC206", + name = "Developpement", + description = AvoidNPlusOneQueryProblemCheck.RULE_MESSAGE, + priority = Priority.MINOR, + tags = {"bug"}) +public class AvoidNPlusOneQueryProblemCheck extends IssuableSubscriptionVisitor { + + protected static final String RULE_MESSAGE = "Avoid N+1 Query problem"; + + private static final String SPRING_REPOSITORY = "org.springframework.data.repository.Repository"; + + private final AvoidSpringRepositoryCallInLoopCheckVisitor visitorInFile = new AvoidSpringRepositoryCallInLoopCheckVisitor(); + + @Override + public List nodesToVisit() { + return Arrays.asList(Tree.Kind.INTERFACE); + } + + @Override + public void visitNode(Tree tree) { + if (((ClassTree) tree).symbol().type().isSubtypeOf(SPRING_REPOSITORY)) { + tree.accept(visitorInFile); + } + } + + private class AvoidSpringRepositoryCallInLoopCheckVisitor extends BaseTreeVisitor { + + + @Override + public void visitMethod(MethodTree tree) { + + if ( + tree.returnType().symbolType().isSubtypeOf(Iterable.class.getName()) + && hasNoCompliantAnnotation(tree) + ) { + reportIssue(tree, RULE_MESSAGE); + } else { + super.visitMethod(tree); + } + + + } + + boolean hasNoCompliantAnnotation(MethodTree tree) { + return tree.modifiers().annotations().stream().noneMatch( + a -> isQueryAnnotationWithFetch(a) || + a.symbolType().is("EntityGraph") + ); + } + + private boolean isQueryAnnotationWithFetch(AnnotationTree a) { + return a.symbolType().is("Query") + // kind = ASSIGNMENT Value + // variable = value + // children[2] = HQL + + // Kind = String LITERAL + // Children[0] = HQL + ; + } + + } +} diff --git a/java-plugin/src/test/files/AvoidNPlusOneQueryProblemBad.java b/java-plugin/src/test/files/AvoidNPlusOneQueryProblemBad.java new file mode 100644 index 000000000..49f12705d --- /dev/null +++ b/java-plugin/src/test/files/AvoidNPlusOneQueryProblemBad.java @@ -0,0 +1,15 @@ +import org.springframework.data.repository.CrudRepository; +import java.util.List; + +public interface UserRepository extends CrudRepository { + + List findAll(); // Noncompliant {{Avoid N+1 Query problem}} + + @Override // Noncompliant {{Avoid N+1 Query problem}} + List findByIds(); + +} + +public class User { + +} diff --git a/java-plugin/src/test/files/AvoidNPlusOneQueryProblemGood.java b/java-plugin/src/test/files/AvoidNPlusOneQueryProblemGood.java new file mode 100644 index 000000000..f9b46a9f8 --- /dev/null +++ b/java-plugin/src/test/files/AvoidNPlusOneQueryProblemGood.java @@ -0,0 +1,27 @@ +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.jpa.repository.EntityGraph; +import java.util.List; +public interface UserRepository extends CrudRepository { + + + User findById(); + + @Query("SELECT p FROM User p LEFT JOIN FETCH p.roles") + List findWithoutNPlusOne(); + + @EntityGraph(attributePaths = {"roles"}) + List findAll(); + @Query(value = "SELECT p FROM User p LEFT JOIN FETCH p.roles", nativeQuery = false) + @Override + List findAllWithRoles(); + + @Query("SELECT p FROM User p LEFT JOIN FETCH p.roles") + @Override + List findAllWithAdminRoles(); + +} + +public class User { + +} diff --git a/java-plugin/src/test/files/AvoidNPlusOneQueryProblemNotRepositoryGood.java b/java-plugin/src/test/files/AvoidNPlusOneQueryProblemNotRepositoryGood.java new file mode 100644 index 000000000..624d20c5a --- /dev/null +++ b/java-plugin/src/test/files/AvoidNPlusOneQueryProblemNotRepositoryGood.java @@ -0,0 +1,17 @@ +import org.springframework.data.repository.CrudRepository; +import java.util.List; +public interface UserRepository extends User { + + User findById(); + + List findWithoutNPlusOne(); + + List findAll(); + @Override + List findAllWithRoles(); + +} + +public class User { + +} diff --git a/java-plugin/src/test/java/fr/greencodeinitiative/java/checks/AvoidOnePlusOneQueryProblemTest.java b/java-plugin/src/test/java/fr/greencodeinitiative/java/checks/AvoidOnePlusOneQueryProblemTest.java new file mode 100644 index 000000000..85400b201 --- /dev/null +++ b/java-plugin/src/test/java/fr/greencodeinitiative/java/checks/AvoidOnePlusOneQueryProblemTest.java @@ -0,0 +1,39 @@ +package fr.greencodeinitiative.java.checks; + +import fr.greencodeinitiative.java.utils.FilesUtils; +import org.junit.jupiter.api.Test; +import org.sonar.java.checks.verifier.CheckVerifier; + +class AvoidOnePlusOneQueryProblemTest { + + /** + * @formatter:off + */ + @Test + void testHasIssues() { + CheckVerifier.newVerifier() + .onFile("src/test/files/AvoidNPlusOneQueryProblemBad.java") + .withCheck(new AvoidNPlusOneQueryProblemCheck()) + .withClassPath(FilesUtils.getClassPath("target/test-jars")) + .verifyIssues(); + } + + @Test + void testHasNoIssue() { + CheckVerifier.newVerifier() + .onFile("src/test/files/AvoidNPlusOneQueryProblemGood.java") + .withCheck(new AvoidNPlusOneQueryProblemCheck()) + .withClassPath(FilesUtils.getClassPath("target/test-jars")) + .verifyNoIssues(); + } + + @Test + void testHasNoIssueWhenNotRepository() { + CheckVerifier.newVerifier() + .onFile("src/test/files/AvoidNPlusOneQueryProblemNotRepositoryGood.java") + .withCheck(new AvoidNPlusOneQueryProblemCheck()) + .withClassPath(FilesUtils.getClassPath("target/test-jars")) + .verifyNoIssues(); + } + +} From 6d7045852493dc177f8a03c3c26bd54352ee8343 Mon Sep 17 00:00:00 2001 From: MaximeAT Date: Thu, 6 Apr 2023 10:07:01 +0200 Subject: [PATCH 02/14] isQueryAnnotationWithFetch --- .../greencodeinitiative/java/RulesList.java | 4 ++- .../AvoidNPlusOneQueryProblemCheck.java | 29 +++++++++---------- .../files/AvoidNPlusOneQueryProblemBad.java | 2 ++ 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/java-plugin/src/main/java/fr/greencodeinitiative/java/RulesList.java b/java-plugin/src/main/java/fr/greencodeinitiative/java/RulesList.java index d6a5cdbf4..f5a7b43b8 100644 --- a/java-plugin/src/main/java/fr/greencodeinitiative/java/RulesList.java +++ b/java-plugin/src/main/java/fr/greencodeinitiative/java/RulesList.java @@ -29,6 +29,7 @@ import fr.greencodeinitiative.java.checks.AvoidFullSQLRequest; import fr.greencodeinitiative.java.checks.AvoidGettingSizeCollectionInLoop; import fr.greencodeinitiative.java.checks.AvoidMultipleIfElseStatement; +import fr.greencodeinitiative.java.checks.AvoidNPlusOneQueryProblemCheck; import fr.greencodeinitiative.java.checks.AvoidRegexPatternNotStatic; import fr.greencodeinitiative.java.checks.AvoidSQLRequestInLoop; import fr.greencodeinitiative.java.checks.AvoidSetConstantInBatchUpdate; @@ -77,7 +78,8 @@ public static List> getJavaChecks() { AvoidUsingGlobalVariablesCheck.class, AvoidSetConstantInBatchUpdate.class, FreeResourcesOfAutoCloseableInterface.class, - AvoidMultipleIfElseStatement.class + AvoidMultipleIfElseStatement.class, + AvoidNPlusOneQueryProblemCheck.class )); } diff --git a/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemCheck.java b/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemCheck.java index dfd03b97a..71c2b1b29 100644 --- a/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemCheck.java +++ b/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemCheck.java @@ -18,8 +18,12 @@ public class AvoidNPlusOneQueryProblemCheck extends IssuableSubscriptionVisitor protected static final String RULE_MESSAGE = "Avoid N+1 Query problem"; private static final String SPRING_REPOSITORY = "org.springframework.data.repository.Repository"; + private static final String QUERY = "org.springframework.data.jpa.repository.Query"; + private static final String ENTITY_GRAPH = "org.springframework.data.jpa.repository.EntityGraph"; + private static final String LEFT_JOIN = "LEFT JOIN"; + private static final String VALUE = "value"; - private final AvoidSpringRepositoryCallInLoopCheckVisitor visitorInFile = new AvoidSpringRepositoryCallInLoopCheckVisitor(); + private final AvoidNPlusOneQueryProblemCheckVisitor visitorInFile = new AvoidNPlusOneQueryProblemCheckVisitor(); @Override public List nodesToVisit() { @@ -33,12 +37,10 @@ public void visitNode(Tree tree) { } } - private class AvoidSpringRepositoryCallInLoopCheckVisitor extends BaseTreeVisitor { - + private class AvoidNPlusOneQueryProblemCheckVisitor extends BaseTreeVisitor { @Override public void visitMethod(MethodTree tree) { - if ( tree.returnType().symbolType().isSubtypeOf(Iterable.class.getName()) && hasNoCompliantAnnotation(tree) @@ -47,27 +49,22 @@ && hasNoCompliantAnnotation(tree) } else { super.visitMethod(tree); } - - } boolean hasNoCompliantAnnotation(MethodTree tree) { return tree.modifiers().annotations().stream().noneMatch( a -> isQueryAnnotationWithFetch(a) || - a.symbolType().is("EntityGraph") + a.symbolType().is(ENTITY_GRAPH) ); } private boolean isQueryAnnotationWithFetch(AnnotationTree a) { - return a.symbolType().is("Query") - // kind = ASSIGNMENT Value - // variable = value - // children[2] = HQL - - // Kind = String LITERAL - // Children[0] = HQL - ; + return a.symbolType().is(QUERY) + && (a.arguments().stream().filter(arg -> Tree.Kind.STRING_LITERAL.equals(arg.kind())) + .anyMatch(arg -> arg.firstToken().text().contains(LEFT_JOIN)) + || (a.arguments().stream().filter(arg -> Tree.Kind.ASSIGNMENT.equals(arg.kind())) + .filter(arg -> VALUE.equals(arg.firstToken().text())) + .anyMatch(arg -> arg.lastToken().text().contains(LEFT_JOIN)))); } - } } diff --git a/java-plugin/src/test/files/AvoidNPlusOneQueryProblemBad.java b/java-plugin/src/test/files/AvoidNPlusOneQueryProblemBad.java index 49f12705d..61e55d89f 100644 --- a/java-plugin/src/test/files/AvoidNPlusOneQueryProblemBad.java +++ b/java-plugin/src/test/files/AvoidNPlusOneQueryProblemBad.java @@ -8,6 +8,8 @@ public interface UserRepository extends CrudRepository { @Override // Noncompliant {{Avoid N+1 Query problem}} List findByIds(); + @Query(value = "SELECT p FROM User p LEFT JOIN p.roles", nativeQuery = false) // Noncompliant {{Avoid N+1 Query problem}} + List findAllWithRoles(); } public class User { From 029b379fe0b1db9f1ba4a2a1e690604ae199793c Mon Sep 17 00:00:00 2001 From: "Samuel MARTIN [aa6a131a-f6b6-40af-9503-007c1ae129a9]" Date: Thu, 6 Apr 2023 11:26:43 +0200 Subject: [PATCH 03/14] =?UTF-8?q?feat:=20D=C3=A9tection=20des=20annotation?= =?UTF-8?q?s=20ManyToOne,=20OneToMany=20et=20ManyToMany=20dans=20l'entit?= =?UTF-8?q?=C3=A9=20li=C3=A9e=20au=20Repository?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AvoidNPlusOneQueryProblemCheck.java | 41 ++++++++++++++++++- .../files/AvoidNPlusOneQueryProblemBad.java | 27 ++++++++++++ ...ava => AvoidNPlusOneQueryProblemTest.java} | 2 +- 3 files changed, 68 insertions(+), 2 deletions(-) rename java-plugin/src/test/java/fr/greencodeinitiative/java/checks/{AvoidOnePlusOneQueryProblemTest.java => AvoidNPlusOneQueryProblemTest.java} (96%) diff --git a/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemCheck.java b/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemCheck.java index 71c2b1b29..70e51f052 100644 --- a/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemCheck.java +++ b/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemCheck.java @@ -2,10 +2,15 @@ import java.util.Arrays; import java.util.List; +import java.util.Optional; +import java.util.Optional; + import org.sonar.check.Priority; import org.sonar.check.Rule; import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; +import org.sonar.plugins.java.api.semantic.Type; +import org.sonar.plugins.java.api.semantic.Type; import org.sonar.plugins.java.api.tree.*; @Rule(key = "EC206", @@ -23,6 +28,12 @@ public class AvoidNPlusOneQueryProblemCheck extends IssuableSubscriptionVisitor private static final String LEFT_JOIN = "LEFT JOIN"; private static final String VALUE = "value"; + private static final List SPRING_PROBLEMATIC_ANNOTATIONS = List.of( + "javax.persistence.OneToMany", + "javax.persistence.ManyToOne", + "javax.persistence.ManyToMany" + ); + private final AvoidNPlusOneQueryProblemCheckVisitor visitorInFile = new AvoidNPlusOneQueryProblemCheckVisitor(); @Override @@ -32,11 +43,39 @@ public List nodesToVisit() { @Override public void visitNode(Tree tree) { - if (((ClassTree) tree).symbol().type().isSubtypeOf(SPRING_REPOSITORY)) { + ClassTree classTree = (ClassTree) tree; + + if (isSpringRepository(classTree) && hasManyToOneAnnotations(classTree)) { tree.accept(visitorInFile); } } + private boolean hasManyToOneAnnotations(ClassTree classTree) { + Optional crudRepositoryInterface = classTree.symbol().interfaces().stream() + .filter(t -> t.isSubtypeOf(SPRING_REPOSITORY)) + .findFirst(); + + return crudRepositoryInterface.map(type -> type + .typeArguments() + .get(0) + .symbol() + .declaration() + .members() + .stream() + .filter(t -> t.is(Tree.Kind.VARIABLE)) + .anyMatch(t -> ((VariableTree) t).modifiers() + .annotations() + .stream() + .anyMatch(a -> + SPRING_PROBLEMATIC_ANNOTATIONS.stream().anyMatch(a.symbolType()::isSubtypeOf) + ))) + .orElse(false); + } + + private static boolean isSpringRepository(ClassTree classTree) { + return classTree.symbol().type().isSubtypeOf(SPRING_REPOSITORY); + } + private class AvoidNPlusOneQueryProblemCheckVisitor extends BaseTreeVisitor { @Override diff --git a/java-plugin/src/test/files/AvoidNPlusOneQueryProblemBad.java b/java-plugin/src/test/files/AvoidNPlusOneQueryProblemBad.java index 61e55d89f..3302aa957 100644 --- a/java-plugin/src/test/files/AvoidNPlusOneQueryProblemBad.java +++ b/java-plugin/src/test/files/AvoidNPlusOneQueryProblemBad.java @@ -1,5 +1,7 @@ import org.springframework.data.repository.CrudRepository; import java.util.List; +import javax.persistence.Entity; +import javax.persistence.OneToMany; public interface UserRepository extends CrudRepository { @@ -12,6 +14,31 @@ public interface UserRepository extends CrudRepository { List findAllWithRoles(); } +@Entity public class User { + @OneToMany + private List roles; + + public List getRoles() { + return roles; + } + + public void setRoles(List roles) { + this.roles = roles; + } +} + +@Entity +public class Role { + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } } diff --git a/java-plugin/src/test/java/fr/greencodeinitiative/java/checks/AvoidOnePlusOneQueryProblemTest.java b/java-plugin/src/test/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemTest.java similarity index 96% rename from java-plugin/src/test/java/fr/greencodeinitiative/java/checks/AvoidOnePlusOneQueryProblemTest.java rename to java-plugin/src/test/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemTest.java index 85400b201..e8374783d 100644 --- a/java-plugin/src/test/java/fr/greencodeinitiative/java/checks/AvoidOnePlusOneQueryProblemTest.java +++ b/java-plugin/src/test/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemTest.java @@ -4,7 +4,7 @@ import org.junit.jupiter.api.Test; import org.sonar.java.checks.verifier.CheckVerifier; -class AvoidOnePlusOneQueryProblemTest { +class AvoidNPlusOneQueryProblemTest { /** * @formatter:off From 0c2726093b3632ac2e9fdc11791579932f45af70 Mon Sep 17 00:00:00 2001 From: javathought Date: Wed, 5 Apr 2023 17:56:18 +0200 Subject: [PATCH 04/14] =?UTF-8?q?N+!=20Query=20problem=20:=20controle=20de?= =?UTF-8?q?=20pr=C3=A9sence=20des=20annotations=20Spring=20Query=20et=20En?= =?UTF-8?q?tityGraph?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AvoidNPlusOneQueryProblemCheck.java | 73 +++++++++++++++++++ .../files/AvoidNPlusOneQueryProblemBad.java | 15 ++++ .../files/AvoidNPlusOneQueryProblemGood.java | 27 +++++++ ...NPlusOneQueryProblemNotRepositoryGood.java | 17 +++++ .../AvoidOnePlusOneQueryProblemTest.java | 39 ++++++++++ 5 files changed, 171 insertions(+) create mode 100644 java-plugin/src/main/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemCheck.java create mode 100644 java-plugin/src/test/files/AvoidNPlusOneQueryProblemBad.java create mode 100644 java-plugin/src/test/files/AvoidNPlusOneQueryProblemGood.java create mode 100644 java-plugin/src/test/files/AvoidNPlusOneQueryProblemNotRepositoryGood.java create mode 100644 java-plugin/src/test/java/fr/greencodeinitiative/java/checks/AvoidOnePlusOneQueryProblemTest.java diff --git a/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemCheck.java b/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemCheck.java new file mode 100644 index 000000000..dfd03b97a --- /dev/null +++ b/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemCheck.java @@ -0,0 +1,73 @@ +package fr.greencodeinitiative.java.checks; + +import java.util.Arrays; +import java.util.List; + +import org.sonar.check.Priority; +import org.sonar.check.Rule; +import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; +import org.sonar.plugins.java.api.tree.*; + +@Rule(key = "EC206", + name = "Developpement", + description = AvoidNPlusOneQueryProblemCheck.RULE_MESSAGE, + priority = Priority.MINOR, + tags = {"bug"}) +public class AvoidNPlusOneQueryProblemCheck extends IssuableSubscriptionVisitor { + + protected static final String RULE_MESSAGE = "Avoid N+1 Query problem"; + + private static final String SPRING_REPOSITORY = "org.springframework.data.repository.Repository"; + + private final AvoidSpringRepositoryCallInLoopCheckVisitor visitorInFile = new AvoidSpringRepositoryCallInLoopCheckVisitor(); + + @Override + public List nodesToVisit() { + return Arrays.asList(Tree.Kind.INTERFACE); + } + + @Override + public void visitNode(Tree tree) { + if (((ClassTree) tree).symbol().type().isSubtypeOf(SPRING_REPOSITORY)) { + tree.accept(visitorInFile); + } + } + + private class AvoidSpringRepositoryCallInLoopCheckVisitor extends BaseTreeVisitor { + + + @Override + public void visitMethod(MethodTree tree) { + + if ( + tree.returnType().symbolType().isSubtypeOf(Iterable.class.getName()) + && hasNoCompliantAnnotation(tree) + ) { + reportIssue(tree, RULE_MESSAGE); + } else { + super.visitMethod(tree); + } + + + } + + boolean hasNoCompliantAnnotation(MethodTree tree) { + return tree.modifiers().annotations().stream().noneMatch( + a -> isQueryAnnotationWithFetch(a) || + a.symbolType().is("EntityGraph") + ); + } + + private boolean isQueryAnnotationWithFetch(AnnotationTree a) { + return a.symbolType().is("Query") + // kind = ASSIGNMENT Value + // variable = value + // children[2] = HQL + + // Kind = String LITERAL + // Children[0] = HQL + ; + } + + } +} diff --git a/java-plugin/src/test/files/AvoidNPlusOneQueryProblemBad.java b/java-plugin/src/test/files/AvoidNPlusOneQueryProblemBad.java new file mode 100644 index 000000000..49f12705d --- /dev/null +++ b/java-plugin/src/test/files/AvoidNPlusOneQueryProblemBad.java @@ -0,0 +1,15 @@ +import org.springframework.data.repository.CrudRepository; +import java.util.List; + +public interface UserRepository extends CrudRepository { + + List findAll(); // Noncompliant {{Avoid N+1 Query problem}} + + @Override // Noncompliant {{Avoid N+1 Query problem}} + List findByIds(); + +} + +public class User { + +} diff --git a/java-plugin/src/test/files/AvoidNPlusOneQueryProblemGood.java b/java-plugin/src/test/files/AvoidNPlusOneQueryProblemGood.java new file mode 100644 index 000000000..f9b46a9f8 --- /dev/null +++ b/java-plugin/src/test/files/AvoidNPlusOneQueryProblemGood.java @@ -0,0 +1,27 @@ +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.jpa.repository.EntityGraph; +import java.util.List; +public interface UserRepository extends CrudRepository { + + + User findById(); + + @Query("SELECT p FROM User p LEFT JOIN FETCH p.roles") + List findWithoutNPlusOne(); + + @EntityGraph(attributePaths = {"roles"}) + List findAll(); + @Query(value = "SELECT p FROM User p LEFT JOIN FETCH p.roles", nativeQuery = false) + @Override + List findAllWithRoles(); + + @Query("SELECT p FROM User p LEFT JOIN FETCH p.roles") + @Override + List findAllWithAdminRoles(); + +} + +public class User { + +} diff --git a/java-plugin/src/test/files/AvoidNPlusOneQueryProblemNotRepositoryGood.java b/java-plugin/src/test/files/AvoidNPlusOneQueryProblemNotRepositoryGood.java new file mode 100644 index 000000000..624d20c5a --- /dev/null +++ b/java-plugin/src/test/files/AvoidNPlusOneQueryProblemNotRepositoryGood.java @@ -0,0 +1,17 @@ +import org.springframework.data.repository.CrudRepository; +import java.util.List; +public interface UserRepository extends User { + + User findById(); + + List findWithoutNPlusOne(); + + List findAll(); + @Override + List findAllWithRoles(); + +} + +public class User { + +} diff --git a/java-plugin/src/test/java/fr/greencodeinitiative/java/checks/AvoidOnePlusOneQueryProblemTest.java b/java-plugin/src/test/java/fr/greencodeinitiative/java/checks/AvoidOnePlusOneQueryProblemTest.java new file mode 100644 index 000000000..85400b201 --- /dev/null +++ b/java-plugin/src/test/java/fr/greencodeinitiative/java/checks/AvoidOnePlusOneQueryProblemTest.java @@ -0,0 +1,39 @@ +package fr.greencodeinitiative.java.checks; + +import fr.greencodeinitiative.java.utils.FilesUtils; +import org.junit.jupiter.api.Test; +import org.sonar.java.checks.verifier.CheckVerifier; + +class AvoidOnePlusOneQueryProblemTest { + + /** + * @formatter:off + */ + @Test + void testHasIssues() { + CheckVerifier.newVerifier() + .onFile("src/test/files/AvoidNPlusOneQueryProblemBad.java") + .withCheck(new AvoidNPlusOneQueryProblemCheck()) + .withClassPath(FilesUtils.getClassPath("target/test-jars")) + .verifyIssues(); + } + + @Test + void testHasNoIssue() { + CheckVerifier.newVerifier() + .onFile("src/test/files/AvoidNPlusOneQueryProblemGood.java") + .withCheck(new AvoidNPlusOneQueryProblemCheck()) + .withClassPath(FilesUtils.getClassPath("target/test-jars")) + .verifyNoIssues(); + } + + @Test + void testHasNoIssueWhenNotRepository() { + CheckVerifier.newVerifier() + .onFile("src/test/files/AvoidNPlusOneQueryProblemNotRepositoryGood.java") + .withCheck(new AvoidNPlusOneQueryProblemCheck()) + .withClassPath(FilesUtils.getClassPath("target/test-jars")) + .verifyNoIssues(); + } + +} From 3472481e77ab4538b5c171b7d8ed27f90f5dab0d Mon Sep 17 00:00:00 2001 From: MaximeAT Date: Thu, 6 Apr 2023 10:07:01 +0200 Subject: [PATCH 05/14] isQueryAnnotationWithFetch --- .../greencodeinitiative/java/RulesList.java | 4 ++- .../AvoidNPlusOneQueryProblemCheck.java | 29 +++++++++---------- .../files/AvoidNPlusOneQueryProblemBad.java | 2 ++ 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/java-plugin/src/main/java/fr/greencodeinitiative/java/RulesList.java b/java-plugin/src/main/java/fr/greencodeinitiative/java/RulesList.java index d6a5cdbf4..f5a7b43b8 100644 --- a/java-plugin/src/main/java/fr/greencodeinitiative/java/RulesList.java +++ b/java-plugin/src/main/java/fr/greencodeinitiative/java/RulesList.java @@ -29,6 +29,7 @@ import fr.greencodeinitiative.java.checks.AvoidFullSQLRequest; import fr.greencodeinitiative.java.checks.AvoidGettingSizeCollectionInLoop; import fr.greencodeinitiative.java.checks.AvoidMultipleIfElseStatement; +import fr.greencodeinitiative.java.checks.AvoidNPlusOneQueryProblemCheck; import fr.greencodeinitiative.java.checks.AvoidRegexPatternNotStatic; import fr.greencodeinitiative.java.checks.AvoidSQLRequestInLoop; import fr.greencodeinitiative.java.checks.AvoidSetConstantInBatchUpdate; @@ -77,7 +78,8 @@ public static List> getJavaChecks() { AvoidUsingGlobalVariablesCheck.class, AvoidSetConstantInBatchUpdate.class, FreeResourcesOfAutoCloseableInterface.class, - AvoidMultipleIfElseStatement.class + AvoidMultipleIfElseStatement.class, + AvoidNPlusOneQueryProblemCheck.class )); } diff --git a/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemCheck.java b/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemCheck.java index dfd03b97a..71c2b1b29 100644 --- a/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemCheck.java +++ b/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemCheck.java @@ -18,8 +18,12 @@ public class AvoidNPlusOneQueryProblemCheck extends IssuableSubscriptionVisitor protected static final String RULE_MESSAGE = "Avoid N+1 Query problem"; private static final String SPRING_REPOSITORY = "org.springframework.data.repository.Repository"; + private static final String QUERY = "org.springframework.data.jpa.repository.Query"; + private static final String ENTITY_GRAPH = "org.springframework.data.jpa.repository.EntityGraph"; + private static final String LEFT_JOIN = "LEFT JOIN"; + private static final String VALUE = "value"; - private final AvoidSpringRepositoryCallInLoopCheckVisitor visitorInFile = new AvoidSpringRepositoryCallInLoopCheckVisitor(); + private final AvoidNPlusOneQueryProblemCheckVisitor visitorInFile = new AvoidNPlusOneQueryProblemCheckVisitor(); @Override public List nodesToVisit() { @@ -33,12 +37,10 @@ public void visitNode(Tree tree) { } } - private class AvoidSpringRepositoryCallInLoopCheckVisitor extends BaseTreeVisitor { - + private class AvoidNPlusOneQueryProblemCheckVisitor extends BaseTreeVisitor { @Override public void visitMethod(MethodTree tree) { - if ( tree.returnType().symbolType().isSubtypeOf(Iterable.class.getName()) && hasNoCompliantAnnotation(tree) @@ -47,27 +49,22 @@ && hasNoCompliantAnnotation(tree) } else { super.visitMethod(tree); } - - } boolean hasNoCompliantAnnotation(MethodTree tree) { return tree.modifiers().annotations().stream().noneMatch( a -> isQueryAnnotationWithFetch(a) || - a.symbolType().is("EntityGraph") + a.symbolType().is(ENTITY_GRAPH) ); } private boolean isQueryAnnotationWithFetch(AnnotationTree a) { - return a.symbolType().is("Query") - // kind = ASSIGNMENT Value - // variable = value - // children[2] = HQL - - // Kind = String LITERAL - // Children[0] = HQL - ; + return a.symbolType().is(QUERY) + && (a.arguments().stream().filter(arg -> Tree.Kind.STRING_LITERAL.equals(arg.kind())) + .anyMatch(arg -> arg.firstToken().text().contains(LEFT_JOIN)) + || (a.arguments().stream().filter(arg -> Tree.Kind.ASSIGNMENT.equals(arg.kind())) + .filter(arg -> VALUE.equals(arg.firstToken().text())) + .anyMatch(arg -> arg.lastToken().text().contains(LEFT_JOIN)))); } - } } diff --git a/java-plugin/src/test/files/AvoidNPlusOneQueryProblemBad.java b/java-plugin/src/test/files/AvoidNPlusOneQueryProblemBad.java index 49f12705d..61e55d89f 100644 --- a/java-plugin/src/test/files/AvoidNPlusOneQueryProblemBad.java +++ b/java-plugin/src/test/files/AvoidNPlusOneQueryProblemBad.java @@ -8,6 +8,8 @@ public interface UserRepository extends CrudRepository { @Override // Noncompliant {{Avoid N+1 Query problem}} List findByIds(); + @Query(value = "SELECT p FROM User p LEFT JOIN p.roles", nativeQuery = false) // Noncompliant {{Avoid N+1 Query problem}} + List findAllWithRoles(); } public class User { From 02e4a6dd3830dab86a66cb60a6b277e87efdc3ab Mon Sep 17 00:00:00 2001 From: "Samuel MARTIN [aa6a131a-f6b6-40af-9503-007c1ae129a9]" Date: Thu, 6 Apr 2023 11:26:43 +0200 Subject: [PATCH 06/14] =?UTF-8?q?feat:=20D=C3=A9tection=20des=20annotation?= =?UTF-8?q?s=20ManyToOne,=20OneToMany=20et=20ManyToMany=20dans=20l'entit?= =?UTF-8?q?=C3=A9=20li=C3=A9e=20au=20Repository?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AvoidNPlusOneQueryProblemCheck.java | 41 ++++++++++++++++++- .../files/AvoidNPlusOneQueryProblemBad.java | 27 ++++++++++++ ...ava => AvoidNPlusOneQueryProblemTest.java} | 2 +- 3 files changed, 68 insertions(+), 2 deletions(-) rename java-plugin/src/test/java/fr/greencodeinitiative/java/checks/{AvoidOnePlusOneQueryProblemTest.java => AvoidNPlusOneQueryProblemTest.java} (96%) diff --git a/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemCheck.java b/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemCheck.java index 71c2b1b29..70e51f052 100644 --- a/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemCheck.java +++ b/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemCheck.java @@ -2,10 +2,15 @@ import java.util.Arrays; import java.util.List; +import java.util.Optional; +import java.util.Optional; + import org.sonar.check.Priority; import org.sonar.check.Rule; import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; +import org.sonar.plugins.java.api.semantic.Type; +import org.sonar.plugins.java.api.semantic.Type; import org.sonar.plugins.java.api.tree.*; @Rule(key = "EC206", @@ -23,6 +28,12 @@ public class AvoidNPlusOneQueryProblemCheck extends IssuableSubscriptionVisitor private static final String LEFT_JOIN = "LEFT JOIN"; private static final String VALUE = "value"; + private static final List SPRING_PROBLEMATIC_ANNOTATIONS = List.of( + "javax.persistence.OneToMany", + "javax.persistence.ManyToOne", + "javax.persistence.ManyToMany" + ); + private final AvoidNPlusOneQueryProblemCheckVisitor visitorInFile = new AvoidNPlusOneQueryProblemCheckVisitor(); @Override @@ -32,11 +43,39 @@ public List nodesToVisit() { @Override public void visitNode(Tree tree) { - if (((ClassTree) tree).symbol().type().isSubtypeOf(SPRING_REPOSITORY)) { + ClassTree classTree = (ClassTree) tree; + + if (isSpringRepository(classTree) && hasManyToOneAnnotations(classTree)) { tree.accept(visitorInFile); } } + private boolean hasManyToOneAnnotations(ClassTree classTree) { + Optional crudRepositoryInterface = classTree.symbol().interfaces().stream() + .filter(t -> t.isSubtypeOf(SPRING_REPOSITORY)) + .findFirst(); + + return crudRepositoryInterface.map(type -> type + .typeArguments() + .get(0) + .symbol() + .declaration() + .members() + .stream() + .filter(t -> t.is(Tree.Kind.VARIABLE)) + .anyMatch(t -> ((VariableTree) t).modifiers() + .annotations() + .stream() + .anyMatch(a -> + SPRING_PROBLEMATIC_ANNOTATIONS.stream().anyMatch(a.symbolType()::isSubtypeOf) + ))) + .orElse(false); + } + + private static boolean isSpringRepository(ClassTree classTree) { + return classTree.symbol().type().isSubtypeOf(SPRING_REPOSITORY); + } + private class AvoidNPlusOneQueryProblemCheckVisitor extends BaseTreeVisitor { @Override diff --git a/java-plugin/src/test/files/AvoidNPlusOneQueryProblemBad.java b/java-plugin/src/test/files/AvoidNPlusOneQueryProblemBad.java index 61e55d89f..3302aa957 100644 --- a/java-plugin/src/test/files/AvoidNPlusOneQueryProblemBad.java +++ b/java-plugin/src/test/files/AvoidNPlusOneQueryProblemBad.java @@ -1,5 +1,7 @@ import org.springframework.data.repository.CrudRepository; import java.util.List; +import javax.persistence.Entity; +import javax.persistence.OneToMany; public interface UserRepository extends CrudRepository { @@ -12,6 +14,31 @@ public interface UserRepository extends CrudRepository { List findAllWithRoles(); } +@Entity public class User { + @OneToMany + private List roles; + + public List getRoles() { + return roles; + } + + public void setRoles(List roles) { + this.roles = roles; + } +} + +@Entity +public class Role { + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } } diff --git a/java-plugin/src/test/java/fr/greencodeinitiative/java/checks/AvoidOnePlusOneQueryProblemTest.java b/java-plugin/src/test/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemTest.java similarity index 96% rename from java-plugin/src/test/java/fr/greencodeinitiative/java/checks/AvoidOnePlusOneQueryProblemTest.java rename to java-plugin/src/test/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemTest.java index 85400b201..e8374783d 100644 --- a/java-plugin/src/test/java/fr/greencodeinitiative/java/checks/AvoidOnePlusOneQueryProblemTest.java +++ b/java-plugin/src/test/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemTest.java @@ -4,7 +4,7 @@ import org.junit.jupiter.api.Test; import org.sonar.java.checks.verifier.CheckVerifier; -class AvoidOnePlusOneQueryProblemTest { +class AvoidNPlusOneQueryProblemTest { /** * @formatter:off From 99111e2e301f46cece7b647301e318ae5f9da0bb Mon Sep 17 00:00:00 2001 From: "Samuel MARTIN [aa6a131a-f6b6-40af-9503-007c1ae129a9]" Date: Thu, 6 Apr 2023 11:52:22 +0200 Subject: [PATCH 07/14] feat: Ajout de la documentation --- .../AvoidNPlusOneQueryProblemCheck.java | 6 +--- .../l10n/java/rules/java/EC206.html | 36 +++++++++++++++++++ .../l10n/java/rules/java/EC206.json | 18 ++++++++++ 3 files changed, 55 insertions(+), 5 deletions(-) create mode 100644 java-plugin/src/main/resources/fr/greencodeinitiative/l10n/java/rules/java/EC206.html create mode 100644 java-plugin/src/main/resources/fr/greencodeinitiative/l10n/java/rules/java/EC206.json diff --git a/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemCheck.java b/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemCheck.java index 70e51f052..135456ef5 100644 --- a/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemCheck.java +++ b/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemCheck.java @@ -1,16 +1,12 @@ package fr.greencodeinitiative.java.checks; -import java.util.Arrays; import java.util.List; import java.util.Optional; -import java.util.Optional; - import org.sonar.check.Priority; import org.sonar.check.Rule; import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; import org.sonar.plugins.java.api.semantic.Type; -import org.sonar.plugins.java.api.semantic.Type; import org.sonar.plugins.java.api.tree.*; @Rule(key = "EC206", @@ -38,7 +34,7 @@ public class AvoidNPlusOneQueryProblemCheck extends IssuableSubscriptionVisitor @Override public List nodesToVisit() { - return Arrays.asList(Tree.Kind.INTERFACE); + return List.of(Tree.Kind.INTERFACE); } @Override diff --git a/java-plugin/src/main/resources/fr/greencodeinitiative/l10n/java/rules/java/EC206.html b/java-plugin/src/main/resources/fr/greencodeinitiative/l10n/java/rules/java/EC206.html new file mode 100644 index 000000000..535ae5774 --- /dev/null +++ b/java-plugin/src/main/resources/fr/greencodeinitiative/l10n/java/rules/java/EC206.html @@ -0,0 +1,36 @@ +

When using JPA on a relation One to Many, Many to One or Many to Many, you should use a query (Query annotation) with LEFT JOIN FETCH or use attribute path (@EntityGraph annotation) to avoid the generation of N+1 query instead of 1 query

+

Noncompliant Code Example

+
+public interface UserRepository extends CrudRepository {
+
+    List findAllBy();             
+}
+
+@Entity
+public class User {
+
+    @OneToMany
+    private List roles;
+
+    public List getRoles() {
+        return roles;
+    }
+
+    public void setRoles(List roles) {
+        this.roles = roles;
+    }
+}
+
+
+

Compliant Solution

+
+
+public interface UserRepository extends CrudRepository {
+
+    @Query("SELECT p FROM User p LEFT JOIN FETCH p.roles")  
+    List findWithoutNPlusOne();
+
+    @EntityGraph(attributePaths = {"roles"})                
+    List findAll();
+}
+
diff --git a/java-plugin/src/main/resources/fr/greencodeinitiative/l10n/java/rules/java/EC206.json b/java-plugin/src/main/resources/fr/greencodeinitiative/l10n/java/rules/java/EC206.json new file mode 100644 index 000000000..e4d8cc1d0 --- /dev/null +++ b/java-plugin/src/main/resources/fr/greencodeinitiative/l10n/java/rules/java/EC206.json @@ -0,0 +1,18 @@ +{ + "title": "Avoid to generate N+1 query when using JPA on relations One to Many, Many To One or Many To One", + "type": "CODE_SMELL", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "10min" + }, + "tags": [ + "eco-design", + "java", + "performance", + "bug", + "ecocode", + "sql" + ], + "defaultSeverity": "Minor" +} \ No newline at end of file From daa6353ae764c8296d9e9b991d4fa5bb0b482dbe Mon Sep 17 00:00:00 2001 From: Samuel MARTIN Date: Thu, 6 Apr 2023 11:26:43 +0200 Subject: [PATCH 08/14] =?UTF-8?q?feat:=20D=C3=A9tection=20des=20annotation?= =?UTF-8?q?s=20ManyToOne,=20OneToMany=20et=20ManyToMany=20dans=20l'entit?= =?UTF-8?q?=C3=A9=20li=C3=A9e=20au=20Repository?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AvoidNPlusOneQueryProblemCheck.java | 41 ++++++++++++++++++- .../files/AvoidNPlusOneQueryProblemBad.java | 27 ++++++++++++ ...ava => AvoidNPlusOneQueryProblemTest.java} | 2 +- 3 files changed, 68 insertions(+), 2 deletions(-) rename java-plugin/src/test/java/fr/greencodeinitiative/java/checks/{AvoidOnePlusOneQueryProblemTest.java => AvoidNPlusOneQueryProblemTest.java} (96%) diff --git a/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemCheck.java b/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemCheck.java index 71c2b1b29..70e51f052 100644 --- a/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemCheck.java +++ b/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemCheck.java @@ -2,10 +2,15 @@ import java.util.Arrays; import java.util.List; +import java.util.Optional; +import java.util.Optional; + import org.sonar.check.Priority; import org.sonar.check.Rule; import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; +import org.sonar.plugins.java.api.semantic.Type; +import org.sonar.plugins.java.api.semantic.Type; import org.sonar.plugins.java.api.tree.*; @Rule(key = "EC206", @@ -23,6 +28,12 @@ public class AvoidNPlusOneQueryProblemCheck extends IssuableSubscriptionVisitor private static final String LEFT_JOIN = "LEFT JOIN"; private static final String VALUE = "value"; + private static final List SPRING_PROBLEMATIC_ANNOTATIONS = List.of( + "javax.persistence.OneToMany", + "javax.persistence.ManyToOne", + "javax.persistence.ManyToMany" + ); + private final AvoidNPlusOneQueryProblemCheckVisitor visitorInFile = new AvoidNPlusOneQueryProblemCheckVisitor(); @Override @@ -32,11 +43,39 @@ public List nodesToVisit() { @Override public void visitNode(Tree tree) { - if (((ClassTree) tree).symbol().type().isSubtypeOf(SPRING_REPOSITORY)) { + ClassTree classTree = (ClassTree) tree; + + if (isSpringRepository(classTree) && hasManyToOneAnnotations(classTree)) { tree.accept(visitorInFile); } } + private boolean hasManyToOneAnnotations(ClassTree classTree) { + Optional crudRepositoryInterface = classTree.symbol().interfaces().stream() + .filter(t -> t.isSubtypeOf(SPRING_REPOSITORY)) + .findFirst(); + + return crudRepositoryInterface.map(type -> type + .typeArguments() + .get(0) + .symbol() + .declaration() + .members() + .stream() + .filter(t -> t.is(Tree.Kind.VARIABLE)) + .anyMatch(t -> ((VariableTree) t).modifiers() + .annotations() + .stream() + .anyMatch(a -> + SPRING_PROBLEMATIC_ANNOTATIONS.stream().anyMatch(a.symbolType()::isSubtypeOf) + ))) + .orElse(false); + } + + private static boolean isSpringRepository(ClassTree classTree) { + return classTree.symbol().type().isSubtypeOf(SPRING_REPOSITORY); + } + private class AvoidNPlusOneQueryProblemCheckVisitor extends BaseTreeVisitor { @Override diff --git a/java-plugin/src/test/files/AvoidNPlusOneQueryProblemBad.java b/java-plugin/src/test/files/AvoidNPlusOneQueryProblemBad.java index 61e55d89f..3302aa957 100644 --- a/java-plugin/src/test/files/AvoidNPlusOneQueryProblemBad.java +++ b/java-plugin/src/test/files/AvoidNPlusOneQueryProblemBad.java @@ -1,5 +1,7 @@ import org.springframework.data.repository.CrudRepository; import java.util.List; +import javax.persistence.Entity; +import javax.persistence.OneToMany; public interface UserRepository extends CrudRepository { @@ -12,6 +14,31 @@ public interface UserRepository extends CrudRepository { List findAllWithRoles(); } +@Entity public class User { + @OneToMany + private List roles; + + public List getRoles() { + return roles; + } + + public void setRoles(List roles) { + this.roles = roles; + } +} + +@Entity +public class Role { + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } } diff --git a/java-plugin/src/test/java/fr/greencodeinitiative/java/checks/AvoidOnePlusOneQueryProblemTest.java b/java-plugin/src/test/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemTest.java similarity index 96% rename from java-plugin/src/test/java/fr/greencodeinitiative/java/checks/AvoidOnePlusOneQueryProblemTest.java rename to java-plugin/src/test/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemTest.java index 85400b201..e8374783d 100644 --- a/java-plugin/src/test/java/fr/greencodeinitiative/java/checks/AvoidOnePlusOneQueryProblemTest.java +++ b/java-plugin/src/test/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemTest.java @@ -4,7 +4,7 @@ import org.junit.jupiter.api.Test; import org.sonar.java.checks.verifier.CheckVerifier; -class AvoidOnePlusOneQueryProblemTest { +class AvoidNPlusOneQueryProblemTest { /** * @formatter:off From 17bf02e082b81db9e0e2ee0503ac91dfbe52fef2 Mon Sep 17 00:00:00 2001 From: Samuel MARTIN Date: Thu, 6 Apr 2023 11:52:22 +0200 Subject: [PATCH 09/14] feat: Ajout de la documentation --- .../AvoidNPlusOneQueryProblemCheck.java | 6 +--- .../l10n/java/rules/java/EC206.html | 36 +++++++++++++++++++ .../l10n/java/rules/java/EC206.json | 18 ++++++++++ 3 files changed, 55 insertions(+), 5 deletions(-) create mode 100644 java-plugin/src/main/resources/fr/greencodeinitiative/l10n/java/rules/java/EC206.html create mode 100644 java-plugin/src/main/resources/fr/greencodeinitiative/l10n/java/rules/java/EC206.json diff --git a/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemCheck.java b/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemCheck.java index 70e51f052..135456ef5 100644 --- a/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemCheck.java +++ b/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemCheck.java @@ -1,16 +1,12 @@ package fr.greencodeinitiative.java.checks; -import java.util.Arrays; import java.util.List; import java.util.Optional; -import java.util.Optional; - import org.sonar.check.Priority; import org.sonar.check.Rule; import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; import org.sonar.plugins.java.api.semantic.Type; -import org.sonar.plugins.java.api.semantic.Type; import org.sonar.plugins.java.api.tree.*; @Rule(key = "EC206", @@ -38,7 +34,7 @@ public class AvoidNPlusOneQueryProblemCheck extends IssuableSubscriptionVisitor @Override public List nodesToVisit() { - return Arrays.asList(Tree.Kind.INTERFACE); + return List.of(Tree.Kind.INTERFACE); } @Override diff --git a/java-plugin/src/main/resources/fr/greencodeinitiative/l10n/java/rules/java/EC206.html b/java-plugin/src/main/resources/fr/greencodeinitiative/l10n/java/rules/java/EC206.html new file mode 100644 index 000000000..535ae5774 --- /dev/null +++ b/java-plugin/src/main/resources/fr/greencodeinitiative/l10n/java/rules/java/EC206.html @@ -0,0 +1,36 @@ +

When using JPA on a relation One to Many, Many to One or Many to Many, you should use a query (Query annotation) with LEFT JOIN FETCH or use attribute path (@EntityGraph annotation) to avoid the generation of N+1 query instead of 1 query

+

Noncompliant Code Example

+
+public interface UserRepository extends CrudRepository {
+
+    List findAllBy();             
+}
+
+@Entity
+public class User {
+
+    @OneToMany
+    private List roles;
+
+    public List getRoles() {
+        return roles;
+    }
+
+    public void setRoles(List roles) {
+        this.roles = roles;
+    }
+}
+
+
+

Compliant Solution

+
+
+public interface UserRepository extends CrudRepository {
+
+    @Query("SELECT p FROM User p LEFT JOIN FETCH p.roles")  
+    List findWithoutNPlusOne();
+
+    @EntityGraph(attributePaths = {"roles"})                
+    List findAll();
+}
+
diff --git a/java-plugin/src/main/resources/fr/greencodeinitiative/l10n/java/rules/java/EC206.json b/java-plugin/src/main/resources/fr/greencodeinitiative/l10n/java/rules/java/EC206.json new file mode 100644 index 000000000..e4d8cc1d0 --- /dev/null +++ b/java-plugin/src/main/resources/fr/greencodeinitiative/l10n/java/rules/java/EC206.json @@ -0,0 +1,18 @@ +{ + "title": "Avoid to generate N+1 query when using JPA on relations One to Many, Many To One or Many To One", + "type": "CODE_SMELL", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "10min" + }, + "tags": [ + "eco-design", + "java", + "performance", + "bug", + "ecocode", + "sql" + ], + "defaultSeverity": "Minor" +} \ No newline at end of file From 3ffce85de79679787abbf9a3e4e60b751de8d375 Mon Sep 17 00:00:00 2001 From: Samuel MARTIN Date: Thu, 6 Apr 2023 12:01:10 +0200 Subject: [PATCH 10/14] fix: Nombre de regles java dans les tests --- .../fr/greencodeinitiative/java/JavaCheckRegistrarTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java-plugin/src/test/java/fr/greencodeinitiative/java/JavaCheckRegistrarTest.java b/java-plugin/src/test/java/fr/greencodeinitiative/java/JavaCheckRegistrarTest.java index 3225b1a56..fe7f03adf 100644 --- a/java-plugin/src/test/java/fr/greencodeinitiative/java/JavaCheckRegistrarTest.java +++ b/java-plugin/src/test/java/fr/greencodeinitiative/java/JavaCheckRegistrarTest.java @@ -32,7 +32,7 @@ void checkNumberRules() { final JavaCheckRegistrar registrar = new JavaCheckRegistrar(); registrar.register(context); - assertThat(context.checkClasses()).hasSize(19); + assertThat(context.checkClasses()).hasSize(20); assertThat(context.testCheckClasses()).isEmpty(); } } From 872e79916528f4dcd54feac3b3ddb27000f5360d Mon Sep 17 00:00:00 2001 From: MaximeAT Date: Thu, 6 Apr 2023 12:31:29 +0200 Subject: [PATCH 11/14] add comments and refacto isQueryAnnotationWithFetch --- .../AvoidNPlusOneQueryProblemCheck.java | 79 +++++++++++++++++-- .../files/AvoidNPlusOneQueryProblemBad.java | 10 ++- .../files/AvoidNPlusOneQueryProblemGood.java | 11 +-- ...NPlusOneQueryProblemNotRepositoryGood.java | 4 + 4 files changed, 89 insertions(+), 15 deletions(-) diff --git a/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemCheck.java b/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemCheck.java index 135456ef5..2f7691c10 100644 --- a/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemCheck.java +++ b/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemCheck.java @@ -21,7 +21,7 @@ public class AvoidNPlusOneQueryProblemCheck extends IssuableSubscriptionVisitor private static final String SPRING_REPOSITORY = "org.springframework.data.repository.Repository"; private static final String QUERY = "org.springframework.data.jpa.repository.Query"; private static final String ENTITY_GRAPH = "org.springframework.data.jpa.repository.EntityGraph"; - private static final String LEFT_JOIN = "LEFT JOIN"; + private static final String JOIN_FETCH = "JOIN FETCH"; private static final String VALUE = "value"; private static final List SPRING_PROBLEMATIC_ANNOTATIONS = List.of( @@ -46,6 +46,7 @@ public void visitNode(Tree tree) { } } + //Check if the repository entity contains parameter annotate with ManyToOne, OneToMany or ManyToMany private boolean hasManyToOneAnnotations(ClassTree classTree) { Optional crudRepositoryInterface = classTree.symbol().interfaces().stream() .filter(t -> t.isSubtypeOf(SPRING_REPOSITORY)) @@ -68,6 +69,11 @@ private boolean hasManyToOneAnnotations(ClassTree classTree) { .orElse(false); } + /** + * Check if this class implements jpa Repository + * @param classTree the class to analyze + * @return true if this class implements jpa Repository + */ private static boolean isSpringRepository(ClassTree classTree) { return classTree.symbol().type().isSubtypeOf(SPRING_REPOSITORY); } @@ -77,6 +83,7 @@ private class AvoidNPlusOneQueryProblemCheckVisitor extends BaseTreeVisitor { @Override public void visitMethod(MethodTree tree) { if ( + //Check all methods of the repository that return an iterable tree.returnType().symbolType().isSubtypeOf(Iterable.class.getName()) && hasNoCompliantAnnotation(tree) ) { @@ -86,6 +93,11 @@ && hasNoCompliantAnnotation(tree) } } + /** + * Check if the method is correctly annotated with Query or EntityGraph + * @param tree the method to analyze + * @return true if the method is not correctly annotated + */ boolean hasNoCompliantAnnotation(MethodTree tree) { return tree.modifiers().annotations().stream().noneMatch( a -> isQueryAnnotationWithFetch(a) || @@ -93,13 +105,66 @@ boolean hasNoCompliantAnnotation(MethodTree tree) { ); } - private boolean isQueryAnnotationWithFetch(AnnotationTree a) { - return a.symbolType().is(QUERY) - && (a.arguments().stream().filter(arg -> Tree.Kind.STRING_LITERAL.equals(arg.kind())) - .anyMatch(arg -> arg.firstToken().text().contains(LEFT_JOIN)) - || (a.arguments().stream().filter(arg -> Tree.Kind.ASSIGNMENT.equals(arg.kind())) + /** + * Check if the method is correctly annotated with Query. That is to say the value parameter of the annotation + * contains an sql query with {@link #JOIN_FETCH}. + * + * Query("SELECT p FROM User p LEFT JOIN FETCH p.roles") + * With this first solution, the annotation contains a single argument containing a single token which is the sql query + * + * Query(value = "SELECT p FROM User p LEFT JOIN FETCH p.roles") + * With this second solution, the annotation contains a single argument containing three tokens which are : + * - value + * - equal sign + * - sql query + * + * @param annotationTree annotation to analyze + * @return true if annotationTree is a query annotation with sql query that contains "JOIN FETCH" + */ + private boolean isQueryAnnotationWithFetch(AnnotationTree annotationTree) { + return annotationTree.symbolType().is(QUERY) + && getSqlQuery(annotationTree).map(this::isValidSqlQuery).orElse(false); + } + + /** + * @param annotationTree annotation whose symbolType is {@link #QUERY} + * @return the SQLQuery which is in annotation value attribute + */ + private Optional getSqlQuery(AnnotationTree annotationTree) { + return getSqlQueryFromStringLiteralArgument(annotationTree) + .or(() -> getSqlQueryFromAssignementArgument(annotationTree)); + } + + /** + * @param annotationTree annotation whose symbolType is {@link #QUERY} + * @return the SQLQuery which is in annotation value attribute + */ + private Optional getSqlQueryFromStringLiteralArgument(AnnotationTree annotationTree) { + return annotationTree.arguments().stream() + .filter(arg -> Tree.Kind.STRING_LITERAL.equals(arg.kind())) + .findAny() + .map(arg -> arg.firstToken().text()); + } + + /** + * @param annotationTree annotation whose symbolType is {@link #QUERY} + * @return the SQLQuery which is in annotation value attribute + */ + private Optional getSqlQueryFromAssignementArgument(AnnotationTree annotationTree) { + return annotationTree.arguments().stream() + .filter(arg -> Tree.Kind.ASSIGNMENT.equals(arg.kind())) .filter(arg -> VALUE.equals(arg.firstToken().text())) - .anyMatch(arg -> arg.lastToken().text().contains(LEFT_JOIN)))); + .map(arg -> arg.lastToken().text()) + .findAny(); + } + + /** + * + * @param sqlQuery sql query to analyze + * @return true sqlQuery contains {@link #JOIN_FETCH} + */ + private boolean isValidSqlQuery(String sqlQuery) { + return sqlQuery.contains(JOIN_FETCH); } } } diff --git a/java-plugin/src/test/files/AvoidNPlusOneQueryProblemBad.java b/java-plugin/src/test/files/AvoidNPlusOneQueryProblemBad.java index 3302aa957..78928bba1 100644 --- a/java-plugin/src/test/files/AvoidNPlusOneQueryProblemBad.java +++ b/java-plugin/src/test/files/AvoidNPlusOneQueryProblemBad.java @@ -1,17 +1,21 @@ import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.Query; import java.util.List; import javax.persistence.Entity; import javax.persistence.OneToMany; public interface UserRepository extends CrudRepository { - List findAll(); // Noncompliant {{Avoid N+1 Query problem}} + List shouldFailedBecauseIsNotAnnotated(); // Noncompliant {{Avoid N+1 Query problem}} @Override // Noncompliant {{Avoid N+1 Query problem}} - List findByIds(); + List shouldFailedBecauseIsNotAnnotatedWithARightAnnotation(); @Query(value = "SELECT p FROM User p LEFT JOIN p.roles", nativeQuery = false) // Noncompliant {{Avoid N+1 Query problem}} - List findAllWithRoles(); + List shouldFailedBecauseQueryAnnotationDoesNotContainsJointFetch(); + + @Query("SELECT p FROM User p LEFT JOIN p.roles") // Noncompliant {{Avoid N+1 Query problem}} + List shouldFailedBecauseQueryAnnotationDoesNotContainsJointFetch(); } @Entity diff --git a/java-plugin/src/test/files/AvoidNPlusOneQueryProblemGood.java b/java-plugin/src/test/files/AvoidNPlusOneQueryProblemGood.java index f9b46a9f8..6c100ced3 100644 --- a/java-plugin/src/test/files/AvoidNPlusOneQueryProblemGood.java +++ b/java-plugin/src/test/files/AvoidNPlusOneQueryProblemGood.java @@ -5,20 +5,21 @@ public interface UserRepository extends CrudRepository { - User findById(); + User shloudSucceedBecauseDoesNotReturnAnIterable(); @Query("SELECT p FROM User p LEFT JOIN FETCH p.roles") - List findWithoutNPlusOne(); + List shouldSucceedBecauseQueryAnnotationContainsJointFetch(); @EntityGraph(attributePaths = {"roles"}) - List findAll(); + List shouldSucceedBecauseIsAnnotatedWithEntityGraph(); + @Query(value = "SELECT p FROM User p LEFT JOIN FETCH p.roles", nativeQuery = false) @Override - List findAllWithRoles(); + List shouldSucceedBecauseIsAnnotatedSeveralTimesIncludingOnceQueryAnnotationContainsJointFetch(); @Query("SELECT p FROM User p LEFT JOIN FETCH p.roles") @Override - List findAllWithAdminRoles(); + List shouldSucceedBecauseIsAnnotatedSeveralTimesIncludingOnceQueryAnnotationContainsJointFetch(); } diff --git a/java-plugin/src/test/files/AvoidNPlusOneQueryProblemNotRepositoryGood.java b/java-plugin/src/test/files/AvoidNPlusOneQueryProblemNotRepositoryGood.java index 624d20c5a..fd2fd79af 100644 --- a/java-plugin/src/test/files/AvoidNPlusOneQueryProblemNotRepositoryGood.java +++ b/java-plugin/src/test/files/AvoidNPlusOneQueryProblemNotRepositoryGood.java @@ -1,5 +1,9 @@ import org.springframework.data.repository.CrudRepository; import java.util.List; + +/** + * Should succeed because is not a repository + */ public interface UserRepository extends User { User findById(); From b9f0e1a014710bd32b28807dee191c60c41dab7a Mon Sep 17 00:00:00 2001 From: Samuel MARTIN Date: Thu, 6 Apr 2023 13:34:17 +0200 Subject: [PATCH 12/14] =?UTF-8?q?fix:=20Correction=20import=20Am=C3=A9lior?= =?UTF-8?q?ation=20d=C3=A9tection=20LEFT=20JOIN=20FETCH?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- java-plugin/src/test/files/AvoidNPlusOneQueryProblemBad.java | 1 + 1 file changed, 1 insertion(+) diff --git a/java-plugin/src/test/files/AvoidNPlusOneQueryProblemBad.java b/java-plugin/src/test/files/AvoidNPlusOneQueryProblemBad.java index 78928bba1..a495181d9 100644 --- a/java-plugin/src/test/files/AvoidNPlusOneQueryProblemBad.java +++ b/java-plugin/src/test/files/AvoidNPlusOneQueryProblemBad.java @@ -3,6 +3,7 @@ import java.util.List; import javax.persistence.Entity; import javax.persistence.OneToMany; +import org.springframework.data.jpa.repository.Query; public interface UserRepository extends CrudRepository { From 8aaff8339c00af899379c7d28f481f18c3a73340 Mon Sep 17 00:00:00 2001 From: Samuel MARTIN Date: Thu, 6 Apr 2023 13:54:23 +0200 Subject: [PATCH 13/14] =?UTF-8?q?fix:=20Mise=20en=20coh=C3=A9rence=20des?= =?UTF-8?q?=20cas=20de=20test=20avec=20le=20projet=20de=20test=20d'int?= =?UTF-8?q?=C3=A9gration.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../files/AvoidNPlusOneQueryProblemBad.java | 21 ++++++++-------- .../files/AvoidNPlusOneQueryProblemGood.java | 25 ++++++++++--------- ...NPlusOneQueryProblemNotRepositoryGood.java | 23 +++++++++-------- 3 files changed, 36 insertions(+), 33 deletions(-) diff --git a/java-plugin/src/test/files/AvoidNPlusOneQueryProblemBad.java b/java-plugin/src/test/files/AvoidNPlusOneQueryProblemBad.java index a495181d9..59a23e240 100644 --- a/java-plugin/src/test/files/AvoidNPlusOneQueryProblemBad.java +++ b/java-plugin/src/test/files/AvoidNPlusOneQueryProblemBad.java @@ -1,26 +1,27 @@ -import org.springframework.data.repository.CrudRepository; +package fr.greencodeinitiative.java.checks; + import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.CrudRepository; import java.util.List; import javax.persistence.Entity; import javax.persistence.OneToMany; -import org.springframework.data.jpa.repository.Query; -public interface UserRepository extends CrudRepository { +public interface AvoidNPlusOneQueryProblemBad extends CrudRepository { - List shouldFailedBecauseIsNotAnnotated(); // Noncompliant {{Avoid N+1 Query problem}} + List shouldFailBecauseIsNotAnnotated(); // Noncompliant {{Avoid N+1 Query problem}} - @Override // Noncompliant {{Avoid N+1 Query problem}} - List shouldFailedBecauseIsNotAnnotatedWithARightAnnotation(); + @Deprecated // Noncompliant {{Avoid N+1 Query problem}} + List shouldFailBecauseIsNotAnnotatedWithARightAnnotation(); @Query(value = "SELECT p FROM User p LEFT JOIN p.roles", nativeQuery = false) // Noncompliant {{Avoid N+1 Query problem}} - List shouldFailedBecauseQueryAnnotationDoesNotContainsJointFetch(); + List shouldFailBecauseQueryAnnotationDoesNotContainsJointFetch(); @Query("SELECT p FROM User p LEFT JOIN p.roles") // Noncompliant {{Avoid N+1 Query problem}} - List shouldFailedBecauseQueryAnnotationDoesNotContainsJointFetch(); + List shouldFailBecauseQueryAnnotationDoesNotContainsJointFetchValue(); } @Entity -public class User { +class User { @OneToMany private List roles; @@ -35,7 +36,7 @@ public void setRoles(List roles) { } @Entity -public class Role { +class Role { private String name; diff --git a/java-plugin/src/test/files/AvoidNPlusOneQueryProblemGood.java b/java-plugin/src/test/files/AvoidNPlusOneQueryProblemGood.java index 6c100ced3..ef7915deb 100644 --- a/java-plugin/src/test/files/AvoidNPlusOneQueryProblemGood.java +++ b/java-plugin/src/test/files/AvoidNPlusOneQueryProblemGood.java @@ -1,28 +1,29 @@ +package fr.greencodeinitiative.java.checks; + import org.springframework.data.repository.CrudRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.EntityGraph; import java.util.List; -public interface UserRepository extends CrudRepository { +public interface AvoidNPlusOneQueryProblemGood extends CrudRepository { - User shloudSucceedBecauseDoesNotReturnAnIterable(); + Client shouldSucceedBecauseDoesNotReturnAnIterable(); @Query("SELECT p FROM User p LEFT JOIN FETCH p.roles") - List shouldSucceedBecauseQueryAnnotationContainsJointFetch(); + List shouldSucceedBecauseQueryAnnotationContainsJointFetch(); @EntityGraph(attributePaths = {"roles"}) - List shouldSucceedBecauseIsAnnotatedWithEntityGraph(); - - @Query(value = "SELECT p FROM User p LEFT JOIN FETCH p.roles", nativeQuery = false) - @Override - List shouldSucceedBecauseIsAnnotatedSeveralTimesIncludingOnceQueryAnnotationContainsJointFetch(); + List shouldSucceedBecauseIsAnnotatedWithEntityGraph(); + @Query(value = "SELECT p FROM Client p LEFT JOIN FETCH p.roles", nativeQuery = false) + @Deprecated + List shouldSucceedBecauseIsAnnotatedSeveralTimesIncludingOnceQueryAnnotationContainsJointFetch(); - @Query("SELECT p FROM User p LEFT JOIN FETCH p.roles") - @Override - List shouldSucceedBecauseIsAnnotatedSeveralTimesIncludingOnceQueryAnnotationContainsJointFetch(); + @Query("SELECT p FROM Client p LEFT JOIN FETCH p.roles") + @Deprecated + List shouldSucceedBecauseIsAnnotatedSeveralTimesIncludingOnceQueryAnnotationContainsJointFetchValue(); } -public class User { +class Client { } diff --git a/java-plugin/src/test/files/AvoidNPlusOneQueryProblemNotRepositoryGood.java b/java-plugin/src/test/files/AvoidNPlusOneQueryProblemNotRepositoryGood.java index fd2fd79af..d3a057e8b 100644 --- a/java-plugin/src/test/files/AvoidNPlusOneQueryProblemNotRepositoryGood.java +++ b/java-plugin/src/test/files/AvoidNPlusOneQueryProblemNotRepositoryGood.java @@ -1,21 +1,22 @@ -import org.springframework.data.repository.CrudRepository; +package fr.greencodeinitiative.java.checks; + import java.util.List; +public interface AvoidNPlusOneQueryProblemNotRepositoryGood extends CustomerRepository { + + Customer findById(); -/** - * Should succeed because is not a repository - */ -public interface UserRepository extends User { + List findWithoutNPlusOne(); - User findById(); + List findAll(); + @Deprecated + List findAllWithRoles(); - List findWithoutNPlusOne(); +} - List findAll(); - @Override - List findAllWithRoles(); +interface CustomerRepository { } -public class User { +class Customer { } From b17e8b6150b0525f979bf38aa6ca9dce455de053 Mon Sep 17 00:00:00 2001 From: Samuel MARTIN Date: Thu, 6 Apr 2023 14:27:17 +0200 Subject: [PATCH 14/14] doc: Ajout de 2 todos --- .../java/checks/AvoidNPlusOneQueryProblemCheck.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemCheck.java b/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemCheck.java index 2f7691c10..3cb94c869 100644 --- a/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemCheck.java +++ b/java-plugin/src/main/java/fr/greencodeinitiative/java/checks/AvoidNPlusOneQueryProblemCheck.java @@ -9,6 +9,8 @@ import org.sonar.plugins.java.api.semantic.Type; import org.sonar.plugins.java.api.tree.*; +// TODO : Check repository default methods call returning a List of Entity having members with annotation OneToMany +// TODO : Check repository methods having a return type : List of any Entity having members with annotation OneToMany @Rule(key = "EC206", name = "Developpement", description = AvoidNPlusOneQueryProblemCheck.RULE_MESSAGE,