Speichern letzter Query

mankingwwe

Aktives Mitglied
Moin,
folgende Problemstellung. Ich will in der Anwendung die letzte Query speichern die an die DB gesendet wurde.

Habe also eine Componente erstellt :

@Component
public class QueryContainer{
Hashmap<String,String> lastQuery
}

die in einer Hashmap dann für einen Key (Der Key entspricht der entsprechenden HTML Seite, so kann ich für jede Seite die letzte Query speichern), die Query speichert. Diese komponente rufe ich dann immer vor einem db aufruf auf und eben key und den sql string dort rein. Wenn ich in der App dann irgendwo die letzte Query brauche, dann mache ich ein get auf die Hashmap des QueryContainers und hole mir den gespeicherten sql String zum SeitenKey. Das funktioniert auch. Ich will nur wissen, ob ihr das auch so machen würdet oder es eleganter geht? Wie würdet ihr für eine HTML Seite die durch einen String definiert ist die letzte query spechern die für diese Seite aufgerufen wird.

Will gerne lernen und mich weiterentwickeln und das geht nur durch andere Sichtweisen :) Bin dankbar für jede Antwort!
 

Oneixee5

Top Contributor
Mir ist nicht klar wozu das gebraucht werden könnte. Eine Webseite oder Ressource wird durch die URL definiert. Es ist doch also alles vorhanden was benötigt wird. Ich habe noch nie gehört oder gesehen, dass die letzte Query benötigt wird und ich mache Backend und DB's das schon ewig.
Es ist auch unlogisch was du da mit der HashMap vorhast. Webanwendungen funktionieren in mehreren Threads und es ist nicht unbedingt sicher, dass du immer die selbe Instanz deiner @Component erhältst. Es könnte auch mal eine neue Instanz sein. Spring Components/Beans sind statuslos und sollen keinen Zustand speichern, nur Abhängigkeiten.
 

mankingwwe

Aktives Mitglied
Mir ist nicht klar wozu das gebraucht werden könnte. Eine Webseite oder Ressource wird durch die URL definiert. Es ist doch also alles vorhanden was benötigt wird. Ich habe noch nie gehört oder gesehen, dass die letzte Query benötigt wird und ich mache Backend und DB's das schon ewig.
Es ist auch unlogisch was du da mit der HashMap vorhast. Webanwendungen funktionieren in mehreren Threads und es ist nicht unbedingt sicher, dass du immer die selbe Instanz deiner @Component erhältst. Es könnte auch mal eine neue Instanz sein. Spring Components/Beans sind statuslos und sollen keinen Zustand speichern, nur Abhängigkeiten.
Hey. Deshalb frage ich nach. Also das Problem ist, dass im frontend sortiert wird (tabelle). Und dabei gabs paar probleme weshalb wir im Backend sortieren wollten und nicht im frontend via js. Sobald ich auf die spalte klicke zum sortieren, schicke ich via ajax die spalte ins backend und lasse die daten erneut abfragen aus der datenbank und sortiere sie. Statt die daten mit ins backend zu geben zum sortieren, sage ich einfach im backend, hey hier das ist die spalte die sortiert werden soll, ruf bitte die letzte gespeicherte query auf und hol die daten erneut aus der db und sortiere sie dann. Mir ist klar das es nicht schön ist erneut daten abzufragen die man bereits hat, nur um sie dann zu sortieren, aber ich wollte nicht über die Methodik sprechen, sondern über die Möglichkeit die Query zu sichern. Wenn ein User die Anwendung irgendwo erneut aufruft , dann soll er da ruhig eine andere Instanz bekommen, denn dort geht es ja auch um andere daten. Oder verstehe ich da was falsch? Wann könnte es den auftreten das ein neuer thread erstellt wird und der user eine neue instanz der componente erhält.
 

Oneixee5

Top Contributor
Die Query ist vermutlich immer die Gleiche für die selbe Seite/Daten. Es genügt also die Parameter der Query zu senden.
 

Oneixee5

