"Echtes" Software-Engineering - Ein paar Fragen

PlaciD

Böhser Onkel
ID: 55555
L
11 Februar 2007
722
105
Hi,

ich hab ein paar Fragen zur Entwicklung von Software in Java (wobei die Fragen generell für jede objektorientierte Programmiersprache gelten).

1. Wir haben in Software-Engineering gelernt, dass, wenn man wissen muss, welche Objekte eine Klasse hat, eine "List"-Klasse braucht, die eine Liste über alle Objekte führt, die erstellt werden. Klingt für mich logisch. Die Frage ist aber: Wenn wir z.B. eine Bibliotheks-Software programmieren und dort User (Personen, die Bücher ausleihen) anlegen und parallel in der Datenbank speichern. Nun fahren wir das Programm am abend runter. Kein Problem, die User sind ja in der Datenbank permanent gespeichert. Am nächsten morgen starten wir das Programm wieder. Die User sind noch da (in der Datenbank), aber nicht mehr als Objekt in der Anwendung. Wie passt das zusammen? Also soll ich jetzt alle User aus der Datenbank beim Start des Programms auslesen und als Objekte speichern, obwohl ich sie vielleicht bis zum Shutdown garnie brauche? Und wenn nicht, wozu brauch ich dann eine Liste, wenn sie eh keine Übersicht über alle Objekte darstellt (und ich eigentlich besser die db auslesen sollte).

2. Generell weiß ich nicht, wie das bei OBjekten läuft, wenn Fehler auftreten (Exceptions). Kleines Beispiel: Ich erschaffe ein neues User-Objekt und die Datenbank ist aus irgendeinem Grund nicht erreichbar. Die SQL-Anfrage liefert also eine Exception. Natürlich läuft das Programm trotzdem weiter. Es liefert eine Fehlermeldung und bricht nicht komplett ab. Gibt es nun das Objekt? Wenn ja, ist dass dann ein inkonsistenter Zustand? Wie verhindere ich diesen?

3. Dies ist eine Frage speziell für Java: Ist es theoretisch besser, eine Verbindung zur db pro Programm herzustellen oder pro SQL-Abfrage eine Verbindung aufzubauen und wieder zu schließen? Im ersten Fall habe ich keine Ahnung, wie ich das anstelle. Den zweiten Fall praktiziere ich gerade, aber bin mir nicht sicher, ob das so gut ist.

So, das waren die drei Fragen, die mir aktuell den ein oder anderen Gedanken bereiten.

Danke schonmal für alle Antworten,
PlaciD
 
Zu 1) Das mit den Listklassen hab ich noch nie gehört und raffe auch grad nicht, wozu das gut sein soll.

Zu 2) Die SQL-Meldung muss natürlich ausgewertet werden, um einen inkonsistenten Zustand zu vermeiden. Eigentlich sollte sie sowieso ausgewertet werden, als User würde es mich nämlich sehr interessieren, wenn mein Befehl nicht ausgeführt wird. Ich weiß nicht, was du mit der Objekterstellung und der Datenbank meinst, aber wenn du ein Objekt anlegst ist es da - unabhängig davon, ob irgendwelche Daten in irgendwelche Datenbanken geschrieben werden.

Zu 3) Eine SQL-Verbindung aufzubauen und wieder abzubauen kostet Zeit und Resourcen, daher ist eine persistente Verbindung sicherlich besser.
 
Zu 1) Das mit den Listklassen hab ich noch nie gehört und raffe auch grad nicht, wozu das gut sein soll.

Naja, eine Klasse kennt seine Objekte nicht. Wenn du also irgendwann eine Liste brauchst, welche Objekte es gibt (z.B. für eine User-Liste beim Beispiel der Bibliotheksverwaltung), musst du nebenher irgendwo Buch führen. So haben wir das gelernt.

