Warning: include_once(/var/www/html/pmwiki-2.2.86/cookbook/soap4pmwiki/soap4pmwiki.php): failed to open stream: No such file or directory in /var/www/html/fields/cgp08/local/config.php on line 4

Warning: include_once(): Failed opening '/var/www/html/pmwiki-2.2.86/cookbook/soap4pmwiki/soap4pmwiki.php' for inclusion (include_path='.:/opt/php/lib/php') in /var/www/html/fields/cgp08/local/config.php on line 4

Warning: Cannot modify header information - headers already sent by (output started at /var/www/html/fields/cgp08/local/config.php:4) in /var/www/html/pmwiki-2.2.86/pmwiki.php on line 1250
Computergrafik-Praktikum Sommersemester 2008 - Main - Gruppe Datenbank

Dokumentation der Datenbank und des Java-Back-Ends

Diese Dokumentation beschreibt Probleme und Lösungen, die bzgl. der Datenbank bei der Entwicklung der Applikation zur Visualisierung von Immatrikulationsdaten zu bearbeiten waren. Darüber hinaus wird dargestellt, in welcher Weise das Java-Backend mit der entwickelten Datenbank-Struktur interagiert.

Inhaltsverzeichnis

  1. Ramenbedingungen
  2. Backend
  3. Update aus dem Rechenzentrum

















Autoren: Johannes Emden und Peer Wagner


Rahmenbedingungen

Zu Beginn des Computergrafikpraktikums 2008 stand ein aus dem operativen System des Studentenwerks generiertes Data Warehouse zur Verfügung, in dem anonymisiert Studenten- und Absolventendaten seit dem Sommersemester 1995 abgelegt waren. Dieses Data Warehouse bestand im Wesentlichen aus zwei Faktentabellen, einer für die Studierenden mit ca. 460.000 Datesätzen und einer für die Absolventen mit ca. 26.000 Datensätzen. Darüber hinaus waren weitere Dimensionstabellen mit detailierten Informationen über die Fremdschlüssel aus beiden Faktentabellen vorhanden.

Faktentabelle der Studierenden und ihre Beziehungen zu den Dimensionstabellen
Faktentabelle der Absolventen und ihre Beziehungen zu den Dimensionstabellen



Back-End

Das Back-End besteht aus dem Java-Back-End, einem Apache Tomcat Server und einer MySQL-Datenbank zur Speicherung der Daten. Die Kommunikation des Flex-Clients mit der Datenbank erfolgt über das Java-Back-End. Das Java-Back-End fragt Daten aus der Datenbank ab, fasst sie zu Objekten (ValueObjects) zusammen und sendet sie an das Flex-Front-End, um sie im Webinterface darzustellen.

Java-Back-End

Das Java-Back-End implementiert die Interfaces IInitialService und IQueryService zur Anbindung des Flex-Clients. Dabei werden Java-Methoden zu Abfrage der einzelnen Filtertypen bereitgestellt. Die meisten dieser Methoden erwarten ein FilterVO (ValueObject) als Parameter. Der Rückgabewert der Methoden ist jeweils eine Liste von TupleVOs, QuadrupleVOs oder NameQuantityVOs. In den folgenden Abschnitten werden ValueObjects und Services im Einzelnen ausführlich beschrieben.

ValueObjects

Um Daten effizient zusammenzufassen und für den Datenaustausch zwischen Flex-Client und Java-Server zu verwenden, werden verschieden ValueObjects implementiert.

FilterVO

Zur Übergabe der ausgewählten Filterkriterien auf dem Webinterface an das Java-Back-End werden die einzelnen Werte in dem FilterVO zusammengefasst.

package cgp.vo;
 
public class FilterVO 
{
	public String gender; 		// null, "M", "W"
	public String nationality; 	// null, "LandXY"
	public String state; 		// null, "Deutschland"
	public String county;		// null, "NDS"
	public String faculty;		// null, "Mathematik"
	public String superSubject;
	public String firstStudy;	// null, "Info"
	public String secondStudy;	// null, "Info"
	public String degree;		// null, Master
	public int[] age;		// null, [15, 100]
	public int semester;		// 19951
	public String hzbTown;		// HZB-Ort	
 