Top Contributor
Was für Probleme gab es denn bei der Sortierung. Möglicherweise wäre es ja der bessere Ansatz die Sortierung zu verbessern.
 

mankingwwe

Aktives Mitglied
Also zum verbildlichen:
Gibt im Menü folgende Punkte:

Daten
--> Devisen
--> Zinsen
--> Geldmarktfutures

Wenn ich auf Devisen klicke, dann sende ich ne "startquery" die mir zb zum Währungspaar EUR/USD den Kurs liefert für den aktuellen Tag. Diese Query ist hart hinterlegt im einem Object das für jede dieser aufgeführten Seiten den landingpage suchstring enthält. Dient nur dazu damit schonmal was angezeigt wird. Dann habe ich ne tabelle in dieser Form:

Währung 1 Währung 2 Kurs etc... (ca 11 spalten)
[ Suchfeld ] [ Suchfeld ] [ Suchfeld ]

EUR USD 1,2
EUR USD 9,1

Wenn jemand nun ins Suchfeld für Währung 2 CHF eingibt, dann sendet er eine neue query an die Datenbank mit der Suche WHERE w2 = 'CHF'

Also wenn die Seite das erste mal aufgerufen wird, werden die daten zu einer fest angelegten Suchquery 'landingpage sql' angezeigt. Danach kann der User die Suchfelder befüllen, woraus dann eine neue Query gebaut wird. Beim aufruf der zb Devisen Seite, wird dann das landingpage sql als lastquery für devisen hinterlegt. Wenn dann über die suchfelder gesucht wird, dann wird eben die neue query die alte überschreiben und als last query gelistet. So funktioniert es aktuell
 

mankingwwe

Aktives Mitglied
Aber nach dem was ich nun gehört habe werde ich mein Vorgehen ändern und einfach die Suchkriterien erneut ins backend senden und daraus die query bauen und nicht versuchen sie zu "speichern".
 

Oneixee5

Top Contributor
Das klingt irgendwie komisch. Aus Sicherheitsgründen geht es erst mal gar nicht, dass man irgendwelche Queries als Text zusammenbaut.
Deine Anfrage(ajax) geht an eine URL, z.B.: https://meineseite/pfad/CHF oder https://meineseite/pfad/USD. Am Server wird der Pfad ausgewertet und die Daten der entsprechenden Währung zurückgegeben. Die Query erfolgt dann über ein JPA-Repository und die Währung wird als Parameter an das Interface mit den Queries übergeben. Jetzt musst du das noch erweitern um die Anzahl und die Sortierung, also https://meineseite/pfad/CHF?anzahl=2&sort=1 diese optionalen Parameter kannst du einfach zusätzlich auswerten.
 

mankingwwe

Aktives Mitglied
Also die Methode die ich eig nutze um die Query zusammenzubauen ist das ich ein ein Requst objekt habe. Dieses wird via thymeleaf befüllt. Jedes Suchfeld ist an eine Variable des RequestObjekts gebunden. Angekommen im Controller nehme ich dann die Werte aus dem Objekt und baue die Query zusammen. Zb ist Suchfeld Währung 1 an das feld w1 gebunden. Im Controller kommt dann nur WHERE währung1 = requestobject.getW1() etc... Wenn werte nicht befüllt sind werden sie natürlich der query nicht hinzugefügt. So läuft es wenn ich normal suche.

Wenn ich sortiere, dann schicke ich nen ajax request an die url : /getSortDevisen und sende 2 parameter mit: order und column. Dann im Controller der benannte ablauf: Ich rufe die daten in der db nochmal mit der letzten query ab und sortiere dann nach der mitgeschickten column und asc oder desc, je nachdem was in order gespeichert ist.

Ich nutze kein JPA sondern Jdbc Template.
 

mankingwwe

Aktives Mitglied
Im Controller habe ich dann:

@PostMapping(value="getSortDevisen")
public List<Devisen> sortDevisen(@RequestParam("column") String column, @RequestParam("order") String order){
List<Devisen> devisen = devisenService.getByQueryWithOrderByColumn(queryContainer.getLastQuery(PageTitle.DEVISEN), order, column)
return devisen
}

