Skip to content

Commit 6a6dabf

Browse files
committed
Add package-to-scan feature
# Context Some applications can have thousands of classes. When JPA is configured to scan classes (`exclude-unlisted-classes` is `false` in `persistence.xml`), the time used to scan all classes can be quite long. With reasonable application size, class scanning can take only ten's of milliseconds but for heavy application it can take a few seconds. In order to help reducing startup time, we can restrict class scanning to a pre-configured list of known packages to avoid loading unnecessary classes metadata. # What was done - Introduced a new property `PersistenceUnitProperties.PACKAGES_TO_SCAN`: `eclipselink.packages-to-scan` - Modified `PersistenceUnitProcessor.getClassNamesFromURL` to only return eligible classes This new property takes as a value a list of packages as a string: `com.foo.bar, some.other.packages` # Performance result I've tested this feature with 2 applications, one with ~1000 classes and another with more than 30k classes. `MetadataProcessor.initPersistenceUnitClasses` was faster by ~25% for the small application and ~45% for the big one. Signed-off-by: Alexandre Jacob <[email protected]>
1 parent bc2a312 commit 6a6dabf

File tree

3 files changed

+98
-8
lines changed

3 files changed

+98
-8
lines changed

foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/config/PersistenceUnitProperties.java

+9
Original file line numberDiff line numberDiff line change
@@ -3906,6 +3906,15 @@ public class PersistenceUnitProperties {
39063906
*/
39073907
public static final String NAMING_INTO_INDEXED = "eclipselink.jpa.naming_into_indexed";
39083908

3909+
/**
3910+
* The "<code>eclipselink.packages-to-scan</code>" property
3911+
* allows you to specify a comma separated list of packages you allow to be scanned.
3912+
* This could help reducing startup time.
3913+
* <p>
3914+
* By default, no restriction will be applied and classes from every packages will be considered.
3915+
*/
3916+
public static final String PACKAGES_TO_SCAN = "eclipselink.packages-to-scan";
3917+
39093918
/**
39103919
* INTERNAL: The following properties will not be displayed through logging
39113920
* but instead have an alternate value shown in the log.

jpa/eclipselink.jpa.test/src/it/java/org/eclipse/persistence/testing/tests/jpa/advanced/PersistenceUnitProcessorTest.java

+33-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import junit.framework.Test;
2121
import junit.framework.TestSuite;
2222

23+
import org.eclipse.persistence.config.PersistenceUnitProperties;
2324
import org.eclipse.persistence.config.SystemProperties;
2425
import org.eclipse.persistence.exceptions.ValidationException;
2526
import org.eclipse.persistence.internal.jpa.deployment.ArchiveFactoryImpl;
@@ -32,8 +33,7 @@
3233
import java.net.URL;
3334
import java.net.URLConnection;
3435
import java.net.URLStreamHandler;
35-
import java.util.HashMap;
36-
import java.util.Map;
36+
import java.util.*;
3737

3838
import jakarta.persistence.EntityManagerFactory;
3939

@@ -222,4 +222,35 @@ public void testGetArchiveFactoryOverride() {
222222
public static class AF1 extends ArchiveFactoryImpl {}
223223
public static class AF2 extends ArchiveFactoryImpl {}
224224

225+
public void testIsEligibleToScan() {
226+
Assert.assertTrue(PersistenceUnitProcessor.isEligibleToScan(Collections.emptyList(), "foo/bar/MyClass.class"));
227+
228+
List<String> pathsToScan = new ArrayList<>();
229+
pathsToScan.add("foo/bar/");
230+
pathsToScan.add("com/test/");
231+
232+
Assert.assertTrue(PersistenceUnitProcessor.isEligibleToScan(pathsToScan, "foo/bar/MyClass.class"));
233+
Assert.assertTrue(PersistenceUnitProcessor.isEligibleToScan(pathsToScan, "foo/bar/inner/MyClass.class"));
234+
Assert.assertFalse(PersistenceUnitProcessor.isEligibleToScan(pathsToScan, "foo/MyClass.class"));
235+
Assert.assertFalse(PersistenceUnitProcessor.isEligibleToScan(pathsToScan, "foo/barMyClass.class"));
236+
237+
Assert.assertTrue(PersistenceUnitProcessor.isEligibleToScan(pathsToScan, "com/test/MyClass.class"));
238+
239+
Assert.assertFalse(PersistenceUnitProcessor.isEligibleToScan(pathsToScan, "org/apache/SomeClass.class"));
240+
}
241+
242+
public void testPathsToScan() {
243+
Assert.assertTrue(PersistenceUnitProcessor.pathsToScan(null).isEmpty());
244+
Assert.assertTrue(PersistenceUnitProcessor.pathsToScan("").isEmpty());
245+
Assert.assertTrue(PersistenceUnitProcessor.pathsToScan(" ").isEmpty());
246+
247+
List<String> pathsToScan = PersistenceUnitProcessor.pathsToScan("foo.bar,com.test");
248+
Assert.assertEquals(2, pathsToScan.size());
249+
Assert.assertTrue(pathsToScan.contains("foo/bar/"));
250+
Assert.assertTrue(pathsToScan.contains("com/test/"));
251+
252+
List<String> pathsToScan2 = PersistenceUnitProcessor.pathsToScan("foo.bar, ");
253+
Assert.assertEquals(1, pathsToScan2.size());
254+
Assert.assertTrue(pathsToScan2.contains("foo/bar/"));
255+
}
225256
}

jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/deployment/PersistenceUnitProcessor.java

+56-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
* Copyright (c) 1998, 2020 Oracle and/or its affiliates. All rights reserved.
3-
* Copyright (c) 1998, 2018 IBM Corporation. All rights reserved.
3+
* Copyright (c) 1998, 2020 IBM Corporation. All rights reserved.
44
*
55
* This program and the accompanying materials are made available under the
66
* terms of the Eclipse Public License v. 2.0 which is available at
@@ -42,6 +42,7 @@
4242
import java.security.PrivilegedActionException;
4343
import java.util.ArrayList;
4444
import java.util.Collection;
45+
import java.util.Collections;
4546
import java.util.Enumeration;
4647
import java.util.HashSet;
4748
import java.util.Iterator;
@@ -479,22 +480,22 @@ public static ArchiveFactory getArchiveFactory(ClassLoader loader, Map propertie
479480
}
480481

481482
public static Set<String> getClassNamesFromURL(URL url, ClassLoader loader, Map properties) {
482-
Set<String> classNames = new HashSet<String>();
483+
Set<String> classNames = new HashSet<>();
483484
Archive archive = null;
484485
try {
485486
archive = PersistenceUnitProcessor.getArchiveFactory(loader, properties).createArchive(url, properties);
486487

488+
List<String> pathsToScan = pathsToScan(properties != null ? (String) properties.get(PersistenceUnitProperties.PACKAGES_TO_SCAN) : null);
489+
487490
if (archive != null) {
488491
for (Iterator<String> entries = archive.getEntries(); entries.hasNext();) {
489492
String entry = entries.next();
490-
if (entry.endsWith(".class")){ // NOI18N
493+
if (entry.endsWith(".class") && isEligibleToScan(pathsToScan, entry)) { // NOI18N
491494
classNames.add(buildClassNameFromEntryString(entry));
492495
}
493496
}
494497
}
495-
} catch (URISyntaxException e) {
496-
throw new RuntimeException("url = [" + url + "]", e); // NOI18N
497-
} catch (IOException e) {
498+
} catch (IOException | URISyntaxException e) {
498499
throw new RuntimeException("url = [" + url + "]", e); // NOI18N
499500
} finally {
500501
if (archive != null) {
@@ -504,6 +505,55 @@ public static Set<String> getClassNamesFromURL(URL url, ClassLoader loader, Map
504505
return classNames;
505506
}
506507

508+
/**
509+
* Returns true if a class entry is eligible to scan.
510+
* A class entry is eligible if:
511+
* - pathsToScan is empty (not configured)
512+
* - it resides in one of {@code pathsToScan} list
513+
*
514+
* @param pathsToScan list of paths where the class entry must be. Can be empty
515+
* @param classEntry path of the class we want to check in the form : foo/bar/MyClass.class
516+
* @return true is the class entry is eligible
517+
*/
518+
public static boolean isEligibleToScan(List<String> pathsToScan, String classEntry) {
519+
if (pathsToScan.isEmpty()) {
520+
return true;
521+
}
522+
523+
for (String pathToScan : pathsToScan) {
524+
if (classEntry.startsWith(pathToScan)) {
525+
return true;
526+
}
527+
}
528+
529+
return false;
530+
}
531+
532+
/**
533+
* Takes a comma separated list of packages to scan and returns a list of
534+
* paths.
535+
*
536+
* @param packagesToScanProperty list of packages to scan (comma-separated)
537+
* @return list of paths to scans
538+
*/
539+
public static List<String> pathsToScan(String packagesToScanProperty) {
540+
if (packagesToScanProperty == null || packagesToScanProperty.isEmpty()) {
541+
return Collections.emptyList();
542+
}
543+
544+
List<String> packagesToScan = new ArrayList<>();
545+
546+
for (String packageToScan : packagesToScanProperty.split(",")) {
547+
String trimmedPackageToScan = packageToScan.trim();
548+
549+
if (!trimmedPackageToScan.isEmpty()) {
550+
packagesToScan.add(trimmedPackageToScan.replace('.', '/') + "/");
551+
}
552+
}
553+
554+
return packagesToScan;
555+
}
556+
507557
/**
508558
* Return if a given class is annotated with @Embeddable.
509559
*/

0 commit comments

Comments
 (0)