Zu 2) Die SQL-Meldung muss natürlich ausgewertet werden, um einen inkonsistenten Zustand zu vermeiden. Eigentlich sollte sie sowieso ausgewertet werden, als User würde es mich nämlich sehr interessieren, wenn mein Befehl nicht ausgeführt wird. Ich weiß nicht, was du mit der Objekterstellung und der Datenbank meinst, aber wenn du ein Objekt anlegst ist es da - unabhängig davon, ob irgendwelche Daten in irgendwelche Datenbanken geschrieben werden.

Das auswerten sowieso. Mein Problem ist: Was mache ich nun, wenn so eine Fehlermeldung auftaucht. Das Objekt existiert ja, also ist eine Meldung "Objekt nicht erstellt" technisch falsch. Aber eine Meldung wie "OBjekt nicht gespeichert" ist auch sinnlos, wenn es keine Möglichkeit gibt, das Objekt nochmal zu speichern.

Zu 3) Eine SQL-Verbindung aufzubauen und wieder abzubauen kostet Zeit und Resourcen, daher ist eine persistente Verbindung sicherlich besser.

Das klingt logisch. Wie sollte ich dann da vorgehen? Hab mir grad Überlegt, der Datenbankklasse einfach ne static connection zu geben und die nur einmal zu öffnen. Ist das ne gute Idee?

Danke dir auf jedenfall,
PlaciD
 
Zu 2)
Mach halt zuerst die DB-Verbindung. Wenn diese schon fehlschlägt, dann leg das Objekt erst gar nicht an.
 
1. das klingt irgendwie eigenartig... du kannst doch nicht alle daten aus der datenbank ins "programm" laden. mal angenommen du hast sagen wir mal 50000 user... was denkst du passiert da? ich kann mir das gut vorstellen was da passiert ;) also solltest du nur die daten aus der datenbank hollen die du brauchst... wie/wo/was hab ich keine ahnung, ich hab noch nie größere java anwendungen geschreiben. für das was ich schreibe reicht meine frei improvisierte arbeitsweise immer aus... dass ist so nen php oop abklatsch, mit optimierung auf leistung was machmal zu lasten der oop geht ;)

2. das geht ja nun gar nicht... sowas musst du abfangen. das könnte unter umständen zu massiven datenbanken problemen führen... stell dir vor jemand erstellt irgendwelche aufträge mit dem user obwohls ihn gar nicht gibt. und im worst case hat die db keine referentielle integrietät und schon ist der daten gau vorprogrammiert...

3. für jeden query ne neue verbindung? naja das geht vielleicht wenn du einmal inner stunde ein query ausfühst, aber ansonsten sollten die verbindungen bestehen bleiben.
 
Zu 1.
Die Listen die du ansprichst heißen in .Net Collections und werden dazu verwendet, deine Business Objects im Speicher zu halten. Das Modell zur Beschreibung dieses Vorgangs heißt 3-Tier-Architektur, wenn du möchtest kannst du danach mal suchen. Findest ne Menge Informationen darüber =).

Selbstverständlich lädst du nicht "auf Verdacht" alle Objekte, sondern nur, wenn du sie wirklich benötigst. Bei uns ist es üblich, für jede mögliche Kombination aus Filtern eine Abfrage in Form einer Stored Procedure zu schreiben. Die Stored Procedure selektiert notwendige Daten, der Data Access Layer liest die Daten aus der Datenbank und gibt sie weiter an die Business Logic, die wiederum die Business Objects befüllt. Klingt im ersten Moment komplex, ist es aber eigentlich nicht wirklich. Wenn du es mal gemacht hast mutiert das zur puren Fleißarbeit =). Es gibt auch genug Generatoren, die dir eine halbwegs saubere 3-Tier-Architektur generieren.

Wenn dich das Thema interessiert schnapp dir am Besten ein Buch darüber. Das auf die Schnelle zu erklären ist gar nicht so einfach =).