was ich dort geordnet zurückbekomme befülle ich dann wieder in die tabelle
 

mankingwwe

Aktives Mitglied
Und wegen Sicherheitsmaßnahmen. Die Anwendung für nur für 20 Anwender erreichbar sein, die alle über berechtigte Accounts via login in die Anwendung kommen und nicht sehr IT affin sind. Bitte das bedenken, denn sql injection und sowas ist in diesem Kontext kein Thema. Man soll davon zwar nicht ausgehen, aber niemand hat dort böses vor, denn es sind unsere Leute ;)
 

Oneixee5

Top Contributor
OK dann verrate bloß keinem die Firma oder URL. Denn wenn man bewusst Sicherheitslücken zulässt, kann davon ausgegangen werden, dass es noch weitere gibt. Außerdem wollte ja auch noch nie ein Insider einer Firma schaden oder mit Interna Geld machen.

Zu deiner Methode: sortDevisen - ich sehe gar keine Verbindung mit dem Nutzer. Wenn die letzte Abfrage also zwischenzeitlich von einem anderem Nutzer war, dann bekommt der Nutzer eine falsche Liste oder wie muss ich das verstehen? Dein Konzept ist dubios.
 

mrBrown

Super-Moderator
Mitarbeiter
Du wolltest zwar keine Kritik an deiner Methode hören, aber wenn du das mal alles weglässt und es stattdessen einmal sauber löst, hast du am Ende deutlich einfacheren, weniger fehleranfälligen und besser anpassbaren Code…
 

mankingwwe

Aktives Mitglied
Genau deswegen meide ich eigentlich Foren in denen über Programmierung diskutiert wird. Es gibt kaum eine Gemeinschaft die so toxisch ist und wo jeder soviel von sich selber hält. Habe ganz normal gefragt und gesagt was aufgrund meiner nicht so großen Erfahrung in dem Moment sinn gemacht hat und gerade halt auch funktioniert. Und buff folgen ironische Kommentare in meine Richtung Zwecks IT Sicherheit obwohl ich nie sagte das es sowas nicht gibt, sondern nur unwahrscheinlich ist und auch in dem Moment nicht das Thema war, schlecht machen via Aussagen das mein Konzept dubios sei usw. Danke Oneixee5 das du mich daran erinnert hast dieses Forum zu umgehen.

Da ich deutsch LK hatte, mal ein Tipp: in einem aufsatz sollte man fehlerfrei schreiben, auf youtube kurz und schlüssig und in einem Forum sollte man Aufschlussreich und argumentieren können. Du hast mir nun etliche Male gesagt das meine Sicherheit kacke ist, das meine "verbindung mit dem Nutzer fehlt" und das anscheinend alles absolute Scheiße ist. Nun wie es richtig geht:

[Problem bennen] Dein Konzept macht aus Sicht der IT Sicherheit keinen Sinn, weil... [Problem ausführen.]. Das kannst du lösen, indem du... [Gegenvorschläge erbringen.] Wenn du dieses simple Template drauf hast, dann kannst du gerne wenn ich jemals wieder etwas posten sollte Antworten. Ansonsten behalt deine Meinung für dich. Und spar dir Bewertungen wie dubios, lächerlich, seltsam, merkwürdig etc, weil das nicht fair ist Leuten gegenüber die offen nach Hilfe suchen. Schönen Tag
 

mankingwwe

Aktives Mitglied
Du wolltest zwar keine Kritik an deiner Methode hören, aber wenn du das mal alles weglässt und es stattdessen einmal sauber löst, hast du am Ende deutlich einfacheren, weniger fehleranfälligen und besser anpassbaren Code…
"Du wolltest zwar keine Kritik an deiner Methode hören" Nicht ganz richtg, ich wollte nur gerne bei dem Thema bleiben, weil wir sonst hier stundenlang diskutieren über alles und ich eig nur ne Anwort auf meine Frage wollte^^

