Verknüpfung auflösen für Frontend

  • Themenstarter Themenstarter Gelöschtes Mitglied 68249
  • Beginndatum Beginndatum
G

Gelöschtes Mitglied 68249

Gast
Hi,

ich stehe gerade vor einem Problem für mein Frontend, dass ich nicht weiß, wie ich meine Daten richtig darstellen soll. Erstmal die Datenstrukturen, um die es geht:
Die Entität, die dargestellt werden soll ist ein Auftrag mit dem beispielhaften Aufbau:
Java:
@Entity
@Table(name = "OBJ_ORDER")
public class WindowsOrder implements SearchableEntity {

    @Id
    @Column(name = "UUID", length = 36)
    private String uuid;
    
    @Column(name = "ORDER_NO")
    private Integer orderNo;
    
    @Column(name = "POS_NO")
    private Integer posNo;
    
    @Column(name = "ORDER_TYPE", length = 50)
    @Enumerated(EnumType.STRING)
    private OrderType orderType;
    
    @ManyToOne
    @JoinColumn(name = "TRANSMISSION_TYPE", referencedColumnName = "UUID" )
    private KeyValue transmissionType;
    
    @ManyToOne
    @JoinColumn(name = "INSTITUTE_UUID", referencedColumnName = "UUID" )
    private Institute client;
}
Das "Problem" Daran ist Institute, das sieht nämlich so aus:
Java:
@Entity
@Table(name = "OBJ_CLIENT")
public class Institute {

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

    @OneToMany(mappedBy = "institute")
    private Set<InstituteData> timeslices;
}
Denn dies ist nur ein Sammler für die Historie der Institute. Die eigentlichen Werte sind in der Entität InstituteData:
Java:
@Entity
@Table(name = "OBJ_CLIENT_DATA")
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;
}

Theoretisch kann sich in der Lebenszeit eines Institutes auch der Name ändern.

Für das Frontend soll das ungefähr so aussehen:
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">

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

            <p:panel header="Eingang">
                <p:panelGrid columns="8">
                    <p:outputLabel for="@next" value="Auftrag-Nr." />
                    <p:inputText value="#{proIpsSearchHandler.selectedDetailEntity.orderNo}" required="true">
                        <f:validateLength minimum="5" />
                    </p:inputText>

                    <p:outputLabel for="@next" value="Pos-Nr." />
                    <p:inputText value="#{proIpsSearchHandler.selectedDetailEntity.posNo}" required="true" />

                    <p:outputLabel for="@next" value="Auftragsart" />
                    <p:selectOneMenu value="#{proIpsSearchHandler.selectedDetailEntity.orderType}" required="true">
                        <f:selectItems value="#{windowsOrderHandler.orderTypes}" />
                    </p:selectOneMenu>

                    <p:outputLabel for="@next" value="Eingangsart" />
                    <p:selectOneMenu value="#{proIpsSearchHandler.selectedDetailEntity.transmissionType}" required="false">
                        <f:selectItems value="#{windowsOrderHandler.transmissionTypes}" var="trantype" itemValue="#{trantype}" itemLabel="#{trantype.displayname}"/>
                    </p:selectOneMenu>

                    <p:outputLabel for="@next" value="Mandant" />
                    <p:selectOneMenu value="#{proIpsSearchHandler.selectedDetailEntity.client}" required="false">
                        <f:selectItems value="#{windowsOrderHandler.institues}" var="inst" itemValue="#{inst}" itemLabel="#{inst.clientNo}" itemDescription="#{inst.clientName}"/>
                    </p:selectOneMenu>

                </p:panelGrid>
            </p:panel>

    </p:tabView>

</ui:composition>

Das ist so falsch, habe ich festgestellt, weil ich hier zwei Objekte vermische nämlich Institute als value und InstituteData als SelectItem.

Also im Endeffekt möchte ich in den Selectitems zu jedem Institut nur das aktuellste von allen noch aktiven Instituten haben. Das SQL sieht dann ungefähr so aus:
SQL:
select * from obj_client_data c where CONCAT(c.client_uuid, c.valid_date) in
    (select concat(client_uuid, max(valid_date)) from obj_client_data group by client_uuid)
and client_state = 'PRODUCTIVE';

Wie soll ich das denn jetzt im Hintergrund bauen, damit das so funktioniert? Bzw. muss ich vielleicht nur das Frontend anpassen?
 
Also das Problem ist doch ein wenig komplizierter als "Getting Started". Wenn ich die Daten nehme, wie sie aus der einfachen Abfrage kommen, dann habe ich da ja lediglich die Objektrefferenz im Frontend. Aber ich komme ja erstmal nicht an z.B. die clientNo, weil dafür müsste ich ja wissen, welcher Client gerade aktuell ist.
Ich sehe gerade, dass ich TimeSlice nicht mit aufgeführt habe, aber da drin ist nur ein Feld für einen Start-Timestamp und den Create-User. Ich möchte also die ClientData, deren client_uuid die ist, die im Order-Objekt hinterlegt ist, und wo der Start-Timestamp der größte von denen ist, die kleiner sind, als jetzt.
Gibt es dafür irgendwie eine Bord-Möglichkeit? Oder ist das Problem tatsächlich Basic und ich sehe es nicht?
 
Okay, ich habe das Problem jetzt doch ein wenig einfacher angegangen, als ich erst dachte, leider passt das Ergebnis nicht ganz. Also erstmal die Änderungen so weit:
Die Einträge für das Dropdown besorge ich mir jetzt mit der Abfrage:
SQL:
select c from InstituteData c where CONCAT(c.institute.uuid, c.validDate) in (
    select concat(b.institute.uuid, max(b.validDate))
    from InstituteData b
    where b.validDate < CURRENT_TIMESTAMP group by b.institute.uuid
) order by c.clientNo
Im Frontend habe ich den Feldwert geändert auf:
HTML:
<p:selectOneMenu value="#{proIpsSearchHandler.selectedDetailEntity.waaClient}" required="false"
        filter="true" filterMatchMode="contains">
    <f:selectItems value="#{windowsOrderHandler.institutes}" var="inst" itemValue="#{inst}" itemLabel="#{inst.clientNo} - #{inst.clientName}" />
</p:selectOneMenu>
WaaClient baue ich folgendermaßen in die Order ein:
Java:
public InstituteData getWaaClient() {
    return client.getCurrentInstituteData();
}

public void setWaaClient(InstituteData instituteData) {
    this.client = instituteData.getInstitute();
}

Die Methode getCurrentInstituteData im Institut sieht dann folgendermaßen aus.
Java:
public InstituteData getCurrentInstituteData() {
    Optional<InstituteData> current;

    List<InstituteData> tempList = new ArrayList<>(timeslices);

    if(timeslices.size() == 1) {
        return tempList.get(0);
    }

    Comparator<InstituteData> getActual;
    getActual =  new Comparator<InstituteData>() {
        @Override
        public int compare(InstituteData i1, InstituteData i2) {
            if(i1.getValidDate().isAfter(LocalDateTime.now())) return -1;
            if(i2.getValidDate().isAfter(LocalDateTime.now())) return 1;
            if(i1.getValidDate().isAfter(i2.getValidDate())) return 1;
            if(i2.getValidDate().isAfter(i1.getValidDate())) return -1;
            return 0;
        }
    };

    current = tempList.stream().max(getActual);

    if(current.isPresent()) {
        return current.get();
    }
    return null;
}
Die Methode liefert auch genau den Datensatz, den ich an der Stelle erwarte. In der Liste der Datensätze aus windowsOrderHandler.institutes ist der Datensatz auch drin, aber bei der Anzeige wird der erste Satz der Liste angezeigt, nicht der, der im Datensatz hinterlegt ist.
 
Du kannst ja value="#{proIpsSearchHandler.selectedDetailEntity.waaClient}" mal ausgeben, dann sieht man ja was da wirklich drin steht. Der erste Eintrag wird meistens angezeigt, weil nichts passendes gefunden wird.

select c from InstituteData c where CONCAT(c.institute.uuid, c.validDate) in ( select concat(b.institute.uuid, max(b.validDate)) from InstituteData b where b.validDate < CURRENT_TIMESTAMP group by b.institute.uuid ) order by c.clientNo
Die Abfrage ist total lost. Nicht nur, dass hier überhaupt kein Index verwendet werden kann, auch das Ergebnis kann falsch sein. Wenn c.institute.uuid auf eine Ziffer endet, dann kann je nach dem Datumsformat in c.validDate etwas falsches herauskommen. Das maximale Datum einer Gruppe kann man mit einer Window-Funktion einfach und vor allem schnell ermitteln. Dann ist meistens auch gar kein Self-Join mehr notwendig. Aber wenn Self-Join, dann wenigstens über die ID der Tabelle. Wenn man für die Window-Funktion Hibernate nicht direkt verwenden will, kann man immer noch eine JPA-Native-Query verwenden.
 
Den Value habe ich mir auch schon ausgeben lassen, aber mangels toString() Methode kam dabei nur die Objektrefferenz raus.
Habe dann nochmal clientNo und clientName ausgeben lassen und das war wie erwartet das was ich auch in der Datenbank hinterlegt habe. Also an der Stelle schon mal alles gut.
Dass er den ersten Eintrag schreibt, weil er keinen passenden in der Liste findet, hatte ich schon in früheren Fällen, da hatte ich aber auch gravierend was falsch gemacht. Hier verstehe ich es nicht, weil der passende Datensatz ja eigentlich in der Liste ist, ich weiß aber nicht, wie er den Vergleich anstellt, evtl. ist das ja auch ein Referenz-Problem?
Die Abfrage ist total lost.
Okay, wie sich hier JPQL und SQL unterscheiden, weiß ich nicht, aber bei Oracle ist der Timestamp immer gleich lang und die UUID hat immer dieselbe Anzahl Zeichen. Leider habe ich kein Hibernate in dem Projekt, sondern EclipseLink, ist wohl so von der ApplicationServer Seite vorgegeben. Hab zwar ein paar Sachen von Hibernate vermisst, aber bisher die Sache nicht hinterfragt.
Und die Abfrage habe ich als entweder als ähnliches Beispiel damals (an anderer Stelle mache ich das genauso) gefunden, oder sogar mit einem Oracle-Kumpel zusammengebaut.
Aber ich werde mir die Window-Funktion mal anschauen.
 
Muss denn in <p:selectOneMenu value="???" nicht die ID rein? Also das was itemValue entspricht? Bei dir glaub ich die UUID. Da darf keine Object-Reference herauskommen.
HTML:
                <p:selectOneMenu id="option" value="#{<hier etwas was itemValue='Option1/2/3' entspricht}>}">
                    <f:selectItem itemLabel="Select One" itemValue=""/>
                    <f:selectItem itemLabel="OptionA" itemValue="Option1"/>
                    <f:selectItem itemLabel="OptionB" itemValue="Option2"/>
                    <f:selectItem itemLabel="OptionC" itemValue="Option3"/>
                </p:selectOneMenu>
 
Ja, da muss der Value des Feldes rein, in dem Fall also ein InstitutData-Objekt und die Liste ist auch eine Liste von InstitutData, also Objektarten mäßig ist es schon mal gleich.

Hab aber aufgrund der Ladezeiten das Ganze jetzt umgestellt. In dieser Maske werden jetzt nur die Werte aus den Feldern als normales Feld angezeigt. Funktioniert prima und die Ladezeit liegt unter 1s.
Die von dir bemängelte Suche habe ich nochmal angepasst, da ich aber das Window-Prinzip nicht richtig verstanden habe musste ich auf eine NativeQuery zurückgreifen, weil die JPQL-Version führt zu einem Fehler.
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
Jetzt können alle Werte so unterschiedlich sein, wie sie wollen, das Matching ist immer eindeutig.

Wegen der Ladezeiten habe ich jetzt ein anderes Problem, aber das hat nichts mit der DB zu tun, deswegen setze ich das in einem anderen Forum fort.
 

Zurück
Oben