From 09602118b01a75f23650e7997b65cbef4dd3297d Mon Sep 17 00:00:00 2001 From: SIDIBE Date: Tue, 19 May 2026 16:11:50 +0200 Subject: [PATCH 1/3] CRJVM206 Avoid N+1 selects problem --- ...teLazyRelationAccessInLoopCheckSample.java | 56 +++++++++++++++ ...ibernateLazyRelationAccessInLoopCheck.java | 69 +++++++++++++++++++ ...nateLazyRelationAccessInLoopCheckTest.java | 33 +++++++++ 3 files changed, 158 insertions(+) create mode 100644 src/it/test-projects/creedengo-java-plugin-test-project/src/main/java/org/greencodeinitiative/creedengo/java/checks/CRJVM206/AvoidHibernateLazyRelationAccessInLoopCheckSample.java create mode 100644 src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidHibernateLazyRelationAccessInLoopCheck.java create mode 100644 src/test/java/org/greencodeinitiative/creedengo/java/checks/CRJVM206/AvoidHibernateLazyRelationAccessInLoopCheckTest.java diff --git a/src/it/test-projects/creedengo-java-plugin-test-project/src/main/java/org/greencodeinitiative/creedengo/java/checks/CRJVM206/AvoidHibernateLazyRelationAccessInLoopCheckSample.java b/src/it/test-projects/creedengo-java-plugin-test-project/src/main/java/org/greencodeinitiative/creedengo/java/checks/CRJVM206/AvoidHibernateLazyRelationAccessInLoopCheckSample.java new file mode 100644 index 00000000..1bba50c6 --- /dev/null +++ b/src/it/test-projects/creedengo-java-plugin-test-project/src/main/java/org/greencodeinitiative/creedengo/java/checks/CRJVM206/AvoidHibernateLazyRelationAccessInLoopCheckSample.java @@ -0,0 +1,56 @@ +import java.util.List; + +class AvoidHibernateLazyRelationAccessInLoopCheckSample { + + void badForEachLoop(List products) { + for (Product product : products) { + product.getOrders(); // Noncompliant + } + } + + void badForLoop(List products) { + for (int i = 0; i < products.size(); i++) { + products.get(i).getOrders(); // Noncompliant + } + } + + void badStreamForEach(List products) { + products.forEach(product -> { + product.getOrders(); // Noncompliant + }); + } + + void badStreamMap(List products) { + products.stream() + .map(product -> product.getOrders().size()); // Noncompliant + } + + void goodSimpleGetter(List products) { + for (Product product : products) { + product.getName(); + } + } + + void goodIdGetter(List products) { + for (Product product : products) { + product.getId(); + } + } + + class Product { + List getOrders() { + return null; + } + + String getName() { + return ""; + } + + Long getId() { + return 1L; + } + } + + class Order { + } +} \ No newline at end of file diff --git a/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidHibernateLazyRelationAccessInLoopCheck.java b/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidHibernateLazyRelationAccessInLoopCheck.java new file mode 100644 index 00000000..04650c40 --- /dev/null +++ b/src/main/java/org/greencodeinitiative/creedengo/java/checks/AvoidHibernateLazyRelationAccessInLoopCheck.java @@ -0,0 +1,69 @@ +package org.greencodeinitiative.creedengo.java.checks; + +import org.sonar.check.Rule; +import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; +import org.sonar.plugins.java.api.tree.BaseTreeVisitor; +import org.sonar.plugins.java.api.tree.MethodInvocationTree; +import org.sonar.plugins.java.api.tree.Tree; + +import java.util.Arrays; +import java.util.List; + +@Rule(key = "CRJVM206") +public class AvoidHibernateLazyRelationAccessInLoopCheck extends IssuableSubscriptionVisitor { + + private static final String MESSAGE = + "Potential Hibernate N+1 query detected: avoid accessing a lazy relationship inside a loop. " + + "Use JOIN FETCH, EntityGraph, or batch fetching."; + + @Override + public List nodesToVisit() { + return Arrays.asList( + Tree.Kind.FOR_EACH_STATEMENT, + Tree.Kind.FOR_STATEMENT, + Tree.Kind.WHILE_STATEMENT, + Tree.Kind.DO_STATEMENT, + Tree.Kind.METHOD_INVOCATION + ); + } + + @Override + public void visitNode(Tree tree) { + if (tree.is(Tree.Kind.METHOD_INVOCATION)) { + MethodInvocationTree methodInvocationTree = (MethodInvocationTree) tree; + + if (isStreamOperation(methodInvocationTree)) { + checkInsideTree(tree); + } + } else { + checkInsideTree(tree); + } + } + + private void checkInsideTree(Tree tree) { + tree.accept(new BaseTreeVisitor() { + @Override + public void visitMethodInvocation(MethodInvocationTree methodInvocationTree) { + if (isPotentialLazyRelationGetter(methodInvocationTree)) { + reportIssue(methodInvocationTree, MESSAGE); + } + super.visitMethodInvocation(methodInvocationTree); + } + }); + } + + private boolean isStreamOperation(MethodInvocationTree methodInvocationTree) { + String methodName = methodInvocationTree.symbol().name(); + return "forEach".equals(methodName) + || "forEachOrdered".equals(methodName) + || "map".equals(methodName) + || "peek".equals(methodName); + } + + private boolean isPotentialLazyRelationGetter(MethodInvocationTree methodInvocationTree) { + String methodName = methodInvocationTree.symbol().name(); + return methodName.startsWith("get") + && methodName.length() > 4 + && methodName.endsWith("s"); + } +} \ No newline at end of file diff --git a/src/test/java/org/greencodeinitiative/creedengo/java/checks/CRJVM206/AvoidHibernateLazyRelationAccessInLoopCheckTest.java b/src/test/java/org/greencodeinitiative/creedengo/java/checks/CRJVM206/AvoidHibernateLazyRelationAccessInLoopCheckTest.java new file mode 100644 index 00000000..49c0ce73 --- /dev/null +++ b/src/test/java/org/greencodeinitiative/creedengo/java/checks/CRJVM206/AvoidHibernateLazyRelationAccessInLoopCheckTest.java @@ -0,0 +1,33 @@ +/* + * creedengo - Java language - Provides rules to reduce the environmental footprint of your Java programs + * Copyright © 2024 Green Code Initiative (https://green-code-initiative.org/) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.greencodeinitiative.creedengo.java.checks.CRJVM206; + +import org.greencodeinitiative.creedengo.java.checks.AvoidHibernateLazyRelationAccessInLoopCheck; +import org.junit.jupiter.api.Test; +import org.sonar.java.checks.verifier.CheckVerifier; + +class AvoidHibernateLazyRelationAccessInLoopCheckTest { + + @Test + void test() { + CheckVerifier.newVerifier() + .onFile(System.getProperty("testfiles.path") + "/CRJVM206/AvoidHibernateLazyRelationAccessInLoopCheckSample.java") + .withCheck(new AvoidHibernateLazyRelationAccessInLoopCheck()) + .verifyIssues(); + } +} From ae024e9244a2f54a2a8c8b47535fac0688c6d8b0 Mon Sep 17 00:00:00 2001 From: SIDIBE Date: Wed, 20 May 2026 13:41:05 +0200 Subject: [PATCH 2/3] [GCI27] Edit changelog and add code Rules in reedengo_way_profile.json --- CHANGELOG.md | 6 ++++++ .../creedengo/java/creedengo_way_profile.json | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a5c8c92c..aec14a9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -116,6 +116,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Update ecocode-rules-specifications to 1.4.6 +## [unreleased] + +- #188 Add Hibernate N+1 query detection for lazy-loaded relationship access inside loops and streams + +## [2.1.2] - 2026-05-20 + [unreleased](https://github.com/green-code-initiative/creedengo-java/compare/2.1.2...HEAD) [2.1.2](https://github.com/green-code-initiative/creedengo-java/compare/2.1.1...2.1.2) [2.1.1](https://github.com/green-code-initiative/creedengo-java/compare/2.1.0...2.1.1) diff --git a/src/main/resources/org/greencodeinitiative/creedengo/java/creedengo_way_profile.json b/src/main/resources/org/greencodeinitiative/creedengo/java/creedengo_way_profile.json index 059bf0f5..18449d22 100644 --- a/src/main/resources/org/greencodeinitiative/creedengo/java/creedengo_way_profile.json +++ b/src/main/resources/org/greencodeinitiative/creedengo/java/creedengo_way_profile.json @@ -18,6 +18,7 @@ "GCI78", "GCI79", "GCI82", - "GCI94" + "GCI94", + "CRJVM206" ] } From 8f75475429c3de72f421d11e5a93660b2dc7f97a Mon Sep 17 00:00:00 2001 From: SIDIBE Date: Wed, 20 May 2026 14:14:54 +0200 Subject: [PATCH 3/3] [CRJVM206] --- .../greencodeinitiative/creedengo/java/JavaCheckRegistrar.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/greencodeinitiative/creedengo/java/JavaCheckRegistrar.java b/src/main/java/org/greencodeinitiative/creedengo/java/JavaCheckRegistrar.java index 791f0cef..028fb52f 100644 --- a/src/main/java/org/greencodeinitiative/creedengo/java/JavaCheckRegistrar.java +++ b/src/main/java/org/greencodeinitiative/creedengo/java/JavaCheckRegistrar.java @@ -38,6 +38,7 @@ public class JavaCheckRegistrar implements CheckRegistrar { IncrementCheck.class, AvoidUsageOfStaticCollections.class, AvoidGettingSizeCollectionInLoop.class, + AvoidHibernateLazyRelationAccessInLoopCheck.class, AvoidRegexPatternNotStatic.class, NoFunctionCallWhenDeclaringForLoop.class, AvoidStatementForDMLQueries.class,