Zu 2.
Das liegt selbstverständlich am Aufbau deiner 3-Tier-Architektur. Sie sollte aber immer so gestrickt sein, dass es definitiv nicht zu Inkonsistenzen kommen kann. Wir lesen die Daten immer im Konstruktor des Business Objects in die einzelnen Properties ein. Wenn er dabei hinfallen sollte, wird das Objekt gar nicht erst erzeugt. Fällt er schon beim Selektieren der Daten bist du ja noch nicht einmal bis zum Erzeugen des Objektes gekommen - dementsprechend gibt es auch keines, das inkonsistent sein könnte.

Und zu 3.
Nein, das ist keine Frage zu Java sondern eine ganz allgemeine Frage =). Es ist üblich, bei jeder Abfrage eine einzelne Datenbankverbindung zu öffnen. Warum? Ganz einfach: Um die Verwaltung der Connection musst du dich nicht mehr kümmern, das macht deine 3-Tier-Architektur von Haus aus. Datenbankverbindung auf - Selektieren - Datenbankverbindung zu. Was du danach mit den Daten machst ist der Datenbank im Prinzip egal =). Und auch von der Performance her ist es bei Weitem nicht so schlimm, wie man vielleicht denken mag: Die meisten großen Datenbanken unterstützen Connection Pooling. Dieser Mechanismus hält Datenbankverbindungen (in einer gewissen Stückzahl) offen für deine Anfragen bereit. Wenn du jetzt deinen "Open" auf die Datenbank machst, reicht dir der Connection Pool nur noch eine gültige Datenbankverbindung. Davon merkst du nichts und es kostet auch kaum Zeit. Solltest du tatsächlich mit einer Datenbank arbeiten, die Connection Pooling nicht unterstützt, kannst du diesen Mechanismus auch mit wenig Aufwand selbst programmieren. Beispiele dazu findet man im Netz.

Die Vorteile aufgrund einer 3-Tier-Architektur sind gigantisch. Alleine mit dem Thema kann ich dir noch mal so viel schreiben =). Dazu bin ich jetzt aber n Tick zu müde *g*. Ich hoffe ich konnte dir ein wenig weiterhelfen =). Gerne kannst du hier auch weitere Fragen stellen.

Karsten
 
Zuletzt bearbeitet:
Hast Recht, ganz egal ist es natürlich nicht =). Ich hab in dem Fall tatsächlich auch nur an selektiven Zugriff gedacht, was ziemlich unrealistisch wäre ;oD.
Aber alles zu seiner Zeit, wichtig ist erst einmal, dass die Basis verstanden wurde =). Jetzt noch Datensätze zu sperren oder Operationen in Transaktionen zusammen zu fassen ist zumindest in .Net kein großer Aufwand mehr.
 
Zu 2)
Mach halt zuerst die DB-Verbindung. Wenn diese schon fehlschlägt, dann leg das Objekt erst gar nicht an.

Die Frage (die im folgenden von Cryptkeeper beantwortet wurde), die sich mir da stellte, war:
Object test = new Object();
Ab wann ist das Objekt nun vorhanden? Ab diesem Aufruf oder erst, wenn der Konstruktor fehlerfrei durchlaufen wurde. Aber da ich jetzt weiß, dass es beim Abbruch das OBjekt garnicht erzeugt, ist die Frage natürlich geklärt.

1. das klingt irgendwie eigenartig... du kannst doch nicht alle daten aus der datenbank ins "programm" laden. mal angenommen du hast sagen wir mal 50000 user... was denkst du passiert da? ich kann mir das gut vorstellen was da passiert ;) also solltest du nur die daten aus der datenbank hollen die du brauchst... wie/wo/was hab ich keine ahnung, ich hab noch nie größere java anwendungen geschreiben. für das was ich schreibe reicht meine frei improvisierte arbeitsweise immer aus... dass ist so nen php oop abklatsch, mit optimierung auf leistung was machmal zu lasten der oop geht ;)

