Prevent query by ID and perform JOIN instead

Hi, in Jmix 2.0 I have an entity (“Person”) with a variable Author which has a FK to a Person itself.
In the detail view of a Person I want to show the name and the surname of the Person related to the Author of the main Person.
I did achive that but the query applies a correct JOIN on the table Author but not to the Person related to the author. Instead it performs a separated query by ID and I need to prevent that.

In the entity Author:

  @InstanceName
    @DependsOnProperties({"person"})
    public String getInstanceName(){
       return person.getInstanceName();
    }

In the entity Person:

@InstanceName
    @DependsOnProperties({"surname", "name"})
    public String getInstanceName(){
        if(surname!=null && name!=null) {
            return surname + " " + name;
        }
        return null;
    }

In the fetch plan tag of the detail view I tried both

<property name="autore" fetch="JOIN" fetchPlan="_instance_name"/>

and

<property name="autore" fetch="JOIN" fetchPlan="_base"/>

but the result is the same.

Thank you in advance.

Hi Andrea,
Please provide your exact data model, or better a test project that demonstrates the issue.

Also, please explain why you are so concerned by the separate join? If it’s only in detail view, then it’s executed just once, so its impact on performance is usually negligible.

Regards,
Konstantin

Hi Konstantin,
these are my models:

PfPersona (which is a Person)

package com.company.personefisiche.entity;

import io.jmix.core.metamodel.annotation.DependsOnProperties;
import io.jmix.core.metamodel.annotation.InstanceName;
import io.jmix.core.metamodel.annotation.JmixEntity;
import io.jmix.core.metamodel.annotation.Store;
import io.jmix.data.DdlGeneration;
import jakarta.persistence.*;
import org.apache.commons.lang3.StringUtils;

import java.math.BigDecimal;
import java.util.Date;

@DdlGeneration(value = DdlGeneration.DbScriptGenerationMode.DISABLED,
        unmappedColumns = {"r_stato_normalizzazione", "d_stato_normalizzazione", "cittadinanze"})
@JmixEntity
@Store(name = "personefisiche")
@Table(name = "pf_persona", schema = "ana")
@Entity
public class PfPersona {

    public String getResidenzaIndirizzoCompleto() {

        StringBuilder stringBuffer = new StringBuilder();
        if (StringUtils.isNotBlank(getIndirizzoSenzaComune())) {
            stringBuffer.append(getIndirizzoSenzaComune());
        }
        if (rComune != null && StringUtils.isNotBlank(rComune.getDenominazione())) {
            stringBuffer.append(" " + rComune.getDenominazione());
            if (StringUtils.isNotBlank(rComune.getSigla())) {
                stringBuffer.append(" (" + rComune.getSigla() + ") ");
            }
        }
        return stringBuffer.toString();
    }

    public String getIndirizzoSenzaComune() {

        StringBuilder stringBuffer = new StringBuilder();
        if (StringUtils.isNotBlank(rIndirizzo)) {
            stringBuffer.append(rIndirizzo);
        }
        if (StringUtils.isNotBlank(rNumeroCivico)) {
            stringBuffer.append(" n. " + rNumeroCivico);
        }
        if (StringUtils.isNotBlank(rLocalita)) {
            stringBuffer.append(" - " + rLocalita);
        }
        if (StringUtils.isNotBlank(rCap)) {
            stringBuffer.append(", " + rCap);
        }

        return stringBuffer.toString();
    }

    @Column(name = "id", nullable = false)
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @Column(name = "anart_id", length = 24)
    private String anart;

    @Column(name = "aur_id", unique = true, length = 24)
    private String aur;

    @JoinColumn(name = "autore", nullable = false)
    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    private Agente autore;

    @JoinColumn(name = "autore_inserimento", nullable = true)
    @ManyToOne(fetch = FetchType.LAZY, optional = true)
    private Agente autoreInserimento;

    @Column(name = "categoria", length = 10)
    private String categoria;

    @Column(name = "cellulare", length = 200)
    private String cellulare;

    @Column(name = "cittadinanza", length = 100)
    private String cittadinanza;

    @Column(name = "codice_fiscale", unique = true, length = 16)
    private String codiceFiscale;

    @Column(name = "cognome", nullable = false, length = 100)
    private String cognome;

    @JoinColumn(name = "comune_n")
    @ManyToOne(fetch = FetchType.LAZY)
    private IstatComune comuneN;

    @Column(name = "d_cap", length = 5)
    private String dCap;

    @JoinColumn(name = "d_comune")
    @ManyToOne(fetch = FetchType.LAZY)
    private IstatComune dComune;

    @Column(name = "d_coordinata_gauss_boaga_x", precision = 15, scale = 8)
    private BigDecimal dCoordinataGaussBoagaX;

    @Column(name = "d_coordinata_gauss_boaga_y", precision = 15, scale = 8)
    private BigDecimal dCoordinataGaussBoagaY;

    @Temporal(TemporalType.DATE)
    @Column(name = "d_data_inizio")
    private Date dDataInizio;

    @Column(name = "d_indirizzo", length = 200)
    private String dIndirizzo;

    @Column(name = "d_latitudine_wgs84", precision = 15, scale = 8)
    private BigDecimal dLatitudineWgs84;

    @Column(name = "d_localita")
    private String dLocalita;

    @Column(name = "d_longitudine_wgs84", precision = 15, scale = 8)
    private BigDecimal dLongitudineWgs84;

    @Column(name = "d_numero_civico", length = 20)
    private String dNumeroCivico;