	public String optionType;	// "alumni", "students"
 
	/*
	 * attributes for students
	 */
	public int[] curSemester;	//Fachsemester [1, 30]
	public int startSemester;	//Erfassungssemester
 
	/*
	 * view
	 */
	public String view;		//barChart, pieChart, googleMaps, googleEarth
 
	/*
	 * attributes for alumni
	 */
	public int exitSemester; 	//Abgangssemester
	public int[] takenSemesters;	//benoetigte Semester [1, 30]
	public int[] finalGrade;	//AbschlussNote	[1, 5]
 
	/*
	 * group By
	 */
	public String groupBy;		//grade, state, nationality,...
 
	public boolean isEmpty(){...
}
TupleVO

In der Regel werden mehrere TupleVOs in einer Liste als Rückgabewert der Methoden im InitialService verwendet. Ein TupleVO ordnet einen Wert(value) einem Schlüssel(key) zu. Die Art von Wert und Schlüssel ist abhängig von der benutzten Methode (siehe Abschnitt InitialService).

package cgp.vo;
 
public class TupleVO 
{
	public String primaryKey;
	public String value;
 
	public TupleVO(){}
}
QuadrupleVO

Das QuadrupelVO wird im Query verwendet. Im Gegensatz zum TupleVO unterscheidet das QuadrupelVO zusätzlich zwischen der Anzahl männlicher und Anzahl weiblicher Studierender auf die sich das jeweilige Objekt bezieht.

package cgp.vo;
 
public class QuadrupleVO 
{	
	public String primaryKey;
	public int valueMale;
	public int valueFemale;
	public int valueTotal;
 
	public QuadrupleVO(){}
}
NameQuantityVO

Das NameQuantityVO findet Verwendung in Methoden des QueryServices, deren Ergebnisse geo-referenziert werden müssen. So wird z.B. zur Anzeige einer Stadt auf der Karte der Höhen- und Breitegrad angegeben.

package cgp.vo;
 
public class NameQuantityVO 
{
	public String id;
	public String name;
	public double latitude;
	public double longitude;
	public int quantity;
 
	public NameQuantityVO(){}
}

Services

InitialService

Der Initial Service dient zur Abfrage der Filterkriterien.

public interface IInitialService 
{
	public List<TupleVO> getNationalityList(FilterVO filterVO) 
			throws DAOException;
 
	public List<TupleVO> getStatesList(FilterVO filterVO) 
			throws DAOException;
 
	public List<TupleVO> getCountyList(FilterVO filterVO) 
			throws DAOException;
 
	public List<TupleVO> getHZBTownList(FilterVO filterVO) 
			throws DAOException;
 
	public List<TupleVO> getFacultyList(FilterVO filterVO) 
			throws DAOException;
 
	public List<TupleVO> getSuperSubjectList(FilterVO filterVO) 
			throws DAOException;
 
	public List<TupleVO> getDegreeList(FilterVO filterVO) 
			throws DAOException;
 
	public List<TupleVO> getFirstStudyList(FilterVO filterVO) 
			throws DAOException;
 
	public List<TupleVO> getSecondStudyList(FilterVO filterVO) 
			throws DAOException;
 
	public List<TupleVO> getSemesterList()
			throws DAOException;
}
QueryService

Nachdem Filter auf der Web-Oberfläche ausgewählt wurden, werden die Ergebnisse über den QueryService abgefragt.

public interface IQueryService 
{
	public List<QuadrupleVO> executeQuery(FilterVO filterVO) 
			throws DAOException;
 
	public List<NameQuantityVO> executeSpecialQuery(FilterVO filterVO) 
			throws DAOException;
 
	public List<String> getAbscissas(FilterVO filterVO)
			throws DAOException;
 
	public List<NameQuantityVO> getFromCache(FilterVO filterVO)
			throws DAOException; 
 
	public List<QuadrupleVO> getGoogleEarthData(FilterVO filterVO)
			throws DAOException;
 
