Ladezeiten optimieren durch geschicktes verschachteln

DaBe1812

Bekanntes Mitglied
Hallo zusammen,
ich versuche gerade bei meiner Anwendung eine Maske zu bauen, in der ein Datensatz verwaltet werden soll.
Ich habe schonmal aufgrund der Ladezeiten die Maske so umgebaut, dass sie nur eine reine Darstellung macht. Einfache Datumsfelder und Textfelder kann man natürlich einfach bearbeiten.
Ich denke auch, wenn ich für die ENUMs ein einfaches DropDown mache, dann wird das den Server Lade-Technisch auch nicht sonderlich interessieren.
Aber dann gibt es da noch Felder, deren Werte aus Schlüsseltabellen gezogen werden oder bei denen die Werte aus komplett anderen Entitäten gesucht werden müssen.
Fangen wir also mal klein an.
Die Schlüsselwerte setzen sich aus zwei Tabellen zusammen:
Java:
@Entity
@Table(name = "S_KEYRANGE")
public class KeyRange {

    @Id
    @Column(name = "UUID", length = 36)
    private String guid;

    @Column(name = "NAME", length= 255)
    private String name;
    
    @OneToMany(mappedBy = "keyRange")
    private Set<KeyValue> keyValues;
}
Java:
@Entity
@Table(name = "S_KEYTAB")
@NamedQueries ( value={
        @NamedQuery(name = "KeyValue.getByNameAndRange", query = "SELECT k FROM KeyValue k JOIN k.keyRange r WHERE k.keyName = :KEYNAME AND r.name = :KEYRANGE"),
        @NamedQuery(name = "KeyValue.getByRange", query = "SELECT k FROM KeyValue k JOIN k.keyRange r WHERE r.name = :KEYRANGE"),
    })
public class KeyValue {

    @Id
    @Column(name = "UUID", length = 36)
    private String guid;

    @Column(name = "KEYNAME", length = 255)
    private String keyName;
    
    @Column(name = "DISPLAYNAME", length = 255)
    private String displayname;
    
    @JoinColumn(name = "KEYRANGE", referencedColumnName = "UUID" )
    private KeyRange keyRange;
}
Also für ein bestimmtes Feld sollen nur KeyValue aus einer KeyRange zulässig sein. Dafür verwende ich aktuell die NamedQuery KeyValue.getByRange. Bedeutet aber, dass er bei 5 Solcher Felder auf der Maske 5 Mal suchen müsste.
Sobald ich in meiner Maske einen Dialog einbaue:
HTML:
<p:dialog header="Institut wählen" id="clientChoose" widgetVar="clientChoose" minHeight="40" width="500" showEffect="fade">
    <p:selectOneListbox value="#{windowsOrderHandler.selectedInstitute}"
        var="c" filter="true" filterMatchMode="contains" filterNormalize="true">
        <f:selectItems value="#{windowsOrderHandler.institutes}" var="inst" itemLabel="#{inst.clientNo} - #{inst.clientName}" itemValue="#{inst}"/>
    </p:selectOneListbox>

    <f:facet name="footer">
        <p:commandButton value="Übernehmen" icon="pi pi-check" actionListener="#{windowsOrderHandler.selectInstitute(proIpsSearchHandler.selectedDetailEntity)}"
                         update="waaTab clientChoose" process="clientChoose @this" />
        <p:commandButton value="Abbruch" icon="pi pi-times" onclick="PF('clientChoose').hide()" class="ui-button-secondary" type="button" />
    </f:facet>
</p:dialog>
Dauert die Ladezeit unglaublich lange, weil er nur zur Anzeige der Maske schon #{windowsOrderHandler.institutes} aufbaut. Wenn man den Dialog öffnet, dann nochmal und wenn man ihn schließt, schonwieder. Mal ab von der Tatsache, dass die Liste im Dialog leer angezeigt wird, aber das liegt mit Sicherheit an mir.