    @JoinColumn(name = "d_stato")
    @ManyToOne(fetch = FetchType.LAZY)
    private IstatStato dStato;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "data_aggiornamento_orfea")
    private Date dataAggiornamentoOrfea;

    @Temporal(TemporalType.DATE)
    @Column(name = "data_decesso")
    private Date dataDecesso;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "data_inserimento")
    private Date dataInserimento;

    @Temporal(TemporalType.DATE)
    @Column(name = "data_nascita", nullable = false)
    private Date dataNascita;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "data_registrazione", nullable = false)
    private Date dataRegistrazione;

    @Column(name = "email", length = 200)
    private String email;

    @Column(name = "email_verificato")
    private Boolean emailVerificato;

    @JoinColumn(name = "ente_certificatore_id", nullable = false)
    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    private PfgEnteCertificatore enteCertificatore;

    @Column(name = "errore_censimento_orfea", nullable = false)
    private Boolean erroreCensimentoOrfea = false;

    @JoinColumn(name = "etnia_id")
    @ManyToOne(fetch = FetchType.LAZY)
    private PfEtnia etnia;

    @Column(name = "fax", length = 50)
    private String fax;

    @Column(name = "medico_id")
    private Integer medico;

    @Column(name = "nome", nullable = false, length = 100)
    private String nome;

    @Column(name = "offline")
    private Integer offline;

    @Column(name = "origine_cambio_stato_offline")
    private String origineCambioStatoOffline;

    @Column(name = "posizione", length = 2)
    private String posizione;

    @JoinColumn(name = "qualita_cf_orfea")
    @ManyToOne(fetch = FetchType.LAZY)
    private PfTipoQualitaCfOrfea qualitaCfOrfea;

    @JoinColumn(name = "qualita_tratti_orfea")
    @ManyToOne(fetch = FetchType.LAZY)
    private PfTipoQualitaTrattiOrfea qualitaTrattiOrfea;

    @Column(name = "r_cap", length = 5)
    private String rCap;

    @JoinColumn(name = "r_comune")
    @ManyToOne(fetch = FetchType.LAZY)
    private IstatComune rComune;

    @Column(name = "r_coordinata_gauss_boaga_x", precision = 15, scale = 8)
    private BigDecimal rCoordinataGaussBoagaX;

    @Column(name = "r_coordinata_gauss_boaga_y", precision = 15, scale = 8)
    private BigDecimal rCoordinataGaussBoagaY;

    @Temporal(TemporalType.DATE)
    @Column(name = "r_data_inizio")
    private Date rDataInizio;

    @Column(name = "r_indirizzo", length = 200)
    private String rIndirizzo;

    @Column(name = "r_latitudine_wgs84", precision = 15, scale = 8)
    private BigDecimal rLatitudineWgs84;

    @Column(name = "r_localita")
    private String rLocalita;

    @Column(name = "r_longitudine_wgs84", precision = 15, scale = 8)
    private BigDecimal rLongitudineWgs84;

    @Column(name = "r_numero_civico", length = 20)
    private String rNumeroCivico;

    @JoinColumn (name = "r_stato")
    @ManyToOne (fetch = FetchType.LAZY)
    private IstatStato rStato;

    @Column(name = "sesso", nullable = false, length = 2)
    private String sesso;

    @Column(name = "sispc_id", nullable = false, unique = true)
    private Integer sispc;

    @Column(name = "stato_civile", length = 1)
    private String statoCivile;

    @JoinColumn(name = "stato_identita_id")
    @ManyToOne(fetch = FetchType.LAZY)
    private PfStatoIdentita statoIdentita;

    @JoinColumn(name = "stato_n")
    @ManyToOne(fetch = FetchType.LAZY)
    private IstatStato statoN;

    @Column(name = "telefono", length = 50)
    private String telefono;

    @Column(name = "telefono_aziendale", length = 50)
    private String telefonoAziendale;

    @Column(name = "titolo_studio", length = 1)
    private String titoloStudio;


    public void setAutore(Agente autore) {
        this.autore = autore;
    }

    public Agente getAutore() {
        return autore;
    }

    public void setStatoN(IstatStato statoN) {
        this.statoN = statoN;
    }

    public IstatStato getStatoN() {
        return statoN;
    }

    public void setComuneN(IstatComune comuneN) {
        this.comuneN = comuneN;
    }

    public IstatComune getComuneN() {
        return comuneN;
    }

    public Integer getZversione() {
        return zversione;
    }

    public void setZversione(Integer zversione) {
        this.zversione = zversione;
    }

    public String getZtesseraSan() {
        return ztesseraSan;
    }

    public void setZtesseraSan(String ztesseraSan) {
        this.ztesseraSan = ztesseraSan;
    }

    public String getTitoloStudio() {
        return titoloStudio;
    }

    public void setTitoloStudio(String titoloStudio) {
        this.titoloStudio = titoloStudio;
    }

    public String getTelefonoAziendale() {
        return telefonoAziendale;
    }

    public void setTelefonoAziendale(String telefonoAziendale) {
        this.telefonoAziendale = telefonoAziendale;
    }

    public String getTelefono() {
        return telefono;
    }

    public void setTelefono(String telefono) {
        this.telefono = telefono;
    }

    public PfStatoIdentita getStatoIdentita() {
        return statoIdentita;
    }

    public void setStatoIdentita(PfStatoIdentita statoIdentita) {
        this.statoIdentita = statoIdentita;
    }

    public String getStatoCivile() {
        return statoCivile;
    }

    public void setStatoCivile(String statoCivile) {
        this.statoCivile = statoCivile;
    }

    public Integer getSispc() {
        return sispc;
    }

    public void setSispc(Integer sispc) {
        this.sispc = sispc;
    }

    public String getSesso() {
        return sesso;
    }

    public void setSesso(String sesso) {
        this.sesso = sesso;
    }

    public IstatStato getRStato() {
        return rStato;
    }

    public void setRStato(IstatStato rStato) {
        this.rStato = rStato;
    }

    public String getRNumeroCivico() {
        return rNumeroCivico;
    }

    public void setRNumeroCivico(String rNumeroCivico) {
        this.rNumeroCivico = rNumeroCivico;
    }

    public BigDecimal getRLongitudineWgs84() {
        return rLongitudineWgs84;
    }

    public void setRLongitudineWgs84(BigDecimal rLongitudineWgs84) {
        this.rLongitudineWgs84 = rLongitudineWgs84;
    }

    public String getRLocalita() {
        return rLocalita;
    }

    public void setRLocalita(String rLocalita) {
        this.rLocalita = rLocalita;
    }

    public BigDecimal getRLatitudineWgs84() {
        return rLatitudineWgs84;
    }

    public void setRLatitudineWgs84(BigDecimal rLatitudineWgs84) {
        this.rLatitudineWgs84 = rLatitudineWgs84;
    }

    public String getRIndirizzo() {
        return rIndirizzo;
    }

    public void setRIndirizzo(String rIndirizzo) {
        this.rIndirizzo = rIndirizzo;
    }

    public Date getRDataInizio() {
        return rDataInizio;
    }

    public void setRDataInizio(Date rDataInizio) {
        this.rDataInizio = rDataInizio;
    }

    public BigDecimal getRCoordinataGaussBoagaY() {
        return rCoordinataGaussBoagaY;
    }

    public void setRCoordinataGaussBoagaY(BigDecimal rCoordinataGaussBoagaY) {
        this.rCoordinataGaussBoagaY = rCoordinataGaussBoagaY;
    }

    public BigDecimal getRCoordinataGaussBoagaX() {
        return rCoordinataGaussBoagaX;
    }

    public void setRCoordinataGaussBoagaX(BigDecimal rCoordinataGaussBoagaX) {
        this.rCoordinataGaussBoagaX = rCoordinataGaussBoagaX;
    }

    public IstatComune getRComune() {
        return rComune;
    }

    public void setRComune(IstatComune rComune) {
        this.rComune = rComune;
    }

    public String getRCap() {
        return rCap;
    }

    public void setRCap(String rCap) {
        this.rCap = rCap;
    }

    public PfTipoQualitaTrattiOrfea getQualitaTrattiOrfea() {
        return qualitaTrattiOrfea;
    }

    public void setQualitaTrattiOrfea(PfTipoQualitaTrattiOrfea qualitaTrattiOrfea) {
        this.qualitaTrattiOrfea = qualitaTrattiOrfea;
    }

    public PfTipoQualitaCfOrfea getQualitaCfOrfea() {
        return qualitaCfOrfea;
    }

    public void setQualitaCfOrfea(PfTipoQualitaCfOrfea qualitaCfOrfea) {
        this.qualitaCfOrfea = qualitaCfOrfea;
    }

    public String getOrigineCambioStatoOffline() {
        return origineCambioStatoOffline;
    }

    public void setOrigineCambioStatoOffline(String origineCambioStatoOffline) {
        this.origineCambioStatoOffline = origineCambioStatoOffline;
    }

    public Integer getOffline() {
        return offline;
    }

    public void setOffline(Integer offline) {
        this.offline = offline;
    }

    public String getNome() {
        return nome;
    }

    public void setNome(String nome) {
        this.nome = nome;
    }

    public Integer getMedico() {
        return medico;
    }

    public void setMedico(Integer medico) {
        this.medico = medico;
    }

    public String getFax() {
        return fax;
    }

    public void setFax(String fax) {
        this.fax = fax;
    }

    public PfEtnia getEtnia() {
        return etnia;
    }

    public void setEtnia(PfEtnia etnia) {
        this.etnia = etnia;
    }

    public Boolean getErroreCensimentoOrfea() {
        return erroreCensimentoOrfea;
    }

    public void setErroreCensimentoOrfea(Boolean erroreCensimentoOrfea) {
        this.erroreCensimentoOrfea = erroreCensimentoOrfea;
    }

    public PfgEnteCertificatore getEnteCertificatore() {
        return enteCertificatore;
    }

    public void setEnteCertificatore(PfgEnteCertificatore enteCertificatore) {
        this.enteCertificatore = enteCertificatore;
    }

    public Boolean getEmailVerificato() {
        return emailVerificato;
    }

    public void setEmailVerificato(Boolean emailVerificato) {
        this.emailVerificato = emailVerificato;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Date getDataRegistrazione() {
        return dataRegistrazione;
    }

    public void setDataRegistrazione(Date dataRegistrazione) {
        this.dataRegistrazione = dataRegistrazione;
    }

    public Date getDataNascita() {
        return dataNascita;
    }

    public void setDataNascita(Date dataNascita) {
        this.dataNascita = dataNascita;
    }

    public Date getDataInserimento() {
        return dataInserimento;
    }

    public void setDataInserimento(Date dataInserimento) {
        this.dataInserimento = dataInserimento;
    }

    public Date getDataDecesso() {
        return dataDecesso;
    }

    public void setDataDecesso(Date dataDecesso) {
        this.dataDecesso = dataDecesso;
    }

    public Date getDataAggiornamentoOrfea() {
        return dataAggiornamentoOrfea;
    }

    public void setDataAggiornamentoOrfea(Date dataAggiornamentoOrfea) {
        this.dataAggiornamentoOrfea = dataAggiornamentoOrfea;
    }

    public IstatStato getDStato() {
        return dStato;
    }

    public void setDStato(IstatStato dStato) {
        this.dStato = dStato;
    }

    public String getDNumeroCivico() {
        return dNumeroCivico;
    }

    public void setDNumeroCivico(String dNumeroCivico) {
        this.dNumeroCivico = dNumeroCivico;
    }

    public BigDecimal getDLongitudineWgs84() {
        return dLongitudineWgs84;
    }

    public void setDLongitudineWgs84(BigDecimal dLongitudineWgs84) {
        this.dLongitudineWgs84 = dLongitudineWgs84;
    }

    public String getDLocalita() {
        return dLocalita;
    }

    public void setDLocalita(String dLocalita) {
        this.dLocalita = dLocalita;
    }

    public BigDecimal getDLatitudineWgs84() {
        return dLatitudineWgs84;
    }

    public void setDLatitudineWgs84(BigDecimal dLatitudineWgs84) {
        this.dLatitudineWgs84 = dLatitudineWgs84;
    }

    public String getDIndirizzo() {
        return dIndirizzo;
    }

    public void setDIndirizzo(String dIndirizzo) {
        this.dIndirizzo = dIndirizzo;
    }

    public Date getDDataInizio() {
        return dDataInizio;
    }

    public void setDDataInizio(Date dDataInizio) {
        this.dDataInizio = dDataInizio;
    }

    public BigDecimal getDCoordinataGaussBoagaY() {
        return dCoordinataGaussBoagaY;
    }

    public void setDCoordinataGaussBoagaY(BigDecimal dCoordinataGaussBoagaY) {
        this.dCoordinataGaussBoagaY = dCoordinataGaussBoagaY;
    }

    public BigDecimal getDCoordinataGaussBoagaX() {
        return dCoordinataGaussBoagaX;
    }

    public void setDCoordinataGaussBoagaX(BigDecimal dCoordinataGaussBoagaX) {
        this.dCoordinataGaussBoagaX = dCoordinataGaussBoagaX;
    }

    public IstatComune getDComune() {
        return dComune;
    }

    public void setDComune(IstatComune dComune) {
        this.dComune = dComune;
    }

    public String getDCap() {
        return dCap;
    }

    public void setDCap(String dCap) {
        this.dCap = dCap;
    }

    public String getCognome() {
        return cognome;
    }

    public void setCognome(String cognome) {
        this.cognome = cognome;
    }

    public String getCodiceFiscale() {
        return codiceFiscale;
    }

    public void setCodiceFiscale(String codiceFiscale) {
        this.codiceFiscale = codiceFiscale;
    }

    public String getCellulare() {
        return cellulare;
    }

    public void setCellulare(String cellulare) {
        this.cellulare = cellulare;
    }


    public Agente getAutoreInserimento() {
        return autoreInserimento;
    }

    public void setAutoreInserimento(Agente autoreInserimento) {
        this.autoreInserimento = autoreInserimento;
    }

    public String getAur() {
        return aur;
    }

    public void setAur(String aur) {
        this.aur = aur;
    }

    public String getAnart() {
        return anart;
    }

    public void setAnart(String anart) {
        this.anart = anart;
    }

    public Integer getId() {
        return id;
    }

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

    @InstanceName
    @DependsOnProperties({"cognome", "nome"})
    public String getInstanceName(){
        if(cognome!=null && nome!=null) {
            return cognome + " " + nome;
        }
        return null;
    }
}