Bezüglich ThreadSafe: Wie bereits diskutiert wurde kann es verschiedene Instanzen meines "QueryContainers" geben der die letzte query speichern soll. Würde ich das problem prinzipiell dadurch lösen, dass ich das objekt mit new erzeuge und via

HttpSession httpSession = request.getSession();
httpSession.setAttribute("queryContainer", container);

in der session speichere? Dann würde der user doch immer das selbe objekt bekommen korrekt?
 

mrBrown

Super-Moderator
Mitarbeiter
Natürlich kannst du dir irgendwelche komplexen Wege ausdenken, ein Problem zu lösen, welches du hast, weil du es unbedingt willst ;)
Das speichern in der Session klappt dann solange, bis ein Nutzer zwei Tabs auf hat, und dafür findest du wieder eine umständliche Lösung, bis der Nutzer XY macht und es geht wieder von vorne los.


Oder du schmeißt das Konzept über den Haufen (es ist eben wirklich dubios), und baust stattdessen eine zustandslose API, schickst alle Informationen die der Server zum Antworten braucht in den Request mit, und vermeidest einfach alle Probleme. Der Client fragt dann nicht nur nach „Sortiert nach XY“, sondern beispielsweise nach „Währung Z, sortiert nach XY“, so wie @Oneixee5 das in #9 schon vorgeschlagen hat.
 

mankingwwe

Aktives Mitglied
Natürlich kannst du dir irgendwelche komplexen Wege ausdenken, ein Problem zu lösen, welches du hast, weil du es unbedingt willst ;)
Das speichern in der Session klappt dann solange, bis ein Nutzer zwei Tabs auf hat, und dafür findest du wieder eine umständliche Lösung, bis der Nutzer XY macht und es geht wieder von vorne los.


Oder du schmeißt das Konzept über den Haufen (es ist eben wirklich dubios), und baust stattdessen eine zustandslose API, schickst alle Informationen die der Server zum Antworten braucht in den Request mit, und vermeidest einfach alle Probleme. Der Client fragt dann nicht nur nach „Sortiert nach XY“, sondern beispielsweise nach „Währung Z, sortiert nach XY“, so wie @Oneixee5 das in #9 schon vorgeschlagen hat.

Na gut aber Konzept übern Haufen hört sich hart an, weil soviel ändert sich doch nicht? Wie gesagt aktuell ist jede Textbox via Thymeleaf an eine variable im Objekt gebunden, dass mit in den Controller geschickt wird und die werte enthält. Heißt DevisenRequest. Im Controller habe ich dann @ModelAttribute und kann in diesem Objekt "alle Informationen auslesen die der Server zum Antworten braucht". Der Grund wieso es hier anders lief ist, weil ich die angeklickte spalte sortieren will und da auf ein onclick event reagiere. Ansonsten wenn ich nur nach neuen Kriterien in den Suchfeldern filter, dann reagiere ich aufs "ENTER" und dann wird das Objekt via Thymeleaf in den controller geschossen.

Wenn ich aber auf das onclick reagiere dann habe ich da eben ajax um in den controller zu gelangen und dort muss ich sagen was für daten ich mitgebe. Habe leider keine Ahnug wie ich via ajax sagen kann das ich gerne als daten das thymeleaf model mitgeben würde, das ich dort halte^^. Da ich aber die letzten Daten nochmal brauche stellt sich die frage gebe ich die daten ausm frontend mit ins backend zum sortieren oder die suchkriterien und frage die db nochmal ab und sortiere dann. Da wir sehr winzige datengrößen abfragen wurde entschieden beim sortieren nochmal die daten aus der datenbank zu holen. Das war der grund wieso ich die letzte query gespeichert habe, damit ich das tun kann.

Ich könnte aber nach deinem Vorschlag statt nur column und order auch die suchfelder alle via id abrufen und den inhalt via ajax mitsenden und im controller via @requestParam abfangen. dann kann ich im backend die query wieder dynamisch zusammenbauen, die daten abfragen und sortieren und zurück ins frontend geben. Meinst du das wäre besser?
 

mrBrown