Ich hätte also schon mal gerne, dass der Inhalt des Dialogs nur geladen wird, wenn der Dialog auch angezeigt werden soll.
Dann bräuchte ich für das Schlüssel Ding einen Dialog, dem ich dann einfach die KeyRange füttern kann und in dieser dann nur die Werte aus der KeyRange angezeigt werden.

Ist das in Primefaces 12 möglich? Ein Update auf 13 wäre problemlos möglich, wenn benötigt.
 

Oneixee5

Top Contributor
Also ich weiß nicht was du vorhast. Ein paar Dinge fallen mir aber schon mal zu deinen Entitys/Abfragen auf. Warum benutzt du nicht einfach die ID's für die Abfragen?
"SELECT k FROM KeyValue k JOIN k.keyRange r WHERE r.name = :KEYRANGE"
Hier würde doch das Gleiche herauskommen, ohne den JOIN und ohne eine Query über das NULL-Feld name
"SELECT k FROM KeyValue k WHERE k.keyRange.guid; = :UUID"
Dann kann schon mal immer der Index vom FK verwendet werden, sofern man die mit generiert (Bei Oracle geht das automatisch, ja nach Ausstattung mehr oder weniger gut.). Oder sehe ich das falsch?
 

DaBe1812

Bekanntes Mitglied
Möglich, dass das die Abfragezeit hochschraubt, direkt in SQL habe ich mir um sowas noch nie Gedanken gemacht, ich wollte an der Stelle einfach Schlüsselunabhängig sein, da sich diese von Stage zu Stage unterscheiden. Was aber machbar wäre, wäre einen ApplicationScoped Factory dafür ein zu richten, weil die Schlüssel sich zur Lebenszeit des Servers nicht mehr ändern können. Ich möchte eben über den Namen der KeyRange auf die Schlüssel zugreifen.
Zum grundsätzlichen Problem habe ich aber schonmal etwas gebaut. Es gibt in Primefaces das Dialog Framework, das kann genau das, der Dialog wird erst dann gerendert, wenn ich ihn auch anzeigen möchte.
Hier mal ein Minimalbeispiel:
HTML von der aufgerufen wird:
HTML:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:p="http://primefaces.org/ui"
    xmlns:f="http://java.sun.com/jsf/core">
    <h:head>
        <title>TestPage</title>
    </h:head>
    <h:body>

        <div class="card">
            <h:form>
                <p:growl id="growl" showDetail="true"/>
                
                <p:commandButton value="View" icon="pi pi-home" action="#{dfView.viewProducts}" styleClass="mr-2" >
                    <p:ajax event="dialogReturn" listener="#{dfView.onProductChosen}" update="growl"/>
                </p:commandButton>
            </h:form>
        </div>
    </h:body>
</html>
Handler hinter der HTML:
Java:
@Named("dfView")
@RequestScoped
public class DfView {

    public void viewProducts() {
        DialogFrameworkOptions options = DialogFrameworkOptions.builder()
                .resizable(false)
                .build();

        PrimeFaces.current().dialog().openDynamic("chooseInstitute", options, null);
    }
    
    public void onProductChosen(SelectEvent<InstituteData> event) {
        InstituteData product = event.getObject();
        FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO, "Institut Selected", "Name:" + product.getClientName());

        FacesContext.getCurrentInstance().addMessage(null, message);
    }
}
Die chooseInstitute.xhtml:
HTML:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://xmlns.jcp.org/jsf/html" xmlns:p="http://primefaces.org/ui">

<h:head>
    <title>Institut wählen</title>
    <style>
html {
    font-size: 14px;
}
</style>
</h:head>

<h:body>
    <h:form>
        <p:dataTable var="institute" value="#{instituteChooser.institutes}">
            <p:column headerText="Nummer">
                <h:outputText value="#{institute.clientNo}" />
            </p:column>

            <p:column headerText="Name">
                <h:outputText value="#{institute.clientName}" />
            </p:column>

            <p:column style="width:32px;text-align: center">
                <p:commandButton icon="pi pi-angle-right" action="#{instituteChooser.selectInstituteFromDialog(institute)}" />
            </p:column>
        </p:dataTable>
    </h:form>