and Agente which is an Author:

import java.util.Date;

@DdlGeneration(value = DdlGeneration.DbScriptGenerationMode.DISABLED)
@JmixEntity
@Store(name = "personefisiche")
@Table(name = "agente", schema = "gaa")
@Entity
public class Agente {
    @Column(name = "id", nullable = false)
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @JoinColumn(name = "agente_tipo_id", nullable = false)
    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    private AgenteTipo agenteTipo;

    @JoinColumn(name = "amministrazione_codice")
    @ManyToOne(fetch = FetchType.LAZY)
    private AoAmministrazione amministrazioneCodice;

    @Column(name = "amministrazione_codice_backup", length = 4)
    private String amministrazioneCodiceBackup;


    @Column(name = "autore", nullable = false)
    private Integer autore;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "data_registrazione", nullable = false)
    private Date dataRegistrazione;

    @Column(name = "email_agente", length = 200)
    private String emailAgente;

    @Column(name = "matricola", length = 20)
    private String matricola;

    @JoinColumn(name = "persona_id", nullable = false, unique = true)
    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    private PfPersona persona;

    @JoinColumn(name = "qualifica_id")
    @ManyToOne(fetch = FetchType.LAZY)
    private AgenteTipoQualifica qualifica;

    @JoinColumn(name = "richiesta_accesso_id", unique = true)
    @ManyToOne(fetch = FetchType.LAZY)
    private PoRichiestaAccesso richiestaAccesso;

    @JoinColumn(name = "ruolo_id", nullable = false, unique = true)
    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    private PoTipoRuolo ruolo;

    @Column(name = "sigla", length = 6)
    private String sigla;

    @JoinColumn(name = "stato_attuale_id", nullable = false)
    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    private PoStato statoAttuale;


    @JoinColumn(name = "tipo_responsabilita_id", nullable = false)
    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    private AgenteTipoResponsabilita tipoResponsabilita;

    @JoinColumn(name = "titolare_id")
    @ManyToOne(fetch = FetchType.LAZY)
    private Agente titolare;

 


    public AgenteTipoResponsabilita getTipoResponsabilita() {
        return tipoResponsabilita;
    }

    public void setTipoResponsabilita(AgenteTipoResponsabilita tipoResponsabilita) {
        this.tipoResponsabilita = tipoResponsabilita;
    }
    public PoRichiestaAccesso getRichiestaAccesso() {
        return richiestaAccesso;
    }

    public void setRichiestaAccesso(PoRichiestaAccesso richiestaAccesso) {
        this.richiestaAccesso = richiestaAccesso;
    }

    public AgenteTipoQualifica getQualifica() {
        return qualifica;
    }

    public void setQualifica(AgenteTipoQualifica qualifica) {
        this.qualifica = qualifica;
    }

    public PfPersona getPersona() {
        return persona;
    }

    public void setPersona(PfPersona persona) {
        this.persona = persona;
    }

    public String getEmailAgente() {
        return emailAgente;
    }

    public void setEmailAgente(String emailAgente) {
        this.emailAgente = emailAgente;
    }

    public Date getDataRegistrazione() {
        return dataRegistrazione;
    }

    public void setDataRegistrazione(Date dataRegistrazione) {
        this.dataRegistrazione = dataRegistrazione;
    }

    public Integer getAutore() {
        return autore;
    }

    public void setAutore(Integer autore) {
        this.autore = autore;
    }


    public AgenteTipo getAgenteTipo() {
        return agenteTipo;
    }

    public void setAgenteTipo(AgenteTipo agenteTipo) {
        this.agenteTipo = agenteTipo;
    }

    public Integer getId() {
        return id;
    }

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

}