	public List<TupleVO> getGoogleMapsTopThree(
			String hzbTown, 
			FilterVO filterVO)
			throws DAOException; 
}

Datenbank-Struktur

Um eine geeignete Datenhaltung für die Applikation zur Visualisierung von Immatrikulationsdaten zu finden, mussten die Daten in eine Form gebracht werden, die den Ansprüchen der verschiedenen Anwendungsteile gerecht wurde und gleichzeitig die Queryzeiten möglichst kurz gestaltet, um später dem Benutzer eine komfortablere Bedienung zu ermöglichen. In diesem Zusammenhang konkurrierte der Ansatz, eine effiziente Datenhaltung mit möglichst geringem Speicherverbrauch zu erstellen gegen eine redundante Speicherung mit je nach Anwendungsfall aggregierten Tabellen zu Gunsten der Live-Performance.

Als essentiell hat sich die Frage herausgestellt, ob bei einem Query nach der Anzahl an Studienfällen oder die der Studienköpfen gesucht war. Letztere ließ sich bei unveränderter Datenbank-Struktur nur in Zeiten jenseites einer Minute herausfinden, während die Suche nach Studienfällen auf einer eigends für diesen Fragetyp erstellte aggregierte Tabelle innerhalb von max. 3 Sekunden erfolgreich abgeschlossenn war. Die Frage nach Studienköpfen war vorallem bei Queries erwünscht, deren Ergebnis auf der Deutschland bzw. Weltkarte dargestellt werden sollten. Da die Berechnung der Studienköpfe je nach gewählten Filterkriterien viel Zeit in Anspruch nahm und auch die Anzahl aller Möglichkeiten, die Anzahl aller Permutationen, die vorhandenen Speicherresourcen weit überstieg, kam ein Ansatz zu Verwendung, der mittels einer Cache-Tabelle einmal berechnete Queries abspeicherte und somit beim erneuten Anfragen auf das bereits berechnete Ergebnis zurückgegegriffen werden kann. Diesem Ansatz liegt die Annahme zu Grunde, dass nach einem gewissen Zeitraum die Ergebnisse aller gängigen Anfragen bereits in der Cache-Tabelle liegen und nur selten neue Berechnungen erforderlich wären.



Update aus dem Rechenzentrum

Die Datenbank sollte außerdem durch automatische Updates aus dem Rechenzentrum jeweils einmal im Semester auf den aktuellen Stand gebracht werden können, ohne das händische Änderungen nachträglich erforderlich gewesen wären. Hierzu war es notwendig Skripte zu schreiben, die über das Java-Backend alle neuen Daten in das Live-System überführen sowie Transformation anstellen um die Datensätze der aggregierten Tabellen anzupassen. Die Automatismen sind dabei unterteilt in den Transfer der Daten von der Datenbank des Rechenzentrums in die Datenbank des Projektes, das Präparieren der Tabellen zur Erstellung weiterer Tabellen, die Korrektur der Datensätze und letztlich die Aggregation der Daten. Im weiteren steht eine ausführliche Beschreibung des Transfers der Daten, sowie eine Kurzbeschreibung der anderen Automatismen.

Transfer der Daten

Das eigentliche Updaten, also das Durchsuchen der Rechenzentrumsdatenbank nach neuen Datensätzen, gestaltet sich dabei einfacher als das nachträgliche Bearbeiten von bereits vorhandenen Datensätzen. So mussten für die Updatefunktionen die während des Praktikums erarbeiteten Queries zusammengesucht werden und in entsprechender Weise abgeändert werden, um sie den zwischenzeitlichen Änderungen der Datenbank anzugleichen. Änderungen der Struktur waren hier hauptsächlich Namensänderungen einzelner Felder sowie ganzer Tabellen.

Ein Update der Faktentabellen läuft im Prinzip in folgenden vier Schritten ab (hier am Beispiel der Funktion updateFKT_STUDIENGAENGE):

1. Verbindung zur eigenen Datenbank herstellen und aktuelle LOADNR abfragen

try{
	datenbank = ConnectionHelper.getConnection();
	Statement sdb = datenbank.createStatement();
	ResultSet rsdb = sdb.executeQuery("SELECT LD_LOADNR FROM DIM_LOADINFOS");
	if(rsdb.next())
		loadnr = rsdb.getInt("LD_LOADNR")+1;
}catch(Exception e){
	...
}

2. Verbindung zur Datenbank des Rechenzentrums herstellen und nach Datensätzen mit entsprechender LOADNR suchen

rsrz = srz.executeQuery("SELECT STG_SEMESTER, " +
			"STG_MATRIKELNR, " +
			"STG_ABSCHLUSS, " +
			"STG_FACH, " +
			"STG_STAATSANGH, " +
			"STG_HZBORT, " +
			"STG_ALTER, " +
			"STG_FACHSEM, " +
			"STG_LOADNR, " +
			"STG_STGNR, " +
			"STG_LE, " +
			"STG_FACHNR, " +
			"STG_GESCHLECHT " +
			"FROM FKT_STUDIENGAENGE " +
			"WHERE " +
			"STG_LOADNR='" + loadnr + "'");
if(!rsrz.next())
	throw new NoSuchFieldException();
if(rsrz != null)
	storeDatabaseUpdate(datenbank, rsrz, "FKT_STUDIENGAENGE");

3. Entsprechende Datensätze in eine temporäre Tabelle der eigenen Datenbank überführen

PreparedStatement sdb = datenbank.prepareStatement(
		"INSERT INTO FKT_STUDIENGAENGE_TMPEER " +
		"(STG_SEMESTER, " +
		"STG_MATRIKELNR, " +
		"STG_ABSCHLUSS, " +
		"STG_FACH, " +
		"STG_STAATSANGH, " +
		"STG_HZBORT, " +
		"STG_ALTER, " +
		"STG_FACHSEM, " +
		"STG_LOADNR, " +
		"STG_STGNR, " +
		"STG_LE, " +
		"STG_FACHNR, " +
		"STG_GESCHLECHT) " +
		"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
while(rs.next()){
	sdb.setString(1, rs.getString("STG_SEMESTER"));
	sdb.setString(2, rs.getString("STG_MATRIKELNR"));
	sdb.setString(3, rs.getString("STG_ABSCHLUSS"));
	sdb.setString(4, rs.getString("STG_FACH"));
	sdb.setString(5, rs.getString("STG_STAATSANGH"));
	sdb.setString(6, rs.getString("STG_HZBORT"));
	sdb.setString(7, rs.getString("STG_ALTER"));
	sdb.setString(8, rs.getString("STG_FACHSEM"));
	sdb.setString(9, rs.getString("STG_LOADNR"));
	sdb.setString(10, rs.getString("STG_STGNR"));
	sdb.setString(11, rs.getString("STG_LE"));
	sdb.setString(12, rs.getString("STG_FACHNR"));
	sdb.setString(13, rs.getString("STG_GESCHLECHT"));
	sdb.executeUpdate();
}

4. Überführung in Grundlagentabelle aus der später die Tabellen "Working" und "Aggregiert" erstellt werden

private boolean generateSTUDIENGAENGE_FAECHER(){
 
		int nr;
		time = System.nanoTime();
		boolean check = false;
		List<String> update = new ArrayList<String>();
 
		try{
			for(nr=1; nr<=3; nr++){
				datenbank = ConnectionHelper.getConnection();
				sdb = datenbank.createStatement();
 
				rsdb = sdb.executeQuery("SELECT LD_LOADNR FROM DIM_LOADINFOS");
				if(rsdb.next())
					loadnr = rsdb.getInt("LD_LOADNR") + 1;
 
				rsdb = sdb.executeQuery("SELECT " +
						"`STG_SEMESTER`, " +
						"`STG_MATRIKELNR`, " +
						"`STG_STGNR`, " +
						"`STG_FACH`," +
						"`STG_LOADNR`," +
						"`STG_LE`, " +
						"`STG_FACHSEM`"+
						"FROM `FKT_STUDIENGAENGE_TMPEER`, " +
						"WHERE " +
						"`STG_FACHNR`=" + nr);
 
				while(rsdb.next()) {
					update.add("UPDATE FKT_STUDIENGAENGE " +
						...
				}
				datenbank = ConnectionHelper.getConnection();
				for(String sql: update){
					datenbank.prepareStatement(sql).executeUpdate();	
				}
				ConnectionHelper.close(datenbank);
			}
			check = true;
		}catch (SQLException e){
			e.printStackTrace();
			throw new DAOException(e);
		}finally{
			ConnectionHelper.close(datenbank);
			ConnectionHelperRechenzentrum.close(rechenzentrum);
			System.out.println("duration of generateLAB_FAECHER: " + ((System.nanoTime()-time)/(0.6E11)) + " minutes");
			return check;
		}
	}

#Präparation

Präparation und Korrektur der Daten

Durch die verschiedenen Sichtweisen, welche die Applikation ermöglicht, wurden wie bereits erwähnt, verschiedene Strukturen der Tabellen nötig. Doch nicht nur die Struktur der Tabellen sonder durch verschiedene Ideen der einzelnen Gruppen wurde auch eine differenzierte Struktur innerhalb der Tabellen nötig. Das heißt, dass ein Update der Dimensionstabellen alleine oftmals nicht ausreicht und so eine Korrektur oder Erweiterung der Daten vorgenommen werden muss. Darunter fallen hauptsächlich die Erzeugung der Geokoordinaten für den Applikationsteil "GoogleMaps", eine vernünftige Formatierung der Noten der Studienabgänger, sowie eine Anpassung der Städtenamen zur korrekten Verlinkung auf entsprechende Wikipedia Artikel.

Das Erzeugen der Geokoordinaten erfolgt mittels einer Verbindung zum Googleserver, welcher auf eine Namensanfrage Längen- und Breitengrad der gefundenen Stadt zurrückliefert. Die Methode ist leider nicht Konsistent, was zur Folge hat, dass Städte manchmal ein Geotag bekommen und ein anderes mal nicht. Außerdem werden unterschiedliche Städte mit gleichem Namen bevorzugt im amerikanischen Raum platziert. Hierbei ist also eine einmalige Kontrolle der Applikation pro Semester notwendig.

Alle weiteren Korrekturen betreffen eher die Benutzerfreundlichkeit der Oberfläche, da hierbei Namensänderungen der internen Formatierung auf konventionell gebräuchliche Namen geändert werden.

Aggregation

Nachdem die Faktentabellen und die Dimensionstabellen auf dem aktuellsten Stand sind kann die Erzeugung weiterer Tabellen ablaufen. So werden, wie bereits beschrieben, auf Grund der sonst zu langsamen Query-Zeiten, verschiedene weitere Tabellen generiert. Zum einen die zu den Faktentabellen gehörenden Tabellen "Working", sowie "Aggregiert" und letztlich zur Erzeugung der Filter die Tabellen "Initial". Alle drei Tabellen können aus der in Schritt 4 erzeugten Tabelle gewonnen werden.

Die "Working"-Tabelle enthält dann alle relevanten Daten für eine allgemeine Abfrage ohne, dass dabei ein weiterer Join Befehl nötig wird. Die "Aggregiert"-Tabellen beinhalten zusätzlich noch Information über die Anzahl gelieferter Ergebnisse der Query, also Anzahl der Studienfälle. Dies wird genutzt um auch hier Geschwindigkeit der Queries zu erhöhen, welches vor allem für die Georefferenzierten Anfragen nach z.B. Studentenzahlen in Städten oder ähnlichem von Vorteil ist.

Das Update der "Cache"-Tabelle läuft bei jeder Query im Hintergrund automatisch ab. Jede Anfrage wird dabei in die Tabelle eingetragen und gespeichert, sofern noch nicht vorhanden. Mit jeder Aktualisierung der Daten jedoch wird diese Tabelle gelöscht, da sich nun die Grunddaten geändert haben und entsprechend gespeicherte Queries nicht mehr aktuelle Werte enthalten würden. Ebenfalls mit jeder Datenaktualisierung müssen auch die "Initial"-Tabellen neu generiert werden, welche die Filter der Oberfläche zur Verfügung stellen. Dies geschieht um eventuell neu hinzugekommene Werte der Dimensionstabellen zu berücksichtigen.


Page last modified on February 16, 2009, at 09:41 AM