Das war meine Frage. Und so schön es sich in der Theorie alles anhört, ich sitz halt dann da und denke mir "Und wie wird das im richtigen Leben gemacht?". Was man natürlich machen kann ist eine solche List-Klasse, die ihre Liste eben aus der db holt.


Zu 1.
Die Listen die du ansprichst heißen in .Net Collections und werden dazu verwendet, deine Business Objects im Speicher zu halten. Das Modell zur Beschreibung dieses Vorgangs heißt 3-Tier-Architektur, wenn du möchtest kannst du danach mal suchen. Findest ne Menge Informationen darüber =).

Selbstverständlich lädst du nicht "auf Verdacht" alle Objekte, sondern nur, wenn du sie wirklich benötigst. Bei uns ist es üblich, für jede mögliche Kombination aus Filtern eine Abfrage in Form einer Stored Procedure zu schreiben. Die Stored Procedure selektiert notwendige Daten, der Data Access Layer liest die Daten aus der Datenbank und gibt sie weiter an die Business Logic, die wiederum die Business Objects befüllt. Klingt im ersten Moment komplex, ist es aber eigentlich nicht wirklich. Wenn du es mal gemacht hast mutiert das zur puren Fleißarbeit =). Es gibt auch genug Generatoren, die dir eine halbwegs saubere 3-Tier-Architektur generieren.

Wenn dich das Thema interessiert schnapp dir am Besten ein Buch darüber. Das auf die Schnelle zu erklären ist gar nicht so einfach =).

Ich sprach keine bestimmten Listen an, aber in Java heißen sie auch Collections. Und auf die genaue Arbeitsweise wollte ich auch nicht raus, aber danke für deine Einführung. Hört sich interessant an.

Zu 2.
Und zu 3.
Nein, das ist keine Frage zu Java sondern eine ganz allgemeine Frage =). Es ist üblich, bei jeder Abfrage eine einzelne Datenbankverbindung zu öffnen. Warum? Ganz einfach: Um die Verwaltung der Connection musst du dich nicht mehr kümmern, das macht deine 3-Tier-Architektur von Haus aus. Datenbankverbindung auf - Selektieren - Datenbankverbindung zu. Was du danach mit den Daten machst ist der Datenbank im Prinzip egal =). Und auch von der Performance her ist es bei Weitem nicht so schlimm, wie man vielleicht denken mag: Die meisten großen Datenbanken unterstützen Connection Pooling. Dieser Mechanismus hält Datenbankverbindungen (in einer gewissen Stückzahl) offen für deine Anfragen bereit. Wenn du jetzt deinen "Open" auf die Datenbank machst, reicht dir der Connection Pool nur noch eine gültige Datenbankverbindung. Davon merkst du nichts und es kostet auch kaum Zeit. Solltest du tatsächlich mit einer Datenbank arbeiten, die Connection Pooling nicht unterstützt, kannst du diesen Mechanismus auch mit wenig Aufwand selbst programmieren. Beispiele dazu findet man im Netz.

Hmm, ok. Aktuell fahre ich ein Mittelding. Jede Methode öffnet die db-Verbindung, wenn es sie braucht, und schließt sie dann zum Schluss wieder.

So, diese ganzen Antworten haben bei mir jetzt eine sehr konkrete Frage aufgeworfen:
Wir haben das bisher für unser Projekt so gehalten:
Ein db-package mit einer db-Klasse. Man erzeugt ein Datenbankobjekt, hatt dann Konstruktoren wie Open(), Close(), Select() usw. und greift darüber auf die db zu. Bisher war es so, dass db.Select(String sql) ein ResultSet zurückgegeben hat. Eine Select-Abfrage lief also wiefolgt:

Database db = new Database();
db.Open();
ResultSet result = db.Select("SELECT * FROM table");
db.Close();