This is the detail view at the moment (I didn’t complete it but for the fetch plan problem will do):

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<view xmlns="http://jmix.io/schema/flowui/view"
      title="msg://pfPersonaDetailView.title"
      focusComponent="form">
    <data>
        <instance id="pfPersonaDc"
                  class="com.company.personefisiche.entity.PfPersona">
            <fetchPlan extends="_base">
                <property fetch="JOIN" name="rComune" fetchPlan="_instance_name"/>
                <property fetch="JOIN" name="comuneN" fetchPlan="_instance_name"/>
                <property fetch="JOIN" name="dComune" fetchPlan="_instance_name"/>
                <property fetch="JOIN" name="dStato" fetchPlan="_instance_name"/>
                <property fetch="JOIN" name="rStato" fetchPlan="_instance_name"/>
                <property fetch="JOIN" name="statoN" fetchPlan="_instance_name"/>
                <property fetch="JOIN" name="qualitaCfOrfea" fetchPlan="_instance_name"/>
                <property fetch="JOIN" name="qualitaTrattiOrfea" fetchPlan="_instance_name"/>
                <property name="autore" fetch="JOIN" fetchPlan="_base">
                    <property name="persona" fetch="JOIN" fetchPlan="_instance_name"/>
                </property>
                <property name="autoreInserimento" fetch="JOIN" fetchPlan="_base">
                    <property name="persona" fetch="JOIN" fetchPlan="_instance_name"/>
                </property>
            </fetchPlan>
            <loader id="pfPersonaDl">
            </loader>
        </instance>
    </data>
    <facets>
        <dataLoadCoordinator auto="true"/>
    </facets>
    <actions>
        <action id="saveAction" type="detail_saveClose"/>
        <action id="closeAction" type="detail_close"/>
    </actions>
    <layout>

        <formLayout id="form" dataContainer="pfPersonaDc">
            <responsiveSteps>
                <responsiveStep minWidth="0" columns="1"/>
                <responsiveStep minWidth="40em" columns="2"/>
            </responsiveSteps>

            <entityPicker id="qualitaCfOrfeaField" property="qualitaCfOrfea" readOnly="true">
                <actions>

                    <action id="entityClearAction" type="entity_clear"/>
                </actions>
            </entityPicker>
            <entityPicker id="qualitaTrattiOrfeaField" property="qualitaTrattiOrfea" readOnly="true">
                <actions>

                    <action id="entityClearAction" type="entity_clear"/>
                </actions>
            </entityPicker>
            <vbox classNames="bordered-panel" colspan="2">
                <h4 text="Tratti"/>
                    <div classNames="flex flex-row flex-wrap gap-m">
                        <textField label="Nome" id="nomeField" property="nome"/>
                        <textField label="Cognome" id="cognomeField" property="cognome"/>
                        <textField label="Sesso" id="sessoField" property="sesso"/>
                        <datePicker label="Data nascita" id="dataNascitaField" property="dataNascita"/>
                        <entityPicker label ="Comune nascita" id="comuneNField" property="comuneN">
                            <actions>
                                <action id="entityLookupAction" type="entity_lookup"/>
                                <action id="entityClearAction" type="entity_clear"/>
                            </actions>
                        </entityPicker>
                        <!--<textField label="Comune nascita" id="comuneNField" property="comuneN"/>-->
                        <entityPicker label="Stato nascita" id="statoNField" property="statoN">
                            <actions>
                                <action id="entityLookupAction" type="entity_lookup"/>
                                <action id="entityClearAction" type="entity_clear"/>
                            </actions>
                        </entityPicker>
                        <datePicker label="Data decesso" id="dataDecessoField" property="dataDecesso"/>
                    </div>
            </vbox>
            <textField id="codiceFiscaleField" property="codiceFiscale"/>

            <vbox classNames="bordered-panel" colspan="2">
                <h4 text="Residenza"/>
                <div classNames="flex flex-row flex-wrap gap-m">
                    <textField label="Indirizzo" id="rIndirizzoField" property="rIndirizzo" />
                    <textField label="N° civico" id="rNumeroCivicoField" property="rNumeroCivico"/>
                    <entityPicker label="Comune" id="rComuneField" property="rComune">
                        <actions>
                            <action id="entityLookupAction" type="entity_lookup"/>
                            <action id="entityClearAction" type="entity_clear"/>
                        </actions>
                    </entityPicker>
                    <textField label="CAP" id="rCapField" property="rCap" />
                    <entityPicker label="Stato" id="rStatoField" property="rStato">
                        <actions>
                            <action id="entityLookupAction" type="entity_lookup"/>
                            <action id="entityClearAction" type="entity_clear"/>
                        </actions>
                    </entityPicker>
                    <textField label="Località" id="rLocalitaField" property="rLocalita"/>
                    <bigDecimalField label="Coord. GB X" id="rCoordinataGaussBoagaXField" property="rCoordinataGaussBoagaX"/>
                    <bigDecimalField label="Coord. GB Y" id="rCoordinataGaussBoagaYField" property="rCoordinataGaussBoagaY"/>
                    <bigDecimalField label="Latitudine" id="rLatitudineWgs84Field" property="rLatitudineWgs84"/>
                    <bigDecimalField label="Longitudine" property="rLongitudineWgs84"/>
                    <datePicker label="Data inizio" id="rDataInizioField" property="rDataInizio"/>
                </div>
            </vbox>

            <integerField id="idField" property="id" readOnly="true"/>
            <textField id="anartField" property="anart" readOnly="true"/>

            <textField id="statoCivileField" property="statoCivile"/>
            <textField id="titoloStudioField" property="titoloStudio"/>
            <!-- MANCANO LE CITTADINANZE -->
            <textField id="telefonoField" property="telefono"/>
            <textField id="cellulareField" property="cellulare"/>
            <textField id="emailField" property="email"/>

          
            <textField id="medicoField" property="medico" readOnly="true"/>
            <textField label="Autore inserimento" id="autoreInserimentoField" property="autoreInserimento.persona" readOnly="true"/>
            <dateTimePicker id="dataInserimentoField" property="dataInserimento" readOnly="true"/>
            <textField label="Autore Modifica" id="autoreField" property="autore.persona" readOnly="true"/>
            <dateTimePicker id="dataRegistrazioneField" property="dataRegistrazione" readOnly="true"/>
            <dateTimePicker id="dataAggiornamentoOrfeaField" property="dataAggiornamentoOrfea" readOnly="true"/>
        </formLayout>
        <hbox id="detailActions">
            <button id="saveAndCloseButton" action="saveAction"/>
            <button id="closeButton" action="closeAction"/>
        </hbox>
    </layout>