</h:body>
</html>
Und der dazugehörige Handler:
Java:
@Named
@RequestScoped
public class InstituteChooser {

    @Inject InstituteFactory instituteFactory;

    private List<InstituteData> institutes;
    
    public List<InstituteData> getInstitutes() {
        if(institutes == null) {
            loadInstitutes();
        }
        return institutes;
    }

    public void selectInstituteFromDialog(InstituteData institute) {
        PrimeFaces.current().dialog().closeDynamic(institute);
    }
    
    private void loadInstitutes() {
        institutes = instituteFactory.searchCurrentInstituteData();
    }
}
Das erste Laden dauert ein wenig, weil die Abfrage dahinter im JAVA viel länger läuft, als im SQL.
Aber, wenn ich mir ein Institut auswähle und er eigentlich nur den Dialog schließen und das Institut zurück liefern soll, dann ist die Variable intitutes wieder null und er führt die Suche erneut aus.
Kann man das irgendwie unterbinden?
 

DaBe1812

Bekanntes Mitglied
Naja, ich bin mal davon ausgegangen, dass die Bean so lange lebt, wie der Request dauert und ich dachte das wäre vom ersten anzeigen, bis zum verschwinden des Fensters. Wäre das dann eher ViewScope gewesen? Bei den beiden bin ich mir immer nicht so sicher, welches wie lange lebt.
 

Marinek

Bekanntes Mitglied
Denke, dass dies ein zu knapper Scope ist.

Wenn du die Variable und den Inhalt weiter verwenden willst brauchst du einen weiteren Scope. Welche es da gibt, muss Man nachlesen.
 

mihe7

Top Contributor
Bei den beiden bin ich mir immer nicht so sicher, welches wie lange lebt.
Beim RequestScope bezieht sich die Lebensdauer auf den HTTP-Request. Wenn Du eine Seite abrufst, ist das ein Request. Wenn Du später Daten übermittelst, ist das ein neuer Request. Beim ViewScope ist die Lebensdauer an - wer hätte es gedacht - die View/Seite gebunden.
 

DaBe1812

Bekanntes Mitglied
Kaum macht man es richtig funktioniert es. Ich hatte zwischenzeitlich ein anderes Problem bekommen mit einem zweiten Dialog, der ging nicht zu, wenn man was gewählt hat. Da hab ich die Bean auch ViewScoped gestellt und jetzt ging es direkt.
Jetzt sind es noch zwei Sachen, die optimiert werden sollten.
  1. der Vorschlag von @Oneixee5, dass die Abfrage auf die Keytab über den JOIN auf die Keyrange zu langsam ist. Da die Keyrange sich nur von Version zu Version ändern kann, gäbe es hier mehrere Ansätze:
    1. Tabelle löschen und einen ENUM draus machen und in der KeyTab über den ENUM-String Filtern
    2. Ein Factory, bzw. einen Translator aufbauen, ApplicationScoped, der sich die Keyrange lädt und von Range-Name zu RangeID übersetzt und damit suchen. Da wäre für mich interessant, wie man das richtig nennt und ob es dafür evtl. auch Entwurfsmuster gibt.
  2. Die Abfrage auf die Instituts-Daten ist extrem langsam. In SQL merkt man die Abfrage kaum, aber in Java dauert die ewig.
Hier mal die Entität:
Java:
@Entity
@Table(name = "OBJ_CLIENT_DATA")
@NamedQueries ( value={
        @NamedQuery(name = "InstituteData.getByClientNo", query = "SELECT i FROM InstituteData i WHERE i.clientNo = :CLIENTNO"),
    })
@NamedNativeQuery(name = "InstituteData.getCurrent", resultClass = InstituteData.class,
        query = "select a.* "
        + "from obj_client_data a, "
        + "(select client_uuid, "
        + " max(valid_date) as valid_date "
        + " from obj_client_data "
        + " where valid_date < SYSDATE "
        + " group by client_uuid "
        + ") b "
        + "where a.client_uuid = b.client_uuid "
        + " and a.valid_date = b.valid_date "
        + "order by a.client_no ")
