Komponenten-basierte Entwicklung Teil 12: Persistenz mit JDBC Komponenten – WS 2014/15 – Teil 12/Persistenz 10.12.14 1 Literatur [12-1] Müller, Bernd; Wehr, Harald: Java Persistence API 2. Hanser, 2012. [12-2] Beeger, Robert et al.: Hibernate. Persistenz in Java-Systemen mit Hibernate 3. dpunkt, 2006 [12-3] http://www.tutorialspoint.com/jdbc [12-4] Ullenboom, Christian: Java 7 - Mehr als eine Insel. Galileo, 2012. Kapitel 16 [12-5] http://best-practice-softwareengineering.ifs.tuwien.ac.at/patterns/dao.html [12-6] http://www.theserverside.de/singleton-pattern-in-java/ [12-7] http://mvnrepository.com/artifact/mysql/mysql-connector-java/5.1.34 … artifact/org.postgresql/postgresql/9.3-1102-jdbc41 [12-8] http://sqlzoo.net/wiki/Main_Page Komponenten – WS 2014/15 – Teil 12/Persistenz 2 Übersicht • • • • Das Object-Relationale Mapping (ORM) JavaBeans CRUD Ein einfacher OR-Mapper Komponenten – WS 2014/15 – Teil 12/Persistenz 3 Das Object-Relational-Mapping • Object-Relational-Mapping = ORM = Verfahren, bei dem ObjektStrukturen in einer Objekt-orientierten Sprache auf die Tabellenstruktur einer relationale Datenbank abgebildet und damit speicher- und abrufbar werden • Persistenz = Fähigkeit von etwas über die Laufzeit des benutzenden Programms zu existieren • Persistenz wird durch Speichern auf externen Medien, z.B. Datenbanken oder Dateien realisiert. • Persistente Objekte (in Java) = Erzeugte Objekte, die auf externen Medien gespeichert sind und bei Bedarf in den RAM/Heap geladen werden • Siehe – http://de.wikipedia.org/wiki/Objektrelationale_Abbildung – http://de.wikipedia.org/wiki/Persistenz_(Informatik) Komponenten – WS 2014/15 – Teil 12/Persistenz 4 Das CRUD-Prinzip • CRUD = Abkürzung für – – – – Create Read bzw. Retrieve Update Delete • Zu jedem persistenten Objekt gehört eine Implementierung, die die vier CRUD-Operationen möglichst transparent für das Objekt realisiert. • Üblicherweise werden damit die Datenbank-Operationen zur Behandlung von Records bezeichnet. • Siehe – http://de.wikipedia.org/wiki/CRUD Komponenten – WS 2014/15 – Teil 12/Persistenz 5 JavaBeans Eine einzelne Klasse ist dann eine JavaBean, wenn • • • • nur private Attribute vorhanden sind, alle Zugriffe auf Attribute über Getter/Setter laufen, Vererbungen nicht durch Container-Bedingungen erzwungen sind und wenn die Klasse serialisierbar ist, d.h. das Interface java.io.Serializable realisiert. Referenzierungen werden entsprechend dem Geflecht weiter verfolgt, d.h. auch referenzierte Klassen müssen JavaBeans sein oder von der Serialisierung ausgeschlossen werden. Ein anderes Wort für JavaBean ist POJO (Plain Old Java Object). Komponenten – WS 2014/15 – Teil 12/Persistenz 6 Wahl der Datenbank Sie können benutzen: • MySQL in der Version ab 5.4 • Postgresql in der Version 9.3.5 Hier wird MySQL benutzt, da diese aufgrund der Lehrveranstaltung Webentwicklung bekannt ist. Komponenten – WS 2014/15 – Teil 12/Persistenz 7 MySQL-Installationen (unter Windows) I • MySQL selbst: Am besten ist es, wenn XAMPP installiert wird: http://sourceforge.net/projects/xampp/files/ • MySQL Workbench: http://dev.mysql.com/downloads/workbench/ • MySQL Connector/J http://dev.mysql.com/downloads/connector/j/ Zu allen drei Software-Paketen gibt es auch Versionen für Linux und für MacOS. Komponenten – WS 2014/15 – Teil 12/Persistenz 8 MySQL-Installationen (unter Windows) II Nach Installation des Konnektors gibt es im Installationsordner die benötigte jar-Datei Wenn Sie OHNE Maven arbeiten, dann benötigen Sie dieses jar-File im CLASSPATH Hier die Version von netbeans Komponenten – WS 2014/15 – Teil 12/Persistenz 9 MySQL-Installationen (unter Windows) III Wer mit Maven arbeiten will (Empfehlung): <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.34</version> </dependency> Dann wird der Konnektor in der angegebenen Version automatisch herunter geladen und die manuelle Installation mit dem MySQL/JPaket entfällt. Komponenten – WS 2014/15 – Teil 12/Persistenz 10 MySQL-Installationen (unter Windows) IV Server lokal starten Dann über phpMyAdmin die den User root bzw. einen anderen ohne PW einrichten Komponenten – WS 2014/15 – Teil 12/Persistenz Das ist zwar unsicher, aber lokal in Ordnung.11 Ein Beispiel für ein OR-Mapper • Es wird ein einfacher OR-Mapper vorgestellt, der ca. 500 Zeilen bei 23 Klassen groß ist. • Daher können nicht alle Programmzeilen hier dargestellt werden. Deshalb sollte die Implementierung von der Site des Dozenten herunter geladen werden. • Der Mapper erlaubt – – – – alle Formen einer Klasse, also auch ohne Getter/Setter, beliebige Geflechte, auch zirkular, keine Vererbung, ohne ID-Attribute auszukommen. Komponenten – WS 2014/15 – Teil 12/Persistenz 12 Ein Beispiel – Event Manager I Location Appointment Event Description Dies führt zu folgendem Geflecht von Objekten: Location Appointment Event Komponenten – WS 2014/15 – Teil 12/Persistenz Description 13 Ein Beispiel – Event Manager II public class Appointment implements java.io.Serializable { @Attribute private Date date; @Attribute private boolean existFlyer; @Attribute private Timestamp created; @Attribute transient private Location location; @Attribute transient private Event event; … Getter/Setter … } Kennzeichnung der zu speichernden Attribute Ausschluss von Serialisierung Serialisierung (Marker-Interface) Damit alle (bis auf byte) primitiven Datentypen auftauchen, wurden einige Erweiterungen durchgeführt, die vielleicht nicht immer plausibel sind; aber es geht hier primär um die Demonstration, wie es geht. Komponenten – WS 2014/15 – Teil 12/Persistenz 14 Ein Beispiel – Event Manager III public class Event implements java.io.Serializable { @Attribute private EventType mode; @Attribute transient private Description description; @Attribute private int maxPersons; … Getter/Setter … } public class Description implements java.io.Serializable { @Attribute private String text; … Getter/Setter … } Hinweis: Ohne transient wird beim Serialisieren das ganze Geflecht traversiert, und nicht jeweils jede Klasse einzeln. Deshalb muss bei jeder Referenz auf eine andere (Nicht-Standard-)Klasse die Referenz mit transient modifiziert werden. Komponenten – WS 2014/15 – Teil 12/Persistenz 15 Ein Beispiel – Event Manager IV public class Location implements java.io.Serializable { @Attribute private String street; @Attribute private String houseNumber; @Attribute("null") private char houseNumberExtension; @Attribute private String city; @Attribute private String postalCode; @Attribute private float area; … Getter/Setter … } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Attribute { String value() default "notnull"; } Komponenten – WS 2014/15 – Teil 12/Persistenz Kennzeichnung, ob Null-Werte Zulässig sind 16 Implementierungsentscheidungen I • Es werden JavaBeans mit/ohne Getter/Setter unterstützt. • Die Klassen müssen serialisierbar sein. • Alle zu "persistierenden" Attribute werden durch die Annotation @Attribute gekennzeichnet, alle anderen Attribute werden ignoriert. • Es spielt keine Rolle, ob diese Attribute private, protected oder public sind. • Jede Klasse wird in einer eigenen Tabelle abgespeichert, die genauso heißt wie die Klasse (Zeichensätze!). • Es wird keine Vererbung berücksichtigt. • Es werden keine inneren Klassen unterstützt. • Die betroffenen Klassen brauchen keine die Persistenz unterstützenden Attribute, wie z.B. id, enthalten. • Die Struktur des Geflechts darf während der Residenz im RAM nicht verändert werden. Spring erzeugt die Objekt-Geflechte. Komponenten – WS 2014/15 – Teil 12/Persistenz 17 Implementierungsentscheidungen II Bereich der Objekte Location Appointment Appointment Location Event Description Event Description Bereich der Tabellen Komponenten – WS 2014/15 – Teil 12/Persistenz 18 Implementierungsentscheidungen III Object-Referenzen werden durch Schlüssel ausgedrückt. CREATE TABLE IF NOT EXISTS Appointment ( id INTEGER not NULL, `date` DATETIME NOT NULL, Referenzen sind `existFlyer` BIT NOT NULL, Integer-Fremdschlüssel `created` TIMESTAMP NOT NULL, `location` INT NOT NULL, `event` INT NOT NULL, PRIMARY KEY (id) ) CREATE TABLE IF NOT EXISTS Location ( id INTEGER not NULL, `street` VARCHAR(255) NOT NULL, `houseNumber` VARCHAR(255) NOT NULL, `houseNumberExtension` CHAR(1), Schlüssel sind hier `city` VARCHAR(255) NOT NULL, immer Integer (32 bit) `postalCode` VARCHAR(255) NOT NULL, `area` REAL NOT NULL, PRIMARY KEY (id) ) Komponenten – WS 2014/15 – Teil 12/Persistenz 19 Implementierungsentscheidungen IV Und sieht es in MySQL-Workbench aus: Keine Beziehungen auf der Ebene der Datenbank, d.h. das Editieren kann in beliebiger Reihenfolge ablaufen. Komponenten – WS 2014/15 – Teil 12/Persistenz 20 Ein einfaches ORM I - Übersicht BeanManager Spring-DI TableCreation BeanWriter BeanReader HashCode BeanRemover TableInsert TableUpdate Session BeanAdminList JDBC BeanAdminElem ConfigDB ConvertTypes ClassManager ClassDependency Komponenten – WS 2014/15 – Teil 12/Persistenz 21 Klasse ConfigDB I – ein Singleton Diese Klasse dient dazu, einmalig – global – die Datenbankparameter bereit zu halten: public class ConfigDB { private static ConfigDB instance = null; private private private private private private static static static static static static Parameter der String user= "root"; String password= ""; Datenbank String DBName= null; final String driver= "com.mysql.jdbc.Driver"; final String protocol= "mysql"; final ConvertTypes convert= new ConvertTypesMySQL(); private ConfigDB() {} public static ConfigDB getInstance() { if(instance== null) { instance= new ConfigDB(); } return instance; } Komponenten – WS 2014/15 – Teil 12/Persistenz SingletonMechanismus Erzeugen der Instanz 22 Klasse ConfigDB II – ein Singleton Dies ist ein verkürzter Teil zum Setzen der Parameter: public static void createInstance(String DBNamePar, String userPar, String passwordPar) { if(instance== null) { if(DBNamePar!=null) { DBName= DBNamePar; } Setzen der if(userPar!=null) { Parameter der user= userPar; } Datenbank if(passwordPar!=null) { password= passwordPar; } } } … Getter/Setter … Komponenten – WS 2014/15 – Teil 12/Persistenz 23 Klasse Session I public class Session { private Connection con= null; private final ConfigDB DB; private static boolean driverLoaded= false; private boolean autoConnect= false; Session() { DB= ConfigDB.getInstance(); if(!driverLoaded) { loadDriver(DB.getDriver()); } } private void loadDriver(String name) { try { Class.forName(name); driverLoaded= true; } catch (ClassNotFoundException ex) { throw new IllegalArgumentException(...); } } Komponenten – WS 2014/15 – Teil 12/Persistenz Laden des Treibers 24 Klasse Session II private void connect2DB(boolean creating) { String DSN= "jdbc:"+DB.getProtocol()+"://localhost"; if((DB.getDBName()!=null)&&!creating) { DSN= "jdbc:"+DB.getProtocol()+"://localhost/"+DB.getDBName(); } try { con= DriverManager.getConnection(DSN,DB.getUser(), DB.getPassword()); } catch (SQLException ex) { throw new RuntimeException(...); Verbindung zur } Datenbank processWarnings(); herstellen } Der Parameter creating dient zum Anzeigen, dass eine Datenbank erzeugt werden soll; dann darf der Datenbankname nicht in der DSN auftauchen, Ansonsten muss er es. Aus Gründen der Vereinfachung wird eine lokale Datenbank angenommen. Ansonsten muss statt "localhost" die Adresse des Datenbankserver stehen. Komponenten – WS 2014/15 – Teil 12/Persistenz 25 Klasse Session III - JDBC-Operationen public boolean execute(String SQL) { boolean problem= true; try { Statement stmt= con.createStatement(); problem= stmt.execute(SQL); } catch (SQLException ex) { throw new RuntimeException(....); } processWarnings(); return problem; } Nach diesem Schema sind alle JDBCAufrufe strukturiert. public int update(String SQL) { int result= 0; try { Statement stmt= con.createStatement(); result= stmt.executeUpdate(SQL); } catch (SQLException ex) { … … … } Komponenten – WS 2014/15 – Teil 12/Persistenz 26 Klasse Session IV – JDBC-Query public ResultSet query(String SQL) { ResultSet result; try { Statement stmt= con.createStatement( ResultSet.TYPE_FORWARD_ONLY,ResultSet.CONCUR_READ_ONLY); result= stmt.executeQuery(SQL); } catch (SQLException ex) { … … … } Das ist die Query-Routine, bei der die Implementierungsentscheidung getroffen wurde, dass das Result-Set nur von vorn nach hinten gelesen werden darf (einfachste Version). Komponenten – WS 2014/15 – Teil 12/Persistenz 27 Klasse Session V – Generieren des nächsten Schlüssels public int nextIndex(String tblName) { int maxValue= -1; String SQL="SELECT MAX(id) FROM "+tblName+";"; checkForOpen(false); ResultSet result= query(SQL); Es wird ein der bisher höchste try { Index ausgelesen, dieser um 1 if(result.next()) { maxValue= result.getInt(1); erhöht und dann zurück geliefert. } result.close(); } catch (SQLException ex) { throw new RuntimeException(...); } checkForClose(); return maxValue+1; } Das ist eine wichtige Methode, die tief in die Datenbank-Implementierung geht. Es gibt auch andere Strategien zur Generierung des nächsten Indexes. Komponenten – WS 2014/15 – Teil 12/Persistenz 28 Klasse Session VI public void transaction() { try { con.setAutoCommit(false); } catch (SQLException ex) { throw new RuntimeException(...); } } public void commit() { try { con.commit(); con.setAutoCommit(true); } catch (SQLException ex) { throw new RuntimeException(...); } } public void rollback() {… analog …} Ausschalten des Autocommit Einschalten des Autocommit AutoCommit bedeutet, dass direkt nach jedem SQL-Statement ein Commit durchgeführt wird – ist eher MySQL-spezifisch. Komponenten – WS 2014/15 – Teil 12/Persistenz 29 Zusammenfassung getInstance open() ConfigDB close() CreateInstance(...) execute(String) update(String) Session query(String) nextIndex(String) transaction() commit() rollback() Komponenten – WS 2014/15 – Teil 12/Persistenz 30 Ein einfaches ORM II - Übersicht BeanManager Spring-DI TableCreation BeanWriter BeanReader HashCode BeanRemover TableInsert TableUpdate Session BeanAdminList ConfigDB JDBC ConvertTypes BeanAdminElem ClassManager ClassDependency Komponenten – WS 2014/15 – Teil 12/Persistenz 31 BeanAdminList I Location Appointment Event Description BeanAdmin List BeanAdmin Elem BeanAdmin Elem BeanAdmin Elem BeanAdmin Elem • In den Admin-Elementen werden ergänzende Informationen zu den Knoten gehalten. • Die Reihenfolge wird durch ein Pre-Order-Traversieren bestimmt. Komponenten – WS 2014/15 – Teil 12/Persistenz 32 BeanAdminList II public class BeanAdminElem { public Object affectedObj; public int identifier= -1; public byte[] hash; Zeiger auf das Objekt BeanAdminElem(Object affectedObj) { this.affectedObj= affectedObj; actualizeHash(); } public boolean isModified() { byte[] now= HashCode.get(affectedObj); return !Arrays.equals(hash, now); } public final void actualizeHash() { hash= HashCode.get(affectedObj); } DatenbankIndex des Objekts Hash-Code des Objektes Überprüfen, ob der Hash-Code sich geändert hat } Das ist die Klasse der Elemente der Liste. Es wird angenommen, dass gültige Tabellen-Indices positiv sind. Komponenten – WS 2014/15 – Teil 12/Persistenz 33 BeanAdminList III Object • Der Hash-Code ist eine eindeutige "Summe", die einen bestimmten Inhalt charakterisiert. • Anderer Inhalt führt zu einem anderen Wert. • Siehe: – http://de.wikipedia.org/wiki/Hashfunkti on – http://de.wikipedia.org/wiki/MessageDigest_Algorithm_5 affectedObj identifier hash BeanAdminElem DB-Tabelle Der Grund für den Hashwert liegt darin, dass überflüssige Schreiboperationen auf der Datenbank vermieden werden sollen. Komponenten – WS 2014/15 – Teil 12/Persistenz 34 BeanAdminList IV public class BeanAdminList implements Iterator { private final ArrayList<BeanAdminElem> beanList= new ArrayList<>(); private boolean createMode= false; private int listIndex= -1; private int listSize= -1; Diese Werte werden für den Iterator benötigt public void create(Object obj, int identifier) { BeanAdminElem elem= new BeanAdminElem(obj); elem.identifier= identifier; beanList.add(elem); traverseBean(obj); } Komponenten – WS 2014/15 – Teil 12/Persistenz Erzeugen einer neuen Liste 35 BeanAdminList V private void traverseBean(Object obj) { Prüft, ob der for(Field attr : obj.getClass().getDeclaredFields()) { Knoten schon if(ClassManager.isComplexClass(attr.getType())) { try { einmal besucht attr.setAccessible(true); wurde Object objNext= attr.get(obj); if((objNext!=null)&&searchFor(objNext)==null) { Reflexion beanList.add(new BeanAdminElem(objNext)); Zugriff auf traverseBean(objNext); Variable Fügt in } Liste ein } catch (IllegalAccessException ex) { … … … } Es werden die Deklarationen nach Verweisen auf Objekte rekursiv traversiert, sofern der Knoten noch nicht besucht wurde. Alle erzeugten Knoten werden in eine ArrayList eingefügt. Das Pre-Order-Verfahren sollte hier deutlich sein. Komponenten – WS 2014/15 – Teil 12/Persistenz 36 BeanAdminList VI – selbst geschriebener Iterator @Override public boolean hasNext() { Abfrage return (listIndex<listSize); } @Override public BeanAdminElem next() { if(listIndex>=listSize) { Liefert das throw new NoSuchElementException(); nächste Element } return beanList.get(listIndex++); } public void resetIterator() { Lässt den listIndex= 0; Iterator neu listSize= beanList.size(); beginnen } Komponenten – WS 2014/15 – Teil 12/Persistenz 37 Zusammenfassung isModified() release() searchFor(Object) Bean Admin List actualizeHash() Bean Admin Elem create(Object,int) hasNext() next() resetIterator() getCreateMode() setCreateMode(boolean) Komponenten – WS 2014/15 – Teil 12/Persistenz 38 HashCode I Serialisieren public static byte[] get(Object obj) { Hash-Funktion byte[] digest; erzeugen try { if(md5==null) { md5= MessageDigest.getInstance("MD5"); } ByteArrayOutputStream bout= new ByteArrayOutputStream(); ObjectOutputStream stream= new ObjectOutputStream(bout); stream.writeObject(obj); stream.flush(); digest = md5.digest(bout.toByteArray()); } catch (IOException | NoSuchAlgorithmException ex) { throw new RuntimeException("Cant generate hashcode:"+ex); Hash-Funktion } anwenden return digest; } Es wird die kryptographische Hashfunktion MD5 benutzt. Komponenten – WS 2014/15 – Teil 12/Persistenz 39 HashCode II • Von der Benutzung von hashCode() wird abgeraten, da der Algorithmus zu viele Kollisionen hat. • Kollision = zwei oder mehrere unterschiedliche Originalwerte ergeben denselben Hashwert • MD5 erzeugt einen 128-bit-Hash. • Für kryptographische Zwecke ist heute MD5 nicht zu empfehlen, aber wir wollen hier lediglich die Gleichheit bzw. Modifikation prüfen. Komponenten – WS 2014/15 – Teil 12/Persistenz 40 Ein einfaches ORM III - Übersicht BeanManager Spring-DI TableCreation BeanWriter BeanReader HashCode BeanRemover TableInsert TableUpdate Session BeanAdminList ConfigDB JDBC ConvertTypes BeanAdminElem ClassManager ClassDependency Komponenten – WS 2014/15 – Teil 12/Persistenz 41 BeanWriter I public class BeanWriter { private final Session dbm= new Session(); public void write(BeanAdminList bal) { dbm.open(); dbm.transaction(); if(bal.getCreateMode()) { insert(bal); bal.setCreateMode(false); Transaktion } else { update(bal); } dbm.commit(); dbm.close(); } In Abhängigkeit ob das Objekt erzeugt wird (CreateMode) oder nicht, sind SQL Inserts oder Updates erforderlich. Komponenten – WS 2014/15 – Teil 12/Persistenz 42 BeanWriter II - Insert() private void insert(BeanAdminList bal) { Erzeugen des setIndeces(bal); nächsten Index bal.resetIterator(); while(bal.hasNext()) { BeanAdminElem elem= bal.next(); TableInsert ti= new TableInsert(bal,elem.affectedObj,elem.identifier); String SQL= ti.generateSQL(); Hilfsklasse zum elem.actualizeHash(); Erzeugen des dbm.update(SQL); Setzen des SQL-Statements } aktuellen } Hash-Wertes Hier wird das Iterator-Interface von BeanAdminList benutzt. Update sieht genauso aus, außer, dass eine andere Hilfsklasse benutzt wird. Komponenten – WS 2014/15 – Teil 12/Persistenz 43 BeanWriter III - TableInsert() public class TableInsert extends TableCommon { private final BeanAdminList bal; TableInsert(BeanAdminList bal, Object obj, int identifier) { … … startPart= "INSERT INTO "+clazz.getSimpleName()+" ("; endPart= ") VALUES ("; firstTime= true; } public void add(String key, String value) { if(firstTime) { firstTime= false; } else { startPart+= ","; endPart+= ","; } startPart+= ClassManager.encloseName(key,'`'); endPart+= value; } Komponenten – WS 2014/15 – Teil 12/Persistenz 44 BeanWriter IV - TableInsert() public String generateSQL() { ArrayList<Field> fields= getAnnotatedFields(); add(indexName,Integer.toString(identifier)); for(Field elem: fields) { Attribute anno= elem.getAnnotation(Attribute.class); Object objVal= getValue(elem); AttributeType atype= ....determineAttributeType(elem.getType()); if(objVal==null) { … … add(elem.getName(),convert.attribute2SQL(atype, null)); } else { if(atype==AttributeType.typClass){ BeanAdminElem ref= bal.searchFor(objVal); … … add(elem.getName(),convert.attribute2SQL(...)); } else { add(elem.getName(),convert.attribute2SQL(atype, objVal)); }}} return startPart+endPart+");"; } Komponenten – WS 2014/15 – Teil 12/Persistenz 45 Bemerkungen • Mit add() werden die einzelnen Elemente in den SQL-Ausdruck gebracht. • DetermineAttributeType() liefert in einem Aufzählungstyp den Typ des betreffenden Elements. public enum AttributeType { typString, typFloat, typDouble, typInt, typBoolean, typLong, typChar, typDate, typTimestamp, typEnum, typArray, typClass, typUnknown } • Mit attribute2SQL() wird ein Wert auf die Ebene von SQL gebracht (Typen in SQL sind nicht dieselben wie in Java). • Diese Konvertierung wird in einer Hilfsklasse versteckt. Komponenten – WS 2014/15 – Teil 12/Persistenz 46 BeanWriter V - ConvertTypesMySQL() class ConvertTypesMySQL implements ConvertTypes { @Override public String attributeType2SQL(Class clazz) { AttributeType atype; switch(atype= ClassManager.determineAttributeType(clazz)) { case typString: return "VARCHAR(255)"; case typFloat: return "REAL"; case typDouble: return "FLOAT"; case typInt: return "INT"; case typBoolean: return "BIT"; case typLong: return "BIG INT"; case typChar: return "CHAR(1)"; case typDate: return "DATETIME"; case typTimestamp: return "TIMESTAMP"; case typEnum: return "VARCHAR(255)"; Dies ist die Abbildung case typArray: throw new ... der Java-Datentypen case typClass: return "INT"; auf SQL-Datentypen case typUnknown: throw new ... für die TabellenDefault: throw new ... generierung. } } Komponenten – WS 2014/15 – Teil 12/Persistenz 47 BeanWriter VI - ConvertTypesMySQL() String attribute2SQL(AttributeType atype, Object obj) { ... switch(atype) { case typString: return ClassManager.encloseName((String) obj,'\''); case typFloat: return Float.toString((float) obj); case typDouble: return Double.toString((double) obj); case typInt: return Integer.toString((int) obj); case typBoolean: return ((boolean) obj)? "1" : "0"; case typLong: return Long.toString((long) obj); case typChar: ... Dies ist die Abbildung der Java-Datentypen } auf SQL-Datentypen } für die Werte. Komponenten – WS 2014/15 – Teil 12/Persistenz 48 Ein einfaches ORM IV - Übersicht BeanManager Spring-DI TableCreation BeanWriter BeanReader HashCode BeanRemover TableInsert TableUpdate Session BeanAdminList ConfigDB JDBC ConvertTypes BeanAdminElem ClassManager ClassDependency Komponenten – WS 2014/15 – Teil 12/Persistenz 49 BeanManager I public class BeanManager { private ApplicationContext appContext= null; private final String SpringFile; private final Class beanClass; private boolean tablesExist= false; private final Map<Object,BeanAdminList> myObjects= new HashMap<>(); public Object createBean(int identifier, String springName) { initContext(); Spring tablesExistence(); Object obj= appContext.getBean(springName); CreateMode BeanAdminList bal= new BeanAdminList(true); bal.create(obj,identifier); myObjects.put(obj, bal); Ab in die HashMap return beanClass.cast(obj); } Komponenten – WS 2014/15 – Teil 12/Persistenz 50 BeanManager II public void writeBean(Object obj) { tablesExistence(); BeanAdminList bal= myObjects.get(obj); if(bal==null) { throw new RuntimeException(...); } BeanWriter bw= new BeanWriter(); bw.write(bal); } Erzeuge Tabellen falls erforderlich BeanWriter public void detachBean(Object obj) { writeBean(obj); BeanWriter BeanAdminList bal= myObjects.get(obj); myObjects.remove(obj); bal.release(); Alles wegwerfen } Komponenten – WS 2014/15 – Teil 12/Persistenz 51 Bemerkungen • Attached = Bean ist persistent und im Speicher • Detached = Bean ist nur in der Datenbank • Beans werden durch einen Integerwert, der gleichzeitig Schlüssel in der ersten Tabelle ist referenziert. Komponenten – WS 2014/15 – Teil 12/Persistenz 52 BeanManager III private void tablesExistence() { if(!tablesExist) { Session dbm= new Session(); if(!dbm.existTable(beanClass.getSimpleName())) { dbm.open(); preparePersistence(dbm); Abfrage, ob schon dbm.close(); Tabellen existieren } tablesExist= true; } } Rekursives Traversieren der private void preparePersistence(Session dbm) { Class-Definitionen ClassDependency cd= new ClassDependency(); Class[] clist= cd.listConnectedClasses(this.beanClass); for(Class elem: clist) { TableCreation tbl= new TableCreation(elem); dbm.update(tbl.generateSQL()); } } Komponenten – WS 2014/15 – Teil 12/Persistenz 53 Hilfsklasse: ClassDependency() private ArrayList<Class> clazzList; private void traverseClassDefinitions(Class clazz) { clazzList.add(clazz); for(Field attr : clazz.getDeclaredFields()) { Class nowClazz= attr.getType(); if((attr.getAnnotation(Attribute.class)!=null)&& ClassManager.isComplexClass(nowClazz)) { if(!clazzList.contains(nowClazz)) { traverseClassDefinitions(nowClazz); } } Rekursives } } Traversieren der Class-Definitionen public Class[] listConnectedClasses(Class clazz) { clazzList= new ArrayList<>(); traverseClassDefinitions(clazz); return clazzList.toArray(new Class[clazzList.size()]); } ListConnectedClasses() wird von preparePersistence() aufgerufen Komponenten – WS 2014/15 – Teil 12/Persistenz 54 Das Hauptprogramm... I App() { System.out.println("Liste der Feten 2012"); ConfigDB.createInstance("EventDB", "root", ""); destroyDatabase(); createDatabase(); BeanManager ev= new BeanManager(Appointment.class, "Feten.xml"); Appointment ap1= (Appointment)ev.createBean(1, "IDApps"); setBeanValues(0, ap1); printAppointments(new Appointment[] {ap1}); ev.detachBean(ap1); ap1= (Appointment)ev.attachBean(1, "IDApps"); In setBeanValues werden die Werte der Objekte gesetzt. Komponenten – WS 2014/15 – Teil 12/Persistenz 55 Das Hauptprogramm... II ap1.setMaxPersons(100); //event printAppointments(new Appointment[] {ap1}); ev.writeBean(ap1); ap1.setExistFlyer(false); ap1.setArea(0.2f); ev.writeBean(ap1); ap1.setHouseNumberExtension(' '); ap1.setStreet(""); ap1.setDescription("Square Dance ab 7"); ev.writeBean(ap1); ev.detachBean(ap1); ap1= (Appointment)ev.attachBean(1, "IDApps"); printAppointments(new Appointment[] {ap1}); Komponenten – WS 2014/15 – Teil 12/Persistenz 56 Das Ganze noch einmal BeanManager Spring-DI TableCreation BeanWriter BeanReader HashCode BeanRemover TableInsert TableUpdate Session BeanAdminList ConfigDB JDBC ConvertTypes BeanAdminElem ClassManager ClassDependency Komponenten – WS 2014/15 – Teil 12/Persistenz 57 Nach dieser Anstrengung etwas Entspannung... Komponenten – WS 2014/15 – Teil 12/Persistenz 58