</view>

I am concerned because I tried to put the exact same information on the list-view as a column and also there a findbyId is performed for every row. Also,we reverse engineered a HUGE postgresql Db (talking about millions record for hundreds of tables) and we already have an old application with a lot of query written for optimize the performances and we cannot change them. We really need (in most cases) to just reproduce them as they are. In this case we need a join (or left join for “autoreInserimento”).

I don’t understand why an extra query is performed and I don’t know how to say to the system “just do a join”.

Thank you for your time.

I couldn’t post all the code because it exceeded the 32,000 character limit.

Thank you for the explanation, now I understand your concerns.

The first idea that came to my mind is to remove nullable = false and optional = false from autore attribute annotations. Please try this and let me know about results.

Regards,
Konstantin

Hi,
I tried that but unfortunately nothing changed

OK, let’s try to use logging.
Set the following properties:

logging.level.io.jmix.eclipselink.impl.FetchGroupManager=trace
logging.level.io.jmix.core.datastore=debug
logging.level.eclipselink.logging.sql=debug

And attach here the log content right after opening the detail view.

Regards,
Konstantin

Here’s the log content:

025-09-04T15:19:55.681+02:00 DEBUG 76662 --- [nio-8080-exec-7] i.jmix.core.datastore.AbstractDataStore  : load: store=personefisiche, metaClass=PfPersona, id=1, fetchPlan=com.company.personefisiche.entity.PfPersona/
2025-09-04T15:19:55.689+02:00 TRACE 76662 --- [nio-8080-exec-7] i.j.eclipselink.impl.FetchGroupManager   : Fetch group for com.company.personefisiche.entity.PfPersona/:
anart
annullato
aur
autore
autore.agenteTipo
autore.amministrazioneCodice
autore.amministrazioneCodiceBackup
autore.annullato
autore.autore
autore.dataRegistrazione
autore.emailAgente
autore.id
autore.matricola
autore.persona
autore.persona.anart
autore.persona.annullato
autore.persona.aur
autore.persona.autore
autore.persona.autoreInserimento
autore.persona.categoria
autore.persona.cellulare
autore.persona.cittadinanza
autore.persona.codiceFiscale
autore.persona.cognome
autore.persona.comuneN
autore.persona.dCap
autore.persona.dComune
autore.persona.dCoordinataGaussBoagaX
autore.persona.dCoordinataGaussBoagaY
autore.persona.dDataInizio
autore.persona.dIndirizzo
autore.persona.dLatitudineWgs84
autore.persona.dLocalita
autore.persona.dLongitudineWgs84
autore.persona.dNumeroCivico
autore.persona.dStato
autore.persona.dataAggiornamentoOrfea
autore.persona.dataDecesso
autore.persona.dataInserimento
autore.persona.dataNascita
autore.persona.dataRegistrazione
autore.persona.email
autore.persona.emailVerificato
autore.persona.enteCertificatore
autore.persona.erroreCensimentoOrfea
autore.persona.etnia
autore.persona.fax
autore.persona.id
autore.persona.medico
autore.persona.nome
autore.persona.offline
autore.persona.origineCambioStatoOffline
autore.persona.posizione
autore.persona.qualitaCfOrfea
autore.persona.qualitaTrattiOrfea
autore.persona.rCap
autore.persona.rComune
autore.persona.rCoordinataGaussBoagaX
autore.persona.rCoordinataGaussBoagaY
autore.persona.rDataInizio
autore.persona.rIndirizzo
autore.persona.rLatitudineWgs84
autore.persona.rLocalita
autore.persona.rLongitudineWgs84
autore.persona.rNumeroCivico
autore.persona.rStato
autore.persona.sesso
autore.persona.sispc
autore.persona.statoCivile
autore.persona.statoIdentita
autore.persona.statoN
autore.persona.telefono
autore.persona.telefonoAziendale
autore.persona.titoloStudio
autore.persona.zcognomeCongiunto
autore.persona.zflagCoerente
autore.persona.zmadre
autore.persona.zpadre
autore.persona.ztesseraSan
autore.persona.zversione
autore.qualifica
autore.richiestaAccesso
autore.ruolo
autore.sigla
autore.statoAttuale
autore.superAdmin
autore.telefonoAgente
autore.tipoResponsabilita
autore.titolare
autore.ultimoAccesso
autoreInserimento
autoreInserimento.agenteTipo
autoreInserimento.amministrazioneCodice
autoreInserimento.amministrazioneCodiceBackup
autoreInserimento.annullato
autoreInserimento.autore
autoreInserimento.dataRegistrazione
autoreInserimento.emailAgente
autoreInserimento.id
autoreInserimento.matricola
autoreInserimento.persona
autoreInserimento.persona.anart
autoreInserimento.persona.annullato
autoreInserimento.persona.aur
autoreInserimento.persona.autore
autoreInserimento.persona.autoreInserimento
autoreInserimento.persona.categoria
autoreInserimento.persona.cellulare
autoreInserimento.persona.cittadinanza
autoreInserimento.persona.codiceFiscale
autoreInserimento.persona.cognome
autoreInserimento.persona.comuneN
autoreInserimento.persona.dCap
autoreInserimento.persona.dComune
autoreInserimento.persona.dCoordinataGaussBoagaX
autoreInserimento.persona.dCoordinataGaussBoagaY
autoreInserimento.persona.dDataInizio
autoreInserimento.persona.dIndirizzo
autoreInserimento.persona.dLatitudineWgs84
autoreInserimento.persona.dLocalita
autoreInserimento.persona.dLongitudineWgs84
autoreInserimento.persona.dNumeroCivico
autoreInserimento.persona.dStato
autoreInserimento.persona.dataAggiornamentoOrfea
autoreInserimento.persona.dataDecesso
autoreInserimento.persona.dataInserimento
autoreInserimento.persona.dataNascita
autoreInserimento.persona.dataRegistrazione
autoreInserimento.persona.email
autoreInserimento.persona.emailVerificato
autoreInserimento.persona.enteCertificatore
autoreInserimento.persona.erroreCensimentoOrfea
autoreInserimento.persona.etnia
autoreInserimento.persona.fax
autoreInserimento.persona.id
autoreInserimento.persona.medico
autoreInserimento.persona.nome
autoreInserimento.persona.offline
autoreInserimento.persona.origineCambioStatoOffline
autoreInserimento.persona.posizione
autoreInserimento.persona.qualitaCfOrfea
autoreInserimento.persona.qualitaTrattiOrfea
autoreInserimento.persona.rCap
autoreInserimento.persona.rComune
autoreInserimento.persona.rCoordinataGaussBoagaX
autoreInserimento.persona.rCoordinataGaussBoagaY
autoreInserimento.persona.rDataInizio
autoreInserimento.persona.rIndirizzo
autoreInserimento.persona.rLatitudineWgs84
autoreInserimento.persona.rLocalita
autoreInserimento.persona.rLongitudineWgs84
autoreInserimento.persona.rNumeroCivico
autoreInserimento.persona.rStato
autoreInserimento.persona.sesso
autoreInserimento.persona.sispc
autoreInserimento.persona.statoCivile
autoreInserimento.persona.statoIdentita
autoreInserimento.persona.statoN
autoreInserimento.persona.telefono
autoreInserimento.persona.telefonoAziendale
autoreInserimento.persona.titoloStudio
autoreInserimento.persona.zcognomeCongiunto
autoreInserimento.persona.zflagCoerente
autoreInserimento.persona.zmadre
autoreInserimento.persona.zpadre
autoreInserimento.persona.ztesseraSan
autoreInserimento.persona.zversione
autoreInserimento.qualifica
autoreInserimento.richiestaAccesso
autoreInserimento.ruolo
autoreInserimento.sigla
autoreInserimento.statoAttuale
autoreInserimento.superAdmin
autoreInserimento.telefonoAgente
autoreInserimento.tipoResponsabilita
autoreInserimento.titolare
autoreInserimento.ultimoAccesso
categoria
cellulare
cittadinanza
codiceFiscale
cognome
comuneN
comuneN.denominazione
dCap
dComune
dComune.denominazione
dCoordinataGaussBoagaX
dCoordinataGaussBoagaY
dDataInizio
dIndirizzo
dLatitudineWgs84
dLocalita
dLongitudineWgs84
dNumeroCivico
dStato
dStato.denominazione
dataAggiornamentoOrfea
dataDecesso
dataInserimento
dataNascita
dataRegistrazione
email
emailVerificato
enteCertificatore
erroreCensimentoOrfea
etnia
fax
id
medico
nome
offline
origineCambioStatoOffline
posizione
qualitaCfOrfea
qualitaCfOrfea.denominazione
qualitaTrattiOrfea
qualitaTrattiOrfea.denominazione
rCap
rComune
rComune.denominazione
rCoordinataGaussBoagaX
rCoordinataGaussBoagaY
rDataInizio
rIndirizzo
rLatitudineWgs84
rLocalita
rLongitudineWgs84
rNumeroCivico
rStato
rStato.denominazione
sesso
sispc
statoCivile
statoIdentita
statoN
statoN.denominazione
telefono
telefonoAziendale
titoloStudio
zcognomeCongiunto
zflagCoerente
zmadre
zpadre
ztesseraSan
zversione
2025-09-04T15:19:55.689+02:00 DEBUG 76662 --- [nio-8080-exec-7] i.j.eclipselink.impl.FetchGroupManager   : Fetch modes for com.company.personefisiche.entity.PfPersona/: e.autore=JOIN, e.autoreInserimento=JOIN, e.comuneN=JOIN, e.dComune=JOIN, e.dStato=JOIN, e.qualitaCfOrfea=JOIN, e.qualitaTrattiOrfea=JOIN, e.rComune=JOIN, e.rStato=JOIN, e.statoN=JOIN
2025-09-04T15:19:55.692+02:00 DEBUG 76662 --- [nio-8080-exec-7] eclipselink.logging.sql                  : <t 1401331242, conn 1331586070> SELECT t1.id, t1.anart_id, t1.annullato, t1.aur_id, t1.categoria, t1.cellulare, t1.cittadinanza, t1.codice_fiscale, t1.cognome, t1.d_cap, t1.d_coordinata_gauss_boaga_x, t1.d_coordinata_gauss_boaga_y, t1.d_data_inizio, t1.d_indirizzo, t1.d_latitudine_wgs84, t1.d_localita, t1.d_longitudine_wgs84, t1.d_numero_civico, t1.data_aggiornamento_orfea, t1.data_decesso, t1.data_inserimento, t1.data_nascita, t1.data_registrazione, t1.email, t1.email_verificato, t1.errore_censimento_orfea, t1.fax, t1.medico_id, t1.nome, t1.offline, t1.origine_cambio_stato_offline, t1.posizione, t1.r_cap, t1.r_coordinata_gauss_boaga_x, t1.r_coordinata_gauss_boaga_y, t1.r_data_inizio, t1.r_indirizzo, t1.r_latitudine_wgs84, t1.r_localita, t1.r_longitudine_wgs84, t1.r_numero_civico, t1.sesso, t1.sispc_id, t1.stato_civile, t1.telefono, t1.telefono_aziendale, t1.titolo_studio, t1.zcognome_congiunto, t1.zflag_coerente, t1.zmadre_id, t1.zpadre_id, t1.ztessera_san, t1.zversione, t1.autore, t1.autore_inserimento, t1.comune_n, t1.d_comune, t1.d_stato, t1.ente_certificatore_id, t1.etnia_id, t1.qualita_cf_orfea, t1.qualita_tratti_orfea, t1.r_comune, t1.r_stato, t1.stato_identita_id, t1.stato_n, t0.id, t0.amministrazione_codice_backup, t0.annullato, t0.autore, t0.data_registrazione, t0.email_agente, t0.matricola, t0.sigla, t0.super_admin, t0.telefono_agente, t0.ultimo_accesso, t0.agente_tipo_id, t0.amministrazione_codice, t0.persona_id, t0.qualifica_id, t0.richiesta_accesso_id, t0.ruolo_id, t0.stato_attuale_id, t0.tipo_responsabilita_id, t0.titolare_id, t2.id, t2.amministrazione_codice_backup, t2.annullato, t2.autore, t2.data_registrazione, t2.email_agente, t2.matricola, t2.sigla, t2.super_admin, t2.telefono_agente, t2.ultimo_accesso, t2.agente_tipo_id, t2.amministrazione_codice, t2.persona_id, t2.qualifica_id, t2.richiesta_accesso_id, t2.ruolo_id, t2.stato_attuale_id, t2.tipo_responsabilita_id, t2.titolare_id, t3.istatcomune, t3.denominazione, t4.istatcomune, t4.denominazione, t5.codice, t5.denominazione, t6.id, t6.denominazione, t7.id, t7.denominazione, t8.istatcomune, t8.denominazione, t9.codice, t9.denominazione, t10.codice, t10.denominazione FROM ana.pf_persona t1 LEFT OUTER JOIN gaa.agente t0 ON (t0.id = t1.autore) LEFT OUTER JOIN gaa.agente t2 ON (t2.id = t1.autore_inserimento) LEFT OUTER JOIN cod.istat_comune t3 ON (t3.istatcomune = t1.comune_n) LEFT OUTER JOIN cod.istat_comune t4 ON (t4.istatcomune = t1.d_comune) LEFT OUTER JOIN cod.istat_stato t5 ON (t5.codice = t1.d_stato) LEFT OUTER JOIN ana.pf_tipo_qualita_cf_orfea t6 ON (t6.id = t1.qualita_cf_orfea) LEFT OUTER JOIN ana.pf_tipo_qualita_tratti_orfea t7 ON (t7.id = t1.qualita_tratti_orfea) LEFT OUTER JOIN cod.istat_comune t8 ON (t8.istatcomune = t1.r_comune) LEFT OUTER JOIN cod.istat_stato t9 ON (t9.codice = t1.r_stato) LEFT OUTER JOIN cod.istat_stato t10 ON (t10.codice = t1.stato_n) WHERE (t1.id = ?)
	bind => [1]