public class InstituteData extends TimeSlice{

    @Id
    @Column(name = "UUID", length = 36)
    private String uuid;
    
    @JoinColumn(name = "CLIENT_UUID", referencedColumnName = "UUID")
    private Institute institute;
    
    @Column(name = "CLIENT_STATE",length = 50)
    @Enumerated(EnumType.STRING)
    private InstituteState state;

    @Column(name = "CLIENT_NO",length = 8)
    private String clientNo;

    @Column(name = "CLIENT_NAME",length = 255)
    private String clientName;

    @Column(name = "CLIENT_WORKPLACES")
    private Integer workplaces;
    
    @Lob
    @Column(name = "CLIENT_COMMENT")
    private String comment;
}
Geladen wird an der Stelle die NativeQuery,als JPQL bekomme ich einen Fehler mit irgendwelchen fehlenden Tabellen-Repräsentationen. Also grundsätzlich will ich an der Stelle alle Clients, Gruppiert nach Institut, und da den Datensatz mit dem neuesten Datum, dass aber kleiner ist, als das heutige Datum.
Hier der Fehler als NativeQuery:
Code:
java.lang.IllegalArgumentException: Ausnahme [EclipseLink-6069] (Eclipse Persistence Services - 3.0.3.v202208190922): org.eclipse.persistence.exceptions.QueryException
Beschreibung der Ausnahme: Das Feld [OBJ_CLIENT_DATA.CLIENT_UUID] in diesem Ausdruck hat in diesem Kontext eine ungültige Tabelle.
Abfrage: ReadAllQuery(name="InstituteData.getCurrentNN" referenceClass=InstituteData
jpql="Select a from InstituteData a,
 (SELECT b.institute, max(b.validDate) as validDate 
  FROM InstituteData b  WHERE b.validDate < CURRENT_DATE  GROUP BY b.institute) as c
  WHERE c.institute = a.institute  AND c.validDate = a.validDate
  ORDER BY a.clientNo")
Vielleicht hab ich auch nen Denkfehler im JPQL
 

DaBe1812

Bekanntes Mitglied
Okay, heute morgen das nächste Problem festgestellt:
Der Einbau in die "echte" Seite funktioniert nicht. Ich habe erstmal testweise einfach nur basic versucht einen Dialog zu öfnnen:
Java:
@Named
@ViewScoped
public class WindowsOrderHandler implements Serializable {

    private static final long serialVersionUID = 8391282997715627511L;

    @PostConstruct
    public void inti() { }

    public void showMessage() {
        FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO, "Message", " Always Bet on Prime!");

        PrimeFaces.current().dialog().showMessageDynamic(message);
    }

    public List<OrderState> getOrderStates() {
        return Arrays.asList(OrderState.values());
    }

    public List<OrderType> getOrderTypes() {
        return Arrays.asList(OrderType.values());
    }

}

Ich sehe in der aufrufenden Seite kein Problem, und im Code wird mir auch kein Fehler angezeigt. Ich habe mal wieder meinen "Baum" in Verdacht, weiß aber auch nicht, wie ich das "geschickter" darstellen soll. Deswegen viel Spaß beim Baum Lesen:
LAYOUT.XHTML
HTML:
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:h="http://java.sun.com/jsf/html"
    xmlns:p="http://primefaces.org/ui" xmlns:f="http://java.sun.com/jsf/core">
<h:head>
    <title>ATC - <h:outputText value="#{menuView.site}" /></title>
    <link type="image/x-icon" href="${facesContext.externalContext.requestContextPath}/resources/images/netzwerk.png"
        rel="shortcut icon" />
</h:head>

