# Rückgabetyp einer Datenbankabfrage



## chriss_2oo4 (1. Mai 2008)

Hi,

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?

Wie löst ihr das in euren Anwendungen?


lg Chriss


----------



## Guest (1. Mai 2008)

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?

Schau dir das DAO-Beispiel hier an: http://www.java-forum.org/de/viewtopic.php?t=65520&start=30
So kann man vorgehen, wenn man, aus welchen Gründen auch immer, nicht gleich JPA o.ä. einsetzen möchte.


----------



## chriss_2oo4 (1. Mai 2008)

Hi,

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:


```
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.

Lg Chriss


----------



## Guest (1. Mai 2008)

Du kannst z.B. wie folgt die Daten auslesen
	
	
	
	





```
ResultSetMetaData md = rs.getMetaData();
String columnNames[] = new String[md.getColumnCount()];
for(int i=0; i<columnNames.length; i++)
{
   columnNames[i] = md.getColumnName(i+1);
}
List<Map<String, Object>> list = new LinkedList<Map<String, Object>>();
while(rs.next())
{
   Map<String, Object> row = new HashMap<String, Object>();
   for(int i=0; i<columnNames.length; i++)
   {
      row.put(columnNames[i], rs.getObject(i+1));
   }
   list.add(row);
}
rs.close();
```
Jetzt brauchst du nur noch ein TableModel, welches mit List<Map<String, Object>> zurecht kommt.

Oder von mir aus gleich so einlesen, dass DefaultTableModel verwendet werden kann.
	
	
	
	





```
ResultSetMetaData md = rs.getMetaData();
String columnNames[] = new String[md.getColumnCount()];
for(int i=0; i<columnNames.length; i++)
{
   columnNames[i] = md.getColumnName(i+1);
}
List<Object[]> list = new LinkedList<Object[]>();
while(rs.next())
{
   Object[] row = new Object[columnNames.length];
   for(int i=0; i<columnNames.length; i++)
   {
      row[i] = rs.getObject(i+1);
   }
   list.add(row);
}
rs.close();

Object[][] result = new Object[list.size()][columnNames.length];
System.arraycopy(list.toArray(), 0, result, 0, result.length);

TableModel model = new DefaultTableModel(result, columnNames);
```


----------



## chriss_2oo4 (5. Mai 2008)

Hi,

nochmals Danke für deine Antwort.

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.

Lg Chriss


----------



## Guest (5. Mai 2008)

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.


----------



## chriss_2oo4 (5. Mai 2008)

Hi,

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?


Lg Chriss


----------



## semi (5. Mai 2008)

chriss_2oo4 hat gesagt.:
			
		

> Hi,
> 
> 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.
	
	
	
	





```
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
	
	
	
	





```
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
	
	
	
	





```
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.ä.

Gruß,
Michael


----------



## chriss_2oo4 (6. Mai 2008)

Hi,

vielen danke für die ausführliche Antwort!

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?

Lg Chriss


----------



## semi (6. Mai 2008)

Gehen wir es mal der Reihe nach durch.
Die Query zum Lesen eines Benutzers mit einer bestimmten Nr könnte wie folgt aussehen:


```
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
	
	
	
	





```
// #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
	
	
	
	





```
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.

```
interface ResultMapper<T>
{
   T map(ResultSet rs) throws SQLException;
}
```
Die Implementierung für Benutzer sieht das so aus (es muss zum Query-String passen!)
	
	
	
	





```
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.
	
	
	
	





```
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
	
	
	
	





```
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.
	
	
	
	





```
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:

Gruß,
Michael


----------



## chriss_2oo4 (6. Mai 2008)

Hi,

danke für die ausführliche Antwort!

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:



```
public Connection GetConnection() throws SQLException
	{
		String strConnection = "jdbc:" + "hsqldb:" + "file:" + strFilename
		+ ";" + "shutdown=true";
		return DriverManager.getConnection(strConnection, "root",
				"pw");
	}
```





> Irgendwann mal wirst du feststellen, dass das ganze Blödsinn ist und steigst auf JPA um. icon_lol.gif



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.


Lg Chriss[/quote]


----------



## semi (6. Mai 2008)

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


Das hier wäre ein Anfang :wink: : http://www.java-forum.org/de/viewforum.php?f=52



			
				chriss_2oo4 hat gesagt.:
			
		

> 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.

Gruß,
Michael


----------



## chriss_2oo4 (7. Mai 2008)

Also nochmal Danke für alles!

Lg Chriss


----------

