(N+1)queries problem on entity browser screens

I am trying to optimize the performance of my application, and I observed that on browse screens Jmix is creating queries to load related entities to my browse entity even when I don´t specify the references to these relations in the fetch plan nor in the screen interface.

For example: I have an entity Document that has a composition to DocVersion linked by the reference Document::docDovs. When I load the browser screen for Document, I see one query for DocVersion from each instance of Document on the screen page. Please see below:

2026-08-12 15:51:07.506 DEBUG 17976 — [nio-8080-exec-5] eclipselink.logging.sql                  : <t 348672877, conn 1013861459> SELECT ID AS a1, DOC_CODE AS a2, DOC_TITLE AS a3, DOC_TYPE AS a4, VERSION AS a5, DOC_ACT_ID AS a6, DOC_PRO_ID AS a7 FROM DOCUMENT LIMIT ?, ?
bind => [0, 50]

2026-08-12 15:51:07.511 DEBUG 17976 — [nio-8080-exec-5] eclipselink.logging.sql                  : <t 348672877, conn 1006824356> SELECT ID, DOV_CREATION_DATE, DOV_DESCR, DOV_FILE_CHANGE_DATE, DOV_FILE_REF, DOV_ID, DOV_LAST_CHANGE_DATE, DOV_LCS, DOV_UPLOADED, VERSION, DOV_DOC_ID, DOV_APPR_CYCLE_EVT_ID FROM DOC_VERSION WHERE (DOV_DOC_ID = ?) ORDER BY DOV_ID ASC
bind => [03da7f6457524884cbc90a773cabeba0]

2026-08-12 15:51:07.519 DEBUG 17976 — [nio-8080-exec-5] eclipselink.logging.sql                  : <t 348672877, conn 2137240691> SELECT ID, DOV_CREATION_DATE, DOV_DESCR, DOV_FILE_CHANGE_DATE, DOV_FILE_REF, DOV_ID, DOV_LAST_CHANGE_DATE, DOV_LCS, DOV_UPLOADED, VERSION, DOV_DOC_ID, DOV_APPR_CYCLE_EVT_ID FROM DOC_VERSION WHERE (DOV_DOC_ID = ?) ORDER BY DOV_ID ASC
bind => [05100c5fe372f9b4373c5645a27310d1]

.

.

.

I figured out that the method Document::getDovDocs is called from within Jmix infrastructure, which seems to be causing the lazy loading of all related DocVersion instances. Please see the stack dump below:

java.lang.Exception: Stack trace
at java.base/java.lang.Thread.dumpStack(Thread.java:1391)
at com.calidus.pdcs.entity.documents.Document.getDocDovs(Document.java:102)
at io.jmix.core.entity.BaseEntityEntry.getAttributeValue(BaseEntityEntry.java:85)
at io.jmix.core.entity.EntityValues.getValue(EntityValues.java:100)
at io.jmix.data.impl.EntityAttributesEraserImpl.traverseEntities(EntityAttributesEraserImpl.java:137)
at io.jmix.data.impl.EntityAttributesEraserImpl.collectErasingReferences(EntityAttributesEraserImpl.java:55)
at io.jmix.data.impl.DataStoreInMemoryCrudListener.entityLoading(DataStoreInMemoryCrudListener.java:104)
at io.jmix.core.datastore.DataStoreEntityLoadingEvent.sendTo(DataStoreEntityLoadingEvent.java:85)
at io.jmix.core.datastore.AbstractDataStore.fireEvent(AbstractDataStore.java:353)
at io.jmix.core.datastore.AbstractDataStore.loadList(AbstractDataStore.java:132)
at io.jmix.core.impl.UnconstrainedDataManagerImpl.loadList(UnconstrainedDataManagerImpl.java:112)
at io.jmix.ui.model.impl.CollectionLoaderImpl._load(CollectionLoaderImpl.java:121)
at io.jmix.ui.model.impl.CollectionLoaderImpl.load(CollectionLoaderImpl.java:101)
at io.jmix.ui.component.dataloadcoordinator.OnFrameOwnerEventLoadTrigger.load(OnFrameOwnerEventLoadTrigger.java:48)
at io.jmix.ui.component.dataloadcoordinator.OnFrameOwnerEventLoadTrigger.lambda$new$0(OnFrameOwnerEventLoadTrigger.java:39)
at io.jmix.core.common.event.EventHub.publish(EventHub.java:170)
at io.jmix.ui.screen.Screen.fireEvent(Screen.java:150)
at io.jmix.ui.screen.UiControllerUtils.fireEvent(UiControllerUtils.java:58)
at io.jmix.ui.sys.ScreensImpl.fireScreenBeforeShowEvent(ScreensImpl.java:1364)
at io.jmix.ui.sys.ScreensImpl.show(ScreensImpl.java:357)
at io.jmix.ui.sys.ScreensImpl.showFromNavigation(ScreensImpl.java:452)
at io.jmix.ui.menu.MenuItemCommands$ScreenCommand.run(MenuItemCommands.java:193)
at io.jmix.ui.menu.SideMenuBuilder$MenuCommandExecutor.accept(SideMenuBuilder.java:272)
at io.jmix.ui.menu.SideMenuBuilder$MenuCommandExecutor.accept(SideMenuBuilder.java:257)
.

.

.

How can I avoid that?

Thanks in advance.

Ramiro.