<h:body>
    <h:outputStylesheet library="css" name="primeflex.min.css" />
    <h:outputStylesheet library="css" name="style.css" />
    <f:event listener="#{sessionHandler.checkLoggedIn}" type="preRenderView" />

    <div class="content">

        <div class="ui-g menusticky">
            <p:staticMessage severity="warn" summary="WARTUNG" detail="#{maintenanceModeHandler.maintenanceText}" style="width: 100%"
                    rendered="#{maintenanceModeHandler.maintenanceModeActive}"/>
            <div class="ui-g-12" style="padding: 0; !important">
                <h:form>
                    <p:menubar model="#{menuView.model}" class="menu" />
                </h:form>
            </div>
        </div>


        <div class="ui-g mainWrapper">
            <div class="ui-g-12" id="main">
                <ui:insert name="main" />
            </div>
        </div>

        <footer>
            <div class="ui-g footer">
                <div class="ui-g-12" style="padding: 0; !important">
                    <h:form>
                        <p:menubar>
                            <p:menuitem value="&#169; ATC Version #{confighandler.productversion} - by 5332" />
                            <p:menuitem value="Changelog" url="https://wiki.intern/display/atc/Changelog" icon="pi pi-sort-amount-down" class="changelog" />
                        </p:menubar>
                    </h:form>
                </div>
            </div>
        </footer>

    </div>

</h:body>
</html>
PROIPS.XHTML
HTML:
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" xmlns:p="http://primefaces.org/ui"
    template="../layout.xhtml">

    <ui:define name="header">
        ProIPS - Datenpflege
    </ui:define>

    <ui:define name="main">

        <f:event type="preRenderView" listener="#{proIpsSearchHandler.onLoad}" />

        <h:form id="datenpflegeForm">
            <p:growl id="msgs" showDetail="true" skipDetailIfEqualsSummary="true" />

            <div class="card">
                <p:tabView>
                    <p:ajax event="tabChange" listener="#{proIpsSearchHandler.onTabChange}" update=":datenpflegeForm:msgs" />
                    <p:ajax event="tabClose" listener="#{proIpsSearchHandler.onTabClose}" update=":datenpflegeForm:msgs" />

                    <p:tab title="Suche" closable="false">
                        <ui:include src="./dataexplorer/search.xhtml" />
                    </p:tab>
                    <p:tab title="Details" closable="true" rendered="#{not empty proIpsSearchHandler.selectedDetailEntity}">
                        <ui:include src="./dataexplorer/windowsOrderDetail.xhtml" />
                    </p:tab>
                </p:tabView>
            </div>
        </h:form>
    </ui:define>
</ui:composition>
Hier habe ich schon die Befürchtung, dass das form einfach zu groß ist, aber ohne Form keine Tabs.
WINDOWSORDERDETAIL.XHTML (gekürzt)
HTML:
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" xmlns:p="http://primefaces.org/ui">

    <f:event listener="#{sessionHandler.checkLoggedIn}" type="preRenderView" />

    <p:toolbar>
        <p:toolbarGroup>
            <p:commandButton type="button" value="Neu" icon="pi pi-file" styleClass="mr-2" />
            <p:commandButton type="button" title="Speichern" icon="pi pi-save" styleClass="ui-button-help mr-2" />
            <p:commandButton type="button" title="Löschen" icon="pi pi-trash" styleClass="ui-button-danger" />
            <p:commandButton value="Institut" icon="pi pi-home" action="#{windowsOrderHandler.showMessage}" style="margin-right:10px" />

        </p:toolbarGroup>
    </p:toolbar>


    <p:tabView id="waaTab">
        <p:tab title="Auftrag">

            <div class="card ui-fluid ">
                <p:panel header="Eingang">
                    <div class="formgrid grid">
                        <div class="field col-12 md:col-4">
                            <p:outputLabel for="@next" value="Auftrag-Nr." />
                            <p:inputText value="#{proIpsSearchHandler.selectedDetailEntity.orderNo}" required="true">
                                <f:validateLength minimum="5" />
                            </p:inputText>
                           </div>
   
                        <div class="field col-12 md:col-4">
                            <p:outputLabel for="@next" value="Pos-Nr." />
                            <p:inputText value="#{proIpsSearchHandler.selectedDetailEntity.posNo}" required="true" />
                           </div>
   
                        <div class="field col-12 md:col-4">
                            <p:outputLabel for="@next" value="Auftragsart" />
                            <p:selectOneMenu value="#{proIpsSearchHandler.selectedDetailEntity.orderType}" required="true">
                                <f:selectItems value="#{windowsOrderHandler.orderTypes}" />
                            </p:selectOneMenu>
                           </div>
   
                        <div class="field col-12 md:col-4">
                            <p:outputLabel for="@next" value="ProIPS-Nr" />
                            <p:inputText value="#{proIpsSearchHandler.selectedDetailEntity.proIpsNo}" />
                           </div>
   
                        <div class="field col-12 md:col-4">
                            <p:outputLabel for="@next" value="Auftragseingang" />
                            <p:datePicker value="#{proIpsSearchHandler.selectedDetailEntity.orderDate}" required="true" />
                           </div>
   
                        <div class="field col-12 md:col-4">
                            <p:outputLabel for="@next" value="Eingangsart" />
                            <div class="ui-inputgroup">
                                <p:inputText value="#{proIpsSearchHandler.selectedDetailEntity.transmissionType.displayname}" readonly="true"/>
                                   <p:commandButton icon="pi pi-search" styleClass="ui-button-success"
                                           action="#{windowsOrderHandler.showMessage}" />
                               </div>
                           </div>
   
                        <div class="field col-12 md:col-4">
                            <p:outputLabel for="@next" value="FISP Request" />
                            <p:inputText value="#{proIpsSearchHandler.selectedDetailEntity.fispRequest}" />
                           </div>
   
                        <div class="field col-12 md:col-4">
                            <p:outputLabel for="@next" value="FISP Item" />
                            <p:inputText value="#{proIpsSearchHandler.selectedDetailEntity.fispItem}" />
                           </div>
                       </div>
                </p:panel>
               </div>
        </p:tab>
    </p:tabView>