Super-Moderator
Mitarbeiter
Ich könnte aber nach deinem Vorschlag statt nur column und order auch die suchfelder alle via id abrufen und den inhalt via ajax mitsenden und im controller via @requestParam abfangen. dann kann ich im backend die query wieder dynamisch zusammenbauen, die daten abfragen und sortieren und zurück ins frontend geben. Meinst du das wäre besser?
Ja, das ist besser.
 

mihe7

Top Contributor
Zum Thema Query zusammenbauen: das ist an sich kein Problem, wenn man Parameter verwendet. Wenn Du die Fitlerfelder hast, weißt Du, welche Spalten abgefragt werden, insofern kannst Du das SQL mit Parametern versehen und diese in einem zweiten Schritt setzen. Machen wir ständig :)
 

mankingwwe

Aktives Mitglied
Zum Thema Query zusammenbauen: das ist an sich kein Problem, wenn man Parameter verwendet. Wenn Du die Fitlerfelder hast, weißt Du, welche Spalten abgefragt werden, insofern kannst Du das SQL mit Parametern versehen und diese in einem zweiten Schritt setzen. Machen wir ständig :)
Genau das tun wir! :) wie gesagt in dem request objekt haben wir variablen w1,w2,termin,kurs etc und beim zusammenbauen habe wir dann halt Select * from .... where wearung1 = obj.getw1 and waerung2 = obj.getw2 usw... Wir geben nicht einfach ein sql ein. sondern bauen es wie du meintest durch die parameter zusammen. Gut zu wissen, dass das kein Problem ist. Habe es nämlich so verstanden. Danke sehr
 

mihe7

Top Contributor
aber was macht das nun soviel besser? Welchen Nachteil habe ich von meinem Vorgehen?
Ein PreparedStatement wird vorkompiliert (z. B. SQL-Statement übersetzt, Ausführungsplan erstellt, optimiert). Das kann die Datenbank cachen.

Machen wir mal ein kleines Beispiel:
SELECT name, vorname FROM Personen WHERE name = ? AND vorname = ?

Das SQL-Statement sieht unabhängig von den Werten immer gleich aus, damit ist es kein Problem, das übersetzte Statement zu cachen. Wenn Du dagegen die Werte direkt ins SQL einsetzt, bekommst Du auch immer ein anderes SQL:

SELECT name, vorname FROM Personen WHERE name = 'Wurst' AND vorname = 'Hans'
SELECT name, vorname FROM Personen WHERE name = 'Dampf' AND vorname = 'Hans'

Außerdem werden beim PreparedStatement die Parameter nicht mehr ins SQL-Statement eingesetzt, was SQL-Injection unmöglich macht

Umgekehrt gibt es überhaupt keinen Grund, auf ein PreparedStatement zu verzichten.
 

mankingwwe

Aktives Mitglied
Ein PreparedStatement wird vorkompiliert (z. B. SQL-Statement übersetzt, Ausführungsplan erstellt, optimiert). Das kann die Datenbank cachen.

Machen wir mal ein kleines Beispiel:
SELECT name, vorname FROM Personen WHERE name = ? AND vorname = ?

Das SQL-Statement sieht unabhängig von den Werten immer gleich aus, damit ist es kein Problem, das übersetzte Statement zu cachen. Wenn Du dagegen die Werte direkt ins SQL einsetzt, bekommst Du auch immer ein anderes SQL:

SELECT name, vorname FROM Personen WHERE name = 'Wurst' AND vorname = 'Hans'
SELECT name, vorname FROM Personen WHERE name = 'Dampf' AND vorname = 'Hans'

Außerdem werden beim PreparedStatement die Parameter nicht mehr ins SQL-Statement eingesetzt, was SQL-Injection unmöglich macht