2025-09-04T15:19:55.698+02:00 DEBUG 76662 --- [nio-8080-exec-7] eclipselink.logging.sql                  : <t 1401331242, conn 1331586070> [6 ms] spent
2025-09-04T15:19:55.703+02:00 DEBUG 76662 --- [nio-8080-exec-7] eclipselink.logging.sql                  : <t 1401331242, conn 779662519> SELECT id, anart_id, annullato, aur_id, categoria, cellulare, cittadinanza, codice_fiscale, cognome, d_cap, d_coordinata_gauss_boaga_x, d_coordinata_gauss_boaga_y, d_data_inizio, d_indirizzo, d_latitudine_wgs84, d_localita, d_longitudine_wgs84, d_numero_civico, data_aggiornamento_orfea, data_decesso, data_inserimento, data_nascita, data_registrazione, email, email_verificato, errore_censimento_orfea, fax, medico_id, nome, offline, origine_cambio_stato_offline, posizione, r_cap, r_coordinata_gauss_boaga_x, r_coordinata_gauss_boaga_y, r_data_inizio, r_indirizzo, r_latitudine_wgs84, r_localita, r_longitudine_wgs84, r_numero_civico, sesso, sispc_id, stato_civile, telefono, telefono_aziendale, titolo_studio, zcognome_congiunto, zflag_coerente, zmadre_id, zpadre_id, ztessera_san, zversione, autore, autore_inserimento, comune_n, d_comune, d_stato, ente_certificatore_id, etnia_id, qualita_cf_orfea, qualita_tratti_orfea, r_comune, r_stato, stato_identita_id, stato_n FROM ana.pf_persona WHERE (id = ?)
	bind => [-57]