</ui:composition>
Ich hab es sowohl in Zeile 14, als auch in Zeile 60 ausprobiert, bei beidem nur die Reaktion, dass kurz das Rad angezeigt wird und dann nichts.
Er springt nicht in die Methode und in der Konsole des Browsers taucht auch nichts auf.
 

DaBe1812

Bekanntes Mitglied
Okay, scheinbar ein Fehler in der WINDOWSORDERDETAIL.XHTML. Wenn ich einen Großteil davon raus nehme, dann geht's. Werde das nachher mal Stück für Stück hinzufügen.
 

DaBe1812

Bekanntes Mitglied
Wow, es war tatsächlich das letzte Feld, in der Maske. Das hatte ich woanders wegkopiert und das falsche Feld für den Value hinterlegt, so habe ich schlussendlich probiert ein Datum in ein Toggel-Feld zu packen. Schade, dass man hier nicht ordentlich debuggen kann, damit man sieht, dass der Fehler ein Konvertierungsproblem ist.
Also die Grundsätzlich Maske funktioniert. Die Dialoge triggern ihren Handler nur, wenn sie auch aufgerufen werden und laden nicht ständig neu, obwohl man es nicht bräuchte.
Danke so weit.
Jetzt kommt der optimierungs-Spaß auf Entitätsseite.
Wir haben zum einen die KeyValues. Geholt werden diese mit
Java:
@RequestScoped
public class KeyValueFactory {

    @Inject private ProIpsEntityManagerProvider emp;

    public List<KeyValue> searchAllByRangeName(String range) {
        EntityManager em = emp.getEntityManager();
        TypedQuery<KeyValue> query = em.createNamedQuery("KeyValue.getByRange", KeyValue.class);
        query.setParameter("KEYRANGE", range);
        
        List<KeyValue> resultList = query.getResultList();

        return resultList;
    }
}
Der JPQL dazu ist:
Java:
SELECT k FROM KeyValue k JOIN k.keyRange r WHERE r.name = :KEYRANGE ORDER BY k.keyName
Hier wurde ja schon bemängelt, dass ich die Range als ihre ID und nicht mit einem Join übergeben sollte.
Mir ist aber aufgefallen, dass die Liste signifikant bei der Dauer an ihrer Größe hängt. Klar, mehr Ergebnisse = längere Laufzeit, aber das sind richtig übel lange Zeiten, um die es da geht. Das SQL separat abgesetzt hätte niemals diese Laufzeitunterschiede. Also irgend etwas muss noch im Java passieren, das so unglaublich lange dauert.

