Du verwendest einen veralteten Browser. Es ist möglich, dass diese oder andere Websites nicht korrekt angezeigt werden. Du solltest ein Upgrade durchführen oder ein alternativer Browser verwenden.
ich habe eine Klasse DatabaseManager, die die Verbindung und Verwaltung der Datenbank kapselt. Diese Klasse enthält die Methoden die Update, Query, Close.
Nun sollte die Methode Query einen Rückgabewert haben, damit ich in anderen Klassen einen Zugriff auf das Resultat aber DB-Abfrage habe.
Naheliegend wäre ja, das ResultSet zurück zugeben, aber ich weiß nicht ob das eine gute Lösung ist, weil ein Resultset benötigt - glaube ich zumindest - eine bestehende Verbindung zur DB und dann muss ich auch noch in anderen Klassen das java.sql Paket benutzen.
Ein Vektor oder Array ist ja auch relativ schwierig, weil man ja nicht weiß wieviele Spalten mit SELECT * FROM ... selektiert werden?
Zunächst mal ist ein SELECT * keine gute Idee. Man erkennt daran nicht sofort im Code, was für Daten gelesen
werden und muss zusätzlich in die Datenbank schauen, um dies zu erfahren.
Die Anzahl und die Namen der Spalten der Ergebnismenge kannst du über das ResultSet ermitteln (siehe
resultSet.getMetaData()). Die Frage ist aber, willst du das wirklich?
erstmal danke für deine Antwort! Ich bin Java-Neuling, von daher kenn ich JPA nicht.
Ich möchte meine Frage mal anahnd eines Beispiels etwas ausführlicher beschreiben:
Code:
class DatabaseManager {
...
public [b]ResultSet [/b]Query(a_strStmt) {
...
}
...
}
class MyFrame {
...
public void FillTable() {
DatabaseManager dbm = new DatabaseManager();
ResultSet rs = dbm.Query("SELECT * FROM IRGENDWAS");
//Nun sollte eine Tabelle mit Daten gefüllt werden
}
...
}
Jetzt müsste ich in MyFrame ein Objekt der Klasse MyFrame verwalten -> Möchte ich aber nicht. Deshalb wollte ich wissen, wie man soetwas am elegantesten löst, ob man eine Klasse die die Informatonen aus einer relationalen Tabelle in einem Objekt kapselt, oder ob es eine andere Lösung gibt.
Bin erst jetzt wieder dazugekommen weiter zu basteln. Sollte man das TableModel nur für Komponenten (z. B. JTable) einsetzen, oder kann das Allgemein verwendet werden? Möchte nämlich eine allgemeine Schnittstelle zur Datenbank definieren, also wo auch für Algorithmen o. Ä. verwendet werden kann.
TableModel ist für JTable gedacht, für nichts anderes sonst. Trenne einfach den JDBC-Teil vom GUI-Code.
Das erreichst du am einfachsten, wenn du eine DAO-Schicht einführst (siehe Link oben) und in JTable nur
noch mit Objekten, nicht mit JDBC, arbeitest. Versuche auch nichts zu verallgemeinen oder irgendwelche
Frameworks zu schreiben, solange du die Grundlagen noch nicht komplett drauf hast.
ich versteh nicht ganz was der Unterschied zwischen einem Interface und einer DAO-Schicht sein soll, oder ist das Ein und das Selbe?
So wie ich das Verstanden habe schreibe ich eine bzw. mehrere Klassen, die beispielsweise den Inhalt einer DB-Tabelle in einem Objekt kapseln und erstelle eine Schnittstelle, die vorgibt welche Methoden später implementiert werden müssen. In der Klasse die die Schnittstelle implementiert, führe ich dann die eigentlichen DB-Abfragen durch und gib die gewünschten Objekte zurück?
Nur was ich dann eben nicht verstehe: Wenn ich die Tabellen in Objekte kapsel und ein Interface dazuschreibe und die einezelnen Methoden implementieren, dann muss ich ja für jede Tabelle aus meiner DB eine eigene Verbindung zur Datenbank aufbauen. Also ich habe eine Tabelle Wort, Satz und WortInSatz.
Dann hab ich ja auch drei Klassen Wort, Satz und WortInSatz mit den Accsessors und eine DAO-Schnittstelle. Jetzt muss ich ja in jeder Implementierung eine Datenbankverbindung aufbauen?
Und wo packe ich dann die Erzeugung der Tabellen hin, dann brauch ich ja nochmal eine Klasse die das übernimmt und somit wieder eine Verbindung zur DB aufbaut?
Wenn ich das so mache, hab ich noch eine Frage: Ich habe sehr viele Objekte bzw. Datensätze in einer Tabelle, wie groß kann denn so eine ArrayList o. Ä. werden ohne dass die Performance meiner Anwendung in die Knie zwinge, oder ist es im Allgemeinen bei einer großen Datenmenge besser die derzeit benörtigten Daten direkt aus der DB zu holen?
ich versteh nicht ganz was der Unterschied zwischen einem Interface und einer DAO-Schicht sein soll, oder ist das Ein und das Selbe?
So wie ich das Verstanden habe schreibe ich eine bzw. mehrere Klassen, die beispielsweise den Inhalt einer DB-Tabelle in einem Objekt kapseln und erstelle eine Schnittstelle, die vorgibt welche Methoden später implementiert werden müssen. In der Klasse die die Schnittstelle implementiert, führe ich dann die eigentlichen DB-Abfragen durch und gib die gewünschten Objekte zurück?
Genau so. Später kannst du mal ein kleines Codereview machen und stellst fest, dass der Code der Abfragen,
bis auf paar Variationen, immer wieder gleich ist.
- Connection anfordern
- Statement vorbereiten
- evtl. Parameter setzen
- ausführen
- Ergebnis lesen
- Alle Resourcen wieder freigeben
Das kannst du dann in eine abstrakte DAO-Implementierung oder eine Hilfsklasse mit zusätzlichen Hilfsmethoden
etc. verfrachten.
z.B. eine Abfrage eines Benutzers aus dem Dao-Beispiel könnte in der BenutzerDao-Implementierung wie folgt aussehen.
Code:
public Benutzer readBenutzer(Integer nr) throws DaoException
{
assert nr != null : "Benutzernummer darf nicht null sein!";
return new JdbcQuery<Benutzer>(
benutzerMapper,
"SELECT BenutzerNr, Pseudonym, Passwort FROM Benutzer WHERE BenutzerNr = ?"
)
.setParameter(1, nr)
.getSingleResult();
}
Das Lesen aller Benutzer so
Code:
public List<Benutzer> readBenutzerList() throws DaoException
{
return new JdbcQuery<Benutzer>(
benutzerMapper,
"SELECT BenutzerNr, Pseudonym, Passwort FROM Benutzer"
).getResultList();
}
Der BenutzerMapper könnte wie folgt aussehen
Code:
benutzerMapper = new ResultMapper<Benutzer>() {
public final Benutzer map(final ResultSet rs) throws SQLException
{
return new Benutzer()
.setNr(rs.getInt("BenutzerNr"))
.setPseudonym(rs.getString("Pseudonym"))
.setPasswort(rs.getString("Passwort"));
}
};
Die eigentliche Abfrage ist in der Hilfsklasse JdbcQuery (die du noch schreiben müsstest ) ausgelagert
und kann immer wieder verwendet werden.
chriss_2oo4 hat gesagt.:
Nur was ich dann eben nicht verstehe: Wenn ich die Tabellen in Objekte kapsel und ein Interface dazuschreibe und die einezelnen Methoden implementieren, dann muss ich ja für jede Tabelle aus meiner DB eine eigene Verbindung zur Datenbank aufbauen. Also ich habe eine Tabelle Wort, Satz und WortInSatz.
Nein, nicht unbedingt. Es kann immer wieder die gleiche Connection verwendet werden. Die Connection kannst
du z.B. in irgendeinem Singleton halten, nennen wir es mal ServiceLocator, welches ein DataSource oder
ConnectionPool verwaltet.
chriss_2oo4 hat gesagt.:
Dann hab ich ja auch drei Klassen Wort, Satz und WortInSatz mit den Accsessors und eine DAO-Schnittstelle. Jetzt muss ich ja in jeder Implementierung eine Datenbankverbindung aufbauen?
Bei jedem Aufruf wird eine Connection angefordert (nicht mit "aufgebaut" gleichsetzen) und direkt wieder freigegeben
(nicht mit "geschlossen" gleichsetzen). Da ist wieder der ServiceLocator, DataSource, ConnectionPool etc. im Spiel.
chriss_2oo4 hat gesagt.:
Und wo packe ich dann die Erzeugung der Tabellen hin, dann brauch ich ja nochmal eine Klasse die das übernimmt und somit wieder eine Verbindung zur DB aufbaut?
Die erzeugst du ausserhalb deiner Anwendung. Deine Anwendung verwendet eine bestehende Datenbank.
chriss_2oo4 hat gesagt.:
Wenn ich das so mache, hab ich noch eine Frage: Ich habe sehr viele Objekte bzw. Datensätze in einer Tabelle, wie groß kann denn so eine ArrayList o. Ä. werden ohne dass die Performance meiner Anwendung in die Knie zwinge, oder ist es im Allgemeinen bei einer großen Datenmenge besser die derzeit benörtigten Daten direkt aus der DB zu holen?
Wie hoch ist dein Monitor? :wink:
Man kann Daten seitenweise laden oder die Ergebnismenge auf z.B. 200 Datensätze beschränken und eine Suchmaske
anbieten. Es gibt zig Möglichkeiten mit grossen Datenmengen umzugehen. Stichwort: ValueListHandler o.ä.
Jetzt hab ich aber noch eine kurze Frage, google hat nichts hilfreiches dazu ausgespuckt, was ist ein ResultMapper und wie muss ich bei so einem Codegerüst die JDBC-Query aufbauen. Sorry hab soetwas noch nie gesehen.
Und wenn ich eine Globale Connection benutze, wann beende ich diese dann?
Gehen wir es mal der Reihe nach durch.
Die Query zum Lesen eines Benutzers mit einer bestimmten Nr könnte wie folgt aussehen:
Code:
public Benutzer readBenutzer(Integer nr) throws DaoException
{
Connection con = null;
PreparedStatement stmt = null;
try
{
// Connection anfordern, woher auch immer
con = getConnection();
// #1: Statement vorbereiten
stmt = con.prepareStatement("SELECT BenutzerNr, Pseudonym, Passwort FROM Benutzer WHERE BenutzerNr = ?");
// #2: evtl. Parameter setzen
stmt.setInt(1, benutzerNr);
// ausführen
ResultSet rs = stmt.executeQuery();
// #3: Ergebnis lesen
Benutzer result = null;
if(rs.next())
{
result = new Benutzer();
result.setNr(rs.getInt("BenutzerNr"));
result.setPseudonym(rs.getString("Pseudonym"));
result.setPasswort(rs.getString("Passwort"));
}
rs.close();
return result;
}
catch(SQLException e)
{
// Fehler loggen + Fehlerbehandlung bzw. Fehler weitergeben
e.printStackTrace(); // später durch Logging ersetzen!
// Der Einfachheit halber werfen wir die Exception weiter
throw new DaoException(e);
}
finally
{
// Alle Resourcen wieder freigeben
if(stmt != null)
{
try
{
stmt.close();
}
catch(SQLException e)
{
// Fehler loggen + Fehlerbehandlung, wobei hier keine sinnvolle Reaktion möglich ist
e.printStackTrace(); // später durch Logging ersetzen!
}
}
if(con != null)
{
try
{
con.close();
}
catch(SQLException e)
{
// Fehler loggen + Fehlerbehandlung, wobei hier keine sinnvolle Reaktion möglich ist
e.printStackTrace(); // später durch Logging ersetzen!
throw new DaoException(e);
}
}
}
}
Dies wird bei so gut wie jeder lesenden Abfrage ähnlich aussehen.
Die mit #1, #2 und #3 markierten Abschnitte sind variabel bzw. von Query zu Query unterschiedlich.
#1 SQL String
#2 Setzen aller Parameter der Abfrage
#3 Ergebnis der Abfrage lesen und mappen
Betrachte jetzt den Kern des ganzen
Code:
// #1: Statement vorbereiten
stmt = con.prepareStatement(query);
// #2: evtl. Parameter setzen
// kommt noch
// ausführen
ResultSet rs = stmt.executeQuery();
// #3: Ergebnis lesen
T result = null; // Kann alles mögliche sein, nicht nur Benutzer
if(rs.next())
{
// Mapping
result = mapper.map(rs); // ResultSet rein, Objekt als Ergebnis raus
}
rs.close();
return result;
bzw. bei einer Liste als Ergebnis
Code:
List<T> result = new LinkedList<T>();
while(rs.next())
{
result.add(mapper.map(rs));
}
#1 ist das einfachste. Irgendwo den Query-String zwischenspeichern.
Bei #3 musst du den Typen und das Mapping variabel machen. Ein Mapper kriegt ein ResultSet
und liefert die Instanz einer bestimmten Klasse.
Code:
interface ResultMapper<T>
{
T map(ResultSet rs) throws SQLException;
}
Die Implementierung für Benutzer sieht das so aus (es muss zum Query-String passen!)
Code:
ResultMapper<Benutzer> benutzerMapper = new ResultMapper<Benutzer>() {
public Benutzer map(final ResultSet rs) throws SQLException
{
Benutzer benutzer = new Benutzer();
benutzer.setNr(rs.getInt("BenutzerNr"));
benutzer.setPseudonym(rs.getString("Pseudonym"));
benutzer.setPasswort(rs.getString("Passwort"));
return benutzer;
}
};
#2 Parameter sind etwas umständlicher, da es verschiedene Typen geben kann.
Wir definieren eine abstrakte Klasse QueryParameter, die ihren Wert an einer
gegebenen Position in der Parameterliste eines Statements setzen kann.
Code:
abstract class QueryParameter<V>
{
private V value;
public QueryParameter(final V value)
{
this.value = value;
}
protected final V getValue()
{
return value;
}
/**
* Setzt den Wert des Parameters an der Position 'index' im gegebenen Statement.
*
* @param stmt PreparedStamement, in das der Parameter gesetzt wird.
* @param index Index des Parameters im Statement.
* @throws SQLException Ungültiger Index oder Fehler beim Setzen des Parameters.
*/
abstract void setParameter(final PreparedStatement stmt, final int index) throws SQLException;
}
Die Implementierung für Integer und String sieht dann so aus
Code:
QueryParameter<Integer> integerParameter = new QueryParameter<Integer>(42) {
@Override
public void setParameter(final PreparedStatement stmt, final int index) throws SQLException
{
if(getValue() != null)
{
stmt.setInt(index, getValue());
}
else
{
stmt.setNull(index, Types.INTEGER);
}
}
});
QueryParameter<String> stringParameter = new QueryParameter<String>("HalloWelt") {
@Override
public void setParameter(final PreparedStatement stmt, final int index) throws SQLException
{
if(getValue() != null)
{
stmt.setString(index, getValue());
}
else
{
stmt.setNull(index, Types.VARCHAR);
}
}
});
OK, so sieht dann die Hilfsklasse JdbcQuery aus. Den Rest kannst du ergänzen.
Code:
public class JdbcQuery<T>
{
private String query;
private ResultMapper<T> mapper;
private Map<Integer, QueryParameter<?>> parameterMap = new HashMap<Integer, QueryParameter<?>>();
public JdbcQuery(final ResultMapper<T> mapper, final String query)
{
assert mapper != null : "Mapper darf nicht null sein!";
assert query != null : "Query String darf nicht null sein!";
this.mapper = mapper;
this.query = query;
}
public JdbcQuery<T> setParameter(final int index, final Integer value)
{
assert index > 0 : "Index muss groesser als 0 sein!";
parameterMap.put(index, new QueryParameter<Integer>(value) {
@Override
public void setParameter(final PreparedStatement stmt, final int index) throws SQLException
{
if(getValue() != null)
{
stmt.setInt(index, getValue());
}
else
{
stmt.setNull(index, Types.INTEGER);
}
}
});
return this;
}
public JdbcQuery<T> setParameter(final int index, final String value)
{
... gleiches mit String, wie oben
return this;
}
public JdbcQuery<T> setParameter(final int index, final Boolean value)
{
... gleiches wie oben, aber für Boolean (Types.BIT)
return this;
}
/// usw. mit anderen Typen
public List<T> getResultList() throws DaoException
{
// ... das ganze try-ctch etc., wie oben beschrieben
con = getConnection();
stmt = con.prepareStatement(query);
for(Map.Entry<Integer, QueryParameter<?>> param : parameterMap.entrySet())
{
param.getValue().setParameter(stmt, param.getKey());
}
ResultSet rs = stmt.executeQuery();
List<T> result = new LinkedList<T>();
while(rs.next())
{
result.add(mapper.map(rs));
}
rs.close();
return result;
...
}
public T getSingleResult() throws DaoException
{
...
con = getConnection();
stmt = con.prepareStatement(query);
for(Map.Entry<Integer, QueryParameter<?>> param : parameterMap.entrySet())
{
param.getValue().setParameter(stmt, param.getKey());
}
ResultSet rs = stmt.executeQuery();
T result = null;
if(rs.next())
{
result = mapper.map(rs);
if(rs.next())
{
// Exception werfen, da mehr als ein Ergebnis vorhanden
}
}
rs.close();
return result;
...
}
... getConnection() etc.
}
Irgendwann mal wirst du feststellen, dass das ganze Blödsinn ist und steigst auf JPA um. :lol:
Das Problem mit der Connection hab ich aber immer noch nicht 100% kapiert:
Muss die Methode getConnection(); dann immer das selbe Connection-Objekt zurückgeben oder kann für jeden Aufruf ein neues Objekt erstellt und zurückgegeben werden, also etwa so:
JPA = Java Persistence API? Hast du evtl. einen Link, wo ich mich einlesen könnte
Ich habe eigentlich nur eine "kleine" Anwendung die mir Sätze, einzelne Wörter und Relationen zwischen Wörtern und Sätzen speichern soll.
Meine Anwendung muss nicht auf einer DAO-Schicht basieren, ich dachte nur das es gut ist eine DAO-Schicht zu implementieren, aber eigentlich schon recht umständlich für so ein kleines Projekt, trozdem gut zu wissen wie man es macht.
Du kannst bei dem getConnection() ein Connection-Pool verwenden. z.B. das hier: http://jakarta.apache.org/commons/dbcp/index.html
Suche auch hier im Forum danach. Das Thema gab es hier bereits sehr oft.
chriss_2oo4 hat gesagt.:
JPA = Java Persistence API? Hast du evtl. einen Link, wo ich mich einlesen könnte
Ich habe eigentlich nur eine "kleine" Anwendung die mir Sätze, einzelne Wörter und Relationen zwischen Wörtern und Sätzen speichern soll.
Meine Anwendung muss nicht auf einer DAO-Schicht basieren, ich dachte nur das es gut ist eine DAO-Schicht zu implementieren, aber eigentlich schon recht umständlich für so ein kleines Projekt, trozdem gut zu wissen wie man es macht.
Umständlich ist es nur anfangs. Wenn du das durch hast, sind die Abfragen recht kurz. Es ist zwar wie "das Rad neu
erfinden", hat aber den Vorteil, dass du bei fertigen Frameworks auch nachvollziehen kannst, was hinter den Kulissen
passiert.