Jmix version: 1.7.3
Jmix Studio Plugin Version: 2.6.4-243
IntelliJ version: IntelliJ IDEA 2024.3.7.1 (Community Edition)

I forgot to explain the fetch plan used here. This screen is a fragment that uses a collection provided by its host screen, as below:

Host screen:

<data>
    <collection id="documentsDc"
                class="com.calidus.pdcs.entity.documents.Document"
                fetchPlan="document-browsing-fetch-plan">
        <loader id="documentsDl">
            <query>
                <![CDATA[select doc from Document doc]]>
            </query>
        </loader>
    </collection>
</data>

Fragment:

<data>
    <collection id="documentsDc"
                class="com.calidus.pdcs.entity.documents.Document"
                provided="true">
    </collection>
</data>

Finally, the fetch plan is:

<fetchPlan entity="Document" name="document-browsing-fetch-plan" extends="_base">
</fetchPlan>

As one can see, I do not specify any references to be eagerly loaded via the fetch plan. My objective is that all related entities are loaded lazily, but that is not what is happening.

Hi Ramiro,
Please provide the source code of the Document.dovDocs attribute with all annotations.

Regards,
Konstantin

Here is my Document entity, Konstantin. The property is Document.docDovs (the document versions for a document):

package com.calidus.pdcs.entity.documents;

import com.calidus.pdcs.entity.activities.Activity;
import com.calidus.pdcs.entity.products.Product;
import io.jmix.core.DeletePolicy;
import io.jmix.core.entity.annotation.CaseConversion;
import io.jmix.core.entity.annotation.JmixGeneratedValue;
import io.jmix.core.entity.annotation.OnDeleteInverse;
import io.jmix.core.metamodel.annotation.Comment;
import io.jmix.core.metamodel.annotation.Composition;
import io.jmix.core.metamodel.annotation.InstanceName;
import io.jmix.core.metamodel.annotation.JmixEntity;

import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

@JmixEntity
@Table(name = "DOCUMENT", indexes = {
        @Index(name = "IDX_DOCUMENT_DOC_PRO", columnList = "DOC_PRO_ID"),
        @Index(name = "IDX_DOCUMENT_DOC_ACT", columnList = "DOC_ACT_ID")
}, uniqueConstraints = {
        @UniqueConstraint(name = "IDX_DOCUMENT_UNQ_DOC_CODE", columnNames = {"DOC_CODE"})
})
@Entity
public class Document {
    @JmixGeneratedValue
    @Column(name = "ID", nullable = false)
    @Id
    private UUID id;

    @Comment("Product related to this document")
    @OnDeleteInverse(DeletePolicy.DENY)
    @JoinColumn(name = "DOC_PRO_ID")
    @ManyToOne(fetch = FetchType.LAZY)
    private Product docPro;

    @Column(name = "VERSION", nullable = false)
    @Version
    private Integer version;

    @Comment("Type of document.")
    @Column(name = "DOC_TYPE", nullable = false)
    @NotNull
    private Integer docType;

    @Comment("User defined code (identification) for document.")
    @CaseConversion
    @Column(name = "DOC_CODE", nullable = false, length = 50)
    @NotNull
    @InstanceName
    private String docCode;

    @Comment("Document title.")
    @Lob
    @Column(name = "DOC_TITLE", nullable = false)
    @NotNull
    private String docTitle;

    @Comment("All document versions existing for this document.")
    @Composition
    @OrderBy("dovId")
    @OneToMany(mappedBy = "dovDoc", cascade = CascadeType.REMOVE)
    private List<DocVersion> docDovs = new ArrayList<>();

    @Comment("Related activity")
    @OnDeleteInverse(DeletePolicy.DENY)
    @JoinColumn(name = "DOC_ACT_ID")
    @ManyToOne(fetch = FetchType.LAZY)
    private Activity docAct;

    public Activity getDocAct() {
        return docAct;
    }

    public void setDocAct(Activity docAct) {
        this.docAct = docAct;
    }

    public Product getDocPro() {
        return docPro;
    }

    public void setDocPro(Product docPro) {
        this.docPro = docPro;
    }

    public List<DocVersion> getDocDovs() {
        return docDovs;
    }
    public void setDocDovs(List<DocVersion> docDovs) {
        this.docDovs = docDovs;
    }

    public String getDocCode() {
        return docCode;
    }

    public void setDocCode(String docCode) {
        this.docCode = docCode;
    }

    public String getDocTitle() {
        return docTitle;
    }

    public void setDocTitle(String docTitle) {
        this.docTitle = docTitle;
    }

    public DocType getDocType() {
        return docType == null ? null : DocType.fromId(docType);
    }

    public void setDocType(DocType docType) {
        this.docType = docType == null ? null : docType.getId();
    }

    public Integer getVersion() {
        return version;
    }

    public void setVersion(Integer version) {
        this.version = version;
    }

    public UUID getId() {
        return id;
    }

    public void setId(UUID id) {
        this.id = id;
    }

}

Remove new ArrayList<>() initialization from the field, or replace it with io.jmix.data.impl.lazyloading.NotInstantiatedList. See Entities :: Jmix Documentation.

If it doesn’t help, attach also the DocVersion source code.

Regards,
Konstantin

It solved the problem, Konstantin!

Now all queries for DocVersion have disappeared.

I would have struggled a lot to find the solution without your help. Thanks a lot!

1 Like