Ähnlich verhält es sich bei der Abfrage auf die Institute:
SQL:
select a.*
from obj_client_data a, (
    select client_uuid,
        max(valid_date) as valid_date
    from obj_client_data
    where valid_date < SYSDATE
    group by client_uuid
) b
where a.client_uuid = b.client_uuid
    and a.valid_date = b.valid_date
order by a.client_no
Hier ist das SQL selbst ein wenig komplizierter, aber auch hier finde ich die Laufzeit nicht nachvollziehbar länger, als im SQL.
 

mihe7

Top Contributor
Mir ist aber aufgefallen, dass die Liste signifikant bei der Dauer an ihrer Größe hängt. Klar, mehr Ergebnisse = längere Laufzeit, aber das sind richtig übel lange Zeiten, um die es da geht. Das SQL separat abgesetzt hätte niemals diese Laufzeitunterschiede. Also irgend etwas muss noch im Java passieren, das so unglaublich lange dauert.
Ich würde mir a) das SQL loggen lassen und b) mir mal den Ausführungsplan in der DB anschauen. An Java sollten signifikante Laufzeitunterschiede eigentlich nicht liegen. Übrigens: ein 36-Zeichen langer String als Primary Key ist auch nicht gerade eine gute Idee :)
 

LimDul

Top Contributor
Wir sind in verschiedenen Anwendungen weg von UUIDs als Primary Key hin zu Long Werten aus einer Sequence, weil das einfach performanter & bei Analysen auch lesbarer ist.
 

DaBe1812

Bekanntes Mitglied
Okay, der Zug ist dann wohl an mir vorbei gefahren. Ich kann mich noch an Zeiten erinnern, da war das Default und aufgrund seiner endlosen Eindeutigkeit gerne genommen.
Was ich nicht verstehe ist, was an den 36 Zeichen das Problem ist?
Aber an den aktuellen Stellen ist das noch änderbar.
 

LimDul

Top Contributor
https://www.baeldung.com/uuid-vs-sequential-id-as-primary-key Hier steht was.

Grundsätzlich werden in Oracle oder wo auch immer bei einem entsprechende Index-Lookups vorgehalten. Die sind bei UUIDs ein gutes Stück aufwendiger - zum einen aufgrund des Speicherbedarfs, zum anderen ist bei Ziffern sowohl der Vergleich einfacher, als auch bzgl. des Aufbaus der Lookup-Struktur (In der Regel B* Bäume) deutlich vorhersagbarer und damit im Zweifelsfall die Bäume balancierter.

Also sollte das nicht die großen Performance Unterschieden ausmachen - da würde ich entweder auf ungünstiges Lazy Load oder ähnliches tippen. Dafür wirklich mal die SQL Statements, am besten inkl. gesetzter Parameter dokumentieren lassen.
 

DaBe1812