Ist das gut so? Oder sollte man Open und Close private machen und die Aufrufe in der Select durchführen? Oder sollte man generell alles anders machen?
Die db-Klasse an sich kann nicht viel, ist eher, weil der Prof des so will. Das einzig wirklich gute ist, dass bei jedem SQL-Befehl automatisch rows-Affacted und der auto_increment_key (sofern vorhanden) gespeichert werden und man über nen Getter drauf zugreifen kann.

Danke euch allen für die Antworten und Grüße,
PlaciD
 
hmm, ich schreib dir mal ein kleines szenario, dann kannst du drüber nachdenken, ob das überhaupt gut so ist. nehmen wir dazu deine bibliothek an.

sei ein buch X einmal vorhanden.

angenommen es gäbe zwei terminals t1 und t2 an denen sich 2 personen unabhängig voneinander bücher reservieren und dann aus dem magazin abholen können. dazu druckt ihnen das terminal einen abholschein.

nun kommen zwei personen p1 und p2 an die terminals und beide wollen das gleiche buch X zur gleichen zeit reservieren um es abholen zu können.

dein programm wird nun vermeindlich erst prüfen ob das buch vorhanden ist, wenn ja, wird es dies reservieren und einen abholschein drucken

der ablauf:

t1 prüft ob buch X vorhanden ist und ob es nicht reserviert ist und bekommt ein ja.

t2 prüft ob buch X vorhanden und bekommt ebenfalls ein ja

t1 setzt buch X auf reserviert und druckt den abholschein für p1

t2 setzt buch x auf reserviert und druckt den abholschein für p2

p1 holt sich das buch...
p2 reserviert noch andere bücher und geht später zum magazin, dann kommt der gau.

warum passiert das?
wenn du gleich nach dem select die db-verbindung schließt gilt diese transaktion definitiv als beendet. besser wäre es, wenn der gesamte vorgang in einer transaktion läuft. aber dazu müsstest du dich auch mit dem verwendeten dbms und dessen funktionsweisen auseinander setzen. ;)
 
Ich denke eine pauschale Antwort auf die Frage gibt es nicht =). Du kannst Open und Close in die Select-Methode verlagern, wenn du denkst, dadurch Vorteile zu haben. Persönlich finde ich aber, dass das Datenbank-Öffnen und -Schließen nicht in die Datenbank-Klasse gehört. Immerhin soll sie ja nur die Datenbankverbindung kapseln, und kein Eigenleben führen =). Bei mir sieht der Code also deinem Beispiel entsprechend aus.

Übrigens steht dein Prof nicht allein mit dem Wunsch an eine eigene Datenbank-Klasse ;o). Mal abgesehen von Rows Affected und Auto Increment kannst du noch andere Sachen einwandfrei in der Klasse handhaben:
- Kapselung des Funktionsaufrufs auf Datenbank-Funktionen (Stored Procedures, User Defined Functions etc.); alleine das Erstellen der Parameter an die Funktionen kann viel unnötigen Code kosten, wenn die Datenbankklasse das nicht kapselt
- Exception Handling; wenn notwendig reagieren auf Error Codes - ist zum Beispiel beim Sql Server unter .Net sinnvoll, wenn du auf unterschiedliche Sql Exceptions reagieren möchtest
- Zugriff auf datenbankspezifische Funktionen (z. B. Versionsnummer abrufen)

Edit:
Da war Thomas wohl schneller =). Recht hast du, wenn das Öffnen und Schließen der Datenbankverbindung in der DbConnection-Klasse gemacht wird kann man auch keine Transaktionen über mehrere Aufrufe setzen. Also noch ein Grund mehr =).
 
hmm, ich schreib dir mal ein kleines szenario, dann kannst du drüber nachdenken, ob das überhaupt gut so ist. nehmen wir dazu deine bibliothek an.

sei ein buch X einmal vorhanden.

angenommen es gäbe zwei terminals t1 und t2 an denen sich 2 personen unabhängig voneinander bücher reservieren und dann aus dem magazin abholen können. dazu druckt ihnen das terminal einen abholschein.