2025-09-04T15:19:55.704+02:00 DEBUG 76662 --- [nio-8080-exec-7] eclipselink.logging.sql                  : <t 1401331242, conn 779662519> [1 ms] spent
2025-09-04T15:19:55.705+02:00 DEBUG 76662 --- [nio-8080-exec-7] eclipselink.logging.sql                  : <t 1401331242, conn 1934390873> SELECT id, anart_id, annullato, aur_id, categoria, cellulare, cittadinanza, codice_fiscale, cognome, d_cap, d_coordinata_gauss_boaga_x, d_coordinata_gauss_boaga_y, d_data_inizio, d_indirizzo, d_latitudine_wgs84, d_localita, d_longitudine_wgs84, d_numero_civico, data_aggiornamento_orfea, data_decesso, data_inserimento, data_nascita, data_registrazione, email, email_verificato, errore_censimento_orfea, fax, medico_id, nome, offline, origine_cambio_stato_offline, posizione, r_cap, r_coordinata_gauss_boaga_x, r_coordinata_gauss_boaga_y, r_data_inizio, r_indirizzo, r_latitudine_wgs84, r_localita, r_longitudine_wgs84, r_numero_civico, sesso, sispc_id, stato_civile, telefono, telefono_aziendale, titolo_studio, zcognome_congiunto, zflag_coerente, zmadre_id, zpadre_id, ztessera_san, zversione, autore, autore_inserimento, comune_n, d_comune, d_stato, ente_certificatore_id, etnia_id, qualita_cf_orfea, qualita_tratti_orfea, r_comune, r_stato, stato_identita_id, stato_n FROM ana.pf_persona WHERE (id = ?)
	bind => [33]
2025-09-04T15:19:55.706+02:00 DEBUG 76662 --- [nio-8080-exec-7] eclipselink.logging.sql                  : <t 1401331242, conn 1934390873> [1 ms] spent

The reason for the separate select for the Persona entity is that it’s a back reference: Persona → Agente → Persona.

When you load a single entity (like in a detail view), Jmix uses separate select for fetching back references – this is what you are observing.

When loading a collection, Jmix will use BATCH fetching (select by batches of IDs) for back references. This is very effective, because usually the back references are already loaded as root entities, so there is no need for fetching at all. In the worst case, when loading say 50 root entities, you will see 2 selects: one for the root entity and one for the back references. The results of the second select are mapped to the proper entities in memory.

So I think you should check loading of a collection of your Persona entities with the same fetch plan. If you see e.autore.persona=BATCH in the FetchGroupManager log output, then most probably you won’t get N+1 queries and the performance will be ok.

As for setting fetch="JOIN" for back references in the fetch plan, it’s simply ignored, and you cannot influence that.

Regards,
Konstantin