Bekanntes Mitglied
Also bei den Instituten habe ich zumindest einen besseren Ansatz für mich gefunden:
Ich habe jetzt im Dialog noch ein Suchfeld, was vor der Tabelle kommt, weil grundsätzlich weiß der User ja welches Institut er sucht, nur natürlich nicht, welchen PK dieses in der Datenbank hat.
Dabei gibt es aber jetzt das Problem, dass ich die Abfrage nicht hin bekomme, weil er mir irgendwie den Parameter nicht nimmt:
Das ist jetzt das SQL:
SQL:
select a.*
from obj_client_data a,
(select client_uuid,
 max(valid_date) as valid_date
 from obj_client_data
 where valid_date < SYSDATE
 group by client_uuid
) b
where a.client_uuid = b.client_uuid
and a.valid_date = b.valid_date
and a.client_no like :SEARCHSTRING
order by a.client_no
Dabei bekomme ich aber folgenden Fehler:
Code:
CWWJP9991W: Ausnahme [EclipseLink-4002] (Eclipse Persistence Services - 3.0.3.v202208190922): org.eclipse.persistence.exceptions.DatabaseException
Interne Ausnahme: java.sql.SQLException: Fehlender IN- oder OUT-Parameter auf Index:: 1
Fehlercode: 17041
Aufruf: select a.* from obj_client_data a, (select client_uuid,  max(valid_date) as valid_date  from obj_client_data  where valid_date < SYSDATE  group by client_uuid ) b where a.client_uuid = b.client_uuid  and a.valid_date = b.valid_date  and a.client_no like :CLIENTNOorder by a.client_no
Abfrage: ReadAllQuery(name="InstituteData.getCurrentByNo" referenceClass=InstituteData sql="select a.* from obj_client_data a, (select client_uuid,  max(valid_date) as valid_date  from obj_client_data  where valid_date < SYSDATE  group by client_uuid ) b where a.client_uuid = b.client_uuid  and a.valid_date = b.valid_date  and a.client_no like :CLIENTNOorder by a.client_no ")
Ich hab es dann mal als NamedQuery hingehunzt, und diese dann auf setMaxResults(1) begrenzt, aber wenn der User dann nur den Anfang weiß, dann ist da irgendwie keine Liste.

Aber viel interessanter, der Dialog resized nicht, wenn die Tabelle eingeblendet wird. Kann man den irgendwie dazu zwingen, seinen Inhalt nochmal zu überdenken und einen Resize zu machen?
 

DaBe1812

Bekanntes Mitglied
Okay, ich mach jetzt Feierabend:
Den Fehler mit der Abfrage könnt ihr nicht mal sehen, aber ich habe ein Leerzeichen hinter :SEARCHSTRING vergessen, also war es im String direkt am order by dran.
Bei den Dialogen habe ich einfach mal mit den Optionen rumgespielt und mit
Java:
DialogFrameworkOptions options = DialogFrameworkOptions.builder()
    .resizable(false)
    .height("400")
    .contentHeight("100%")
    .build();
Tut es den Trick und startet in einer Größe und wird wunderschön angezeigt.
Durch die vorgeschaltete Suche geht es ziemlich schnell mit dem Abfragen.

Spannend wäre jetzt noch die Möglichkeit einer Single-Field Suche, also ein Feld für den Suchbegriff und damit wird in einer Menge X an Feldern gesucht, im Falle der Institute wäre das die Nummer und die Beschreibung.
Aber morgen baue ich dasselbe für Windows Systeme, da könnten ein paar Felder mehr interessant sein.
Gibt es dafür evtl. OnBoard-Mittel, oder Frameworks?
 

DaBe1812

Bekanntes Mitglied
War tatsächlich auch mein erster Ansatz, ich glaube aber das funktioniert nicht bei allen Feldern. Also z.B. wenn ich ein Boolean (Number(1,0) Feld habe, und dem User ist True oder False egal, dann kann ich ja da nicht nichts eintragen. Oder später bei der Suche nach Hosts, wenn es nicht um den Hostnamen geht, dann bekomme ich bei OR HOSTNAME = "" auch noch alle mit leerem Hostnamen angezeigt. Ich suche eher so dynamische WHERE-Bedingungen,
Java:
//Frei aus der Hand geschrieben
@Query(Select * from OBJ_WINDWOWSOS)
public List<WindowsOS> getWindowsOSDynamic(String hostname, boolena active) {
    if(hostname != null && !"".equals(hostname)) {
        @QueryAddition("WHERE HOSTNAME = '" + hostname + "');
    }
    .
    .
    .
}
So in der Art.
Sonst müsste ich für jede Kombination aus Suchfeldern eine extra NamedQuery bauen, oder?
 

DaBe1812

Bekanntes Mitglied
Okay, da muss ich direkt Unwissenheit von Abhängigkeiten klären. Mir wurde gesagt, ich muss im Projekt EclipseLink verwenden, weil das im Server so drin ist. Geht dann trotzdem Hibernate? Ich dachte das wären unterschiedliche Projekte zur selben Sache.
 

Ähnliche Java Themen

Neue Themen


Oben