nun kommen zwei personen p1 und p2 an die terminals und beide wollen das gleiche buch X zur gleichen zeit reservieren um es abholen zu können.

dein programm wird nun vermeindlich erst prüfen ob das buch vorhanden ist, wenn ja, wird es dies reservieren und einen abholschein drucken

der ablauf:

t1 prüft ob buch X vorhanden ist und ob es nicht reserviert ist und bekommt ein ja.

t2 prüft ob buch X vorhanden und bekommt ebenfalls ein ja

t1 setzt buch X auf reserviert und druckt den abholschein für p1

t2 setzt buch x auf reserviert und druckt den abholschein für p2

p1 holt sich das buch...
p2 reserviert noch andere bücher und geht später zum magazin, dann kommt der gau.

warum passiert das?
wenn du gleich nach dem select die db-verbindung schließt gilt diese transaktion definitiv als beendet. besser wäre es, wenn der gesamte vorgang in einer transaktion läuft. aber dazu müsstest du dich auch mit dem verwendeten dbms und dessen funktionsweisen auseinander setzen. ;)

Ich glaube, dass bei uns in der Studiengruppe 60% nicht verstehen würden, was du da gerade redest, obwohl das Thema unserer DBS-Vorlesung war :D
Was ich damit sagen will: Wir machen grad unsere ersten Gehversuche in "richtigen" Programmen mit GUI, da ist eines unserer kleineren Probleme ein Lost Update oder ähnliches. Dazu müsste man sich tatsächlich mal näher mit mysql auseinandersetzen und gucken, wie man das in Java lösen kann. Da wir aber nur das eine Programm local laufen lassen und die db ebenfalls local läuft, ist das Problem kein wirkliches ;)
Trotzdem danke für deine Ausführungen. Wenn ich mal langeweile habe, werde ich das so umbauen, dass die Konsistenz der Daten gewährleistet ist. Ist sicherlich spannend, aber hat eher niedrigere Priorität.

Ich denke eine pauschale Antwort auf die Frage gibt es nicht =). Du kannst Open und Close in die Select-Methode verlagern, wenn du denkst, dadurch Vorteile zu haben. Persönlich finde ich aber, dass das Datenbank-Öffnen und -Schließen nicht in die Datenbank-Klasse gehört. Immerhin soll sie ja nur die Datenbankverbindung kapseln, und kein Eigenleben führen =). Bei mir sieht der Code also deinem Beispiel entsprechend aus.

Übrigens steht dein Prof nicht allein mit dem Wunsch an eine eigene Datenbank-Klasse ;o). Mal abgesehen von Rows Affected und Auto Increment kannst du noch andere Sachen einwandfrei in der Klasse handhaben:
- Kapselung des Funktionsaufrufs auf Datenbank-Funktionen (Stored Procedures, User Defined Functions etc.); alleine das Erstellen der Parameter an die Funktionen kann viel unnötigen Code kosten, wenn die Datenbankklasse das nicht kapselt
- Exception Handling; wenn notwendig reagieren auf Error Codes - ist zum Beispiel beim Sql Server unter .Net sinnvoll, wenn du auf unterschiedliche Sql Exceptions reagieren möchtest
- Zugriff auf datenbankspezifische Funktionen (z. B. Versionsnummer abrufen)

Edit:
Da war Thomas wohl schneller =). Recht hast du, wenn das Öffnen und Schließen der Datenbankverbindung in der DbConnection-Klasse gemacht wird kann man auch keine Transaktionen über mehrere Aufrufe setzen. Also noch ein Grund mehr =).

OK, dann werde ich das so lösen wie bisher, evtl. mit kleineren Änderungen.

Danke euch beiden,
PlaciD
 
Dann mach dir am besten einen Vermerk in deine "DB-Klasse". man ist später schnell mal zu bequem und nimmt klassen, her die man schon mal "programmiert" hat und übernimmt damit probleme die damals bekannt aber nebensächlich waren, die man später aber längst wieder vergessen hat ;)