Umgekehrt gibt es überhaupt keinen Grund, auf ein PreparedStatement zu verzichten.
Ich benutze aktuell aber JDBC Template und dort sehe ich nicht das man das nutzen könnte. Habe zwar https://www.baeldung.com/spring-jdbc-jdbctemplate beispiele gefunden für named parameter, aber was ich mich halt frage ist wie regel ich das Problem das ich nicht weiß welche parameter benötigt werden? Habe 11 Stück die abgefragt werden könnten und jedes könnte befüllt oder leer sein. Werden wenn felder leer sind die dazugehörigen abfragen + dem "AND" entfernt oder wie soll ich das machen? Müsste nach deinem Beispiel

select * from devisen_tabelle where währung1 = ?

select * from devisen_tabelle where währung1 = ? AND währung2 = ?

select * from devisen_tabelle where währung2 = ?

etc abdecken... Wenn ich wie in deinem Beispiel nur zwei felder habe und die immer befüllt sind ist es ja einfach, aber was wenn name leer ist?

In meinem code prüfe ich alle werte (ob sie befüllt sind .> bedeutet sie nach ihnen wird gefiltert) setze sie samt ihrer spalte in eine liste von bedingungen und joine am ende alle bedingungen mit "AND" als trenner zusammen.
PSEUDOCODE wie ich das aktuell mache:
if(request.getw1() != null){ listeMitBedingungen.add("w1 =" + w1)}
if(request.getw2() != null){ listeMitBedingungen.add("w2 =" + w2)}
am ende: bedingungsstring = String.join("AND",listeMitBedingungen)

Dann jdbctemplate.query("Select * from devisen_tabelle WHERE" + bedingungsstring )
 
Zuletzt bearbeitet:

LimDul

Top Contributor
Da
if(request.getw1() != null){ listeMitBedingungen.add("w1 =" + w1)}
if(request.getw2() != null){ listeMitBedingungen.add("w2 =" + w2)}
am ende: bedingungsstring = String.join("AND",listeMitBedingungen)

Dann jdbctemplate.query("Select * from devisen_tabelle WHERE" + bedingungsstring )
Nur um die bereits gegebenen Antworten nochmal zu unterstreichen, der Code schreit "SQL Injection, Sicherheitslücke" und würde durch kein professionelles Code-Review kommen :)

Für sowas gibt es die Criteria API: https://www.baeldung.com/spring-data-criteria-queries

Alternativ kam sowas auch über normale Prepared Statements zusammenbauen, in dem man nicht schreibt "w1 = " +w1 sondern z.B. es so bau
Java:
]
List<Object> parameterValues = new ArrayList<>();
if(request.getw1() != null) { 
  listeMitBedingungen.add("w1 =?");
  parameterValues.add(w1);
}
// usw
bedingungsstring = String.join("AND",listeMitBedingungen);
String sqlQuery = "Select * from devisen_tabelle WHERE" + bedingungsstring;
jdbctemplate.query(sqlQuery, parameterValues);

Muss noch etwas auf jdbctemplate vermutlich angepasst werden (ggf. die Liste in Array umwandeln etc.). Aber so ist es sauber und sicher.
 

mankingwwe

Aktives Mitglied
Da
Nur um die bereits gegebenen Antworten nochmal zu unterstreichen, der Code schreit "SQL Injection, Sicherheitslücke" und würde durch kein professionelles Code-Review kommen :)

Für sowas gibt es die Criteria API: https://www.baeldung.com/spring-data-criteria-queries

Alternativ kam sowas auch über normale Prepared Statements zusammenbauen, in dem man nicht schreibt "w1 = " +w1 sondern z.B. es so bau
Java:
]
List<Object> parameterValues = new ArrayList<>();
if(request.getw1() != null) {
  listeMitBedingungen.add("w1 =?");
  parameterValues.add(w1);
}
// usw
bedingungsstring = String.join("AND",listeMitBedingungen);
String sqlQuery = "Select * from devisen_tabelle WHERE" + bedingungsstring;
jdbctemplate.query(sqlQuery, parameterValues);

Muss noch etwas auf jdbctemplate vermutlich angepasst werden (ggf. die Liste in Array umwandeln etc.). Aber so ist es sauber und sicher.

Vielen Dank für die Hilfe! :)
 

Ähnliche Java Themen


Oben