Persistenz mit Spring Eberhard Wolff Saxonia Systems AG [email protected] Über mich ➜ [email protected] ➜ Chef Architekt Saxonia Systems ➜ Java Champion ➜ Fokus: Java EE, Spring ... ➜ Autor (z.B. Java Magazin, Bücher...) – Server Component Pattern (Wiley) – Java Persistenz Strategien (Software & Support) – Spring (dpunkt, Q1/2006) ➜ Blog: http://JandIandMe.blogspot.com/ 1 Entweder Java (vor allem EE) wird viel einfacher, oder wir haben ein Problem. Teile der Java Community Überblick ➜ Warum Spring? ➜ Dependency Injection ➜ JDBC mit Spring ➜ iBATIS ➜ Transaktionen 2 Warum Spring? ➜ Einfachheit ➜ Fokus auf POJOs (Plain Old Java Objects) ➜ Vereinfachte Benutzung verschiedener APIs – Hibernate, JDO, Top Link, iBATIS, JMS, JDBC, JTA, Java Mail, EJB, ... ➜ Dependency Injection – Objekte bekommen Ressourcen zugewiesen – Objekte sind unabhängig von Infrastruktur Warum Spring? ➜ Keine Zwänge – Man muss Spring nicht komplett nutzen – Viele Integrationsmöglichkeiten – Kann aber One Stop Shop sein ➜ Code weitgehend unabhängig von Spring ➜ Spring - Das neue Java EE? 3 Woher kommt das? ➜ Grundzüge in Rod Johnsons „Expert One-on- One J2EE Programming“ beschrieben ➜ Als „privates Framework“ in verschiedenen Projekten geboren ➜ Dann Open Source ➜ Aus der Praxis entwickelt und schon immer praktisch eingesetzt – So sollte man Frameworks entwickeln... – Anders als bei so manchem JCP / JSR ➜ Etliche Anwendungen in Praxis Dependency Injection 4 Das Beispiel ➜ Kunden, Bestellungen ➜ Alles, was ein Unternehmen braucht! ➜ DAO Pattern: Persistenz in Klasse kapseln Umsetzung der Klassen ➜ Direktes Erzeugen des DAOs ➜ Sehr einfach public class BestellungBusinessProcess { private KundeDAO kundeDAO = new KundeDAO(); … } 5 Testing ➜ Hm... public class BestellungBusinessProcessTest extends TestCase{ public void testBestellen() { Kunde testKunde = new Kunde(); testKunde.setKontostand(42.0); KundeDAO kundeDAO = …; kundeDAO.setTestKunde(testKunde); // Daten von testKunde setzen // und jetzt muss ich ihm dem Bestellungprozess // unterschieben } } OK, das bekomme ich hin... ➜ Refactoring: Factory einziehen public class BestellungBusinessProcess { private IKundeDAO kundeDAO = Factory.getKundeDAO(); … } public class BestellungBusinessProcessTest extends TestCase { public void testBestellen() { Kunde testKunde = new Kunde(); // Daten von testKunde setzen IKundeDAO testKundeDAO = …; testKundeDAO.setKunde(testKunde); Factory.setKundeDAO(testKundeDAO); } } 6 Vorteile ➜ Testdaten unterschieben geht ➜ Objekte können z.B. EJBs sein – Service Locator Pattern – Dann aber Änderungen am Code ➜ DAOs können Singletons werden – Typische Service-basierte Architekturen sind Singleton-lastig Probleme: Dieses Factories sind unangenehm ➜ Eine pro Schicht (DAO/BusinessProcess)? – Mehrmals dasselbe implementieren – Testdaten Unterschieben redundant implementiert ➜ Eine allgemeine? – Jeder hängt davon ab ➜ Typsicher: Zu groß 7 Datenzugriff ➜ Sollte konfigurierbar sein.. public class DAOBase { protected Connection getConnection() { Class.forName(…); // Treiber laden return DriverManager.getConnection(...); // Connection holen } } public class KundeDAO extends DAOBase { public Kunde save(Kunde kunde) { Connection con = getConnection(); … } } Datenzugriff Java EE Umgebungen ➜ Ein Vorschlag, geht sicher auch anders. public class DAOBase { protected Connection getConnection() { if (inJavaEE) { // Wie bekomme ich das raus? InitialContext ic=new InitialContext(); DataSource ds=(DataSource)ic.lookup(...); // hier fehlt Caching return ds.getConnection(); } else { Class.forName(…); return DriverManager.getConnection(...); } } } 8 Und jetzt noch mal mit Dependency Injection ➜ „Normale“ Java Klassen ➜ Referenzen durch set-Methoden zuweisbar ➜ Objekte sind passiv, kein aktiver Look-Up public class BestellungBusinessProcess { private IKundeDAO kundeDAO; public void setKundeDAO(IKundeDAO kundeDAO) { this.kundeDAO = kundeDAO; } public void setBestellungDAO( IBestellungDAO bestellungDAO) { this.bestellungDAO = bestellungDAO; } … } Spring Konfigurationsdatei ➜ DI Konfiguration in Spring Konfigurationsdatei <beans> <bean id="kundeDAO" class="dao.KundeDAOJDBC" singleton="true"> <property name="datasource" ref="datasource"/> </bean> <bean id="bestellungDAO" class="dao.BestellungDAOJDBC" singleton="true"> <property name="datasource" ref="datasource"/> </bean> <bean id="bestellung" class="businessprocess.BestellungBusinessProcess"> <property name="bestellungDAO" ref="bestellungDAO"/ <property name="kundeDAO" ref="kundeDAO"/> </bean> </beans> 9 DI löst das Umgebungsproblem ➜ Java EE: DataSource im JNDI <bean id="datasource" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiName" value="java:comp/env/jdbc/DB" /> </bean> ➜ Java SE: Eigene DataSource <bean id="datasource“ class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost/spring" /> <property name="username" value="spring" /> <property name="password" value="spring" /> </bean> Benutzung... ➜ Vorsicht! Nur Main Methode! (eventuell Tests) ➜ ...denn nur die „Top Level Objekte“ müssen so zugegriffen werden ➜ Abhängige Objekte sind ja konfiguriert ClassPathResource res = new ClassPathResource("beans.xml"); XmlBeanFactory beanFactory = new XmlBeanFactory(res); BestellungBusinessProcess bestellung = (BestellungBusinessProcess) beanFactory.getBean("bestellung"); bestellung.…; 10 DI: Vorteile ➜ Definiert, was Objekt-Referenzen sind ➜ Klassen automatisch unabhängig von Umgebung – Oft Abhängigkeiten zu JNDI – ...oder zum Service Locator ➜ Flexibilität (Java SE oder EE: schon gesehen) ➜ Testbarkeit (andere Konfiguration / Mocks) ➜ Einziger Unterschied von Singletons: Eine Instanz, keine „Man könnte sich ohrfeigen, globale Variable dass man es nicht immer so gemacht hat!“ Fazit Dependency Injection ➜ DI erleichtert die Strukturierung von Σ Anwendungen ➜ Sehr einfaches Prinzip ➜ Spring hat eine mächtige DI Implementierung ➜ Eingebaute Konfiguration ➜ Leichtere Testbarkeit – Statt echter Klassen Mocks zuweisen ➜ Flexibel: Ressourcen aus dem Application Server, Java SE, ... ➜ Viele weitere Möglichkeiten (z.B. Auto Wiring) 11 JDBC mit Spring Beispiel-Code: Wieviele Fehler? Connection con = null; ResultSet rs; Größtenteils schwer diagnostizierbar! try { con = …; Statement stmt = con.createStatement(); rs = stmt.executeQuery("…"); while (rs.next()) { … } rs.close() fehlt (?) stmt.close(); Wird nicht immer ausgeführt }catch(SQLException e) { System.out.println("SQL-Exception:"+e); Fehlerbehandlung? }finally { con.close(); NullPointerException? SQLException? } 12 Das kann man keinem Entwickler zumuten „Benutzen Sie JDBC?“ „Ja.“ „Sie haben ein Problem.“ Oder auch: „Wir haben Garbage Collection, aber was ist mit den Ressourcen?“ Hilfsklassen: JDBC ➜ Problem bei JDBC: Aufräumen der Ressourcen vor allem bei Exceptions ➜ SQLException – Gibt proprietäre Codes zurück – Ist eine checked Exception (keine RuntimeException), obwohl man selten eine sinnvollen Reaktionsmöglichkeiten hat 13 Lösung Allgemein: Template ➜ Auszuführenden Code einem Template übergeben ➜ Template führt Code aus – wandelt Exceptions in RuntimeExceptions um – Gemeinsame Exception Hierarchie für alle Datenbanken ➜ Templates sind in Spring für viele APIs implementiert – Hibernate, iBATIS, JDO, JMS... Lösung JDBC: JdbcTemplate ➜ Für Queries: queryForInt() ... für primitive Daten ➜ queryForList() für komplexe ResultSets: Gibt List von Maps mit Spaltenname als Schlüssel zurück JdbcTemplate insertTemplate = new JdbcTemplate( datasource); insertTemplate.update( "INSERT INTO KUNDE(VORNAME,NAME) VALUES(?,?)", new Object[] {vorname, name }); 14 Hilfsklassen: JDBC Komplexere Queries ➜ Man kann einer query() Methoden auch übergeben: – RowMapper: wandelt Zeilen in Objekte – ResultSetExtractor: gesamtes Ergebnis in Objekt wandeln ➜ Für den Extremfall gibt es Möglichkeiten auf Statement / PreparedStatement Ebene ➜ Oder die Entwicklung eigener Klassen für Queries ➜ Also: Ebenfalls Möglichkeiten für komplexe Ergebnisse Fazit JDBC Hilfsklasse ➜ Macht Benutzung von JDBC deutlich Σ einfacherer und sicherer ➜ Isoliert vom Rest von Spring nutzbar ➜ Beispiel für allgemeinen Template Mechanismus ➜ (Noch) Nicht gezeigt: – Integration iBATIS, Hibernate, JDO, Toplink – Ähnliche Prinzipien (Templates) – Wesentliche Vereinfachungen (z.B. Hibernate Session) 15 iBATIS iBATIS ➜ Eigenes Open Source Framework ➜ Idee: Einfache Abstraktion über JDBC ➜ Direkte Abbildung von Tabellen in Objekte 16 Abbildung: XML Datei <sqlMap namespace="Ware"> <resultMap id="result" class="businessobjects.Ware"> <result property="id" column="ID" columnIndex="1"/> <result property="bezeichnung" column="BEZEICHNUNG" columnIndex="2"/> <result property="preis" column="PREIS" columnIndex="3"/> </resultMap> <select id="getWareByID" resultMap="result" parameterClass="int"> SELECT W.ID as ID, BEZEICHNUNG, PREIS FROM WARE W WHERE W.ID=#id# </select> <insert id="saveWare" parameterClass="map"> INSERT INTO WARE(BEZEICHNUNG, PREIS) VALUES(#bezeichnung#,#preis#) </insert> </sqlMap> Spring Konfiguration <bean id="sqlMapClient" class="....SqlMapClientFactoryBean"> <property name="configLocation“ value="sqlmap-config.xml" /> <property name="dataSource" ref="datasource" /> </bean> <bean id="kundeDAO" class="ibatisdao.KundeDAO" > <property name="sqlMapClient" ref="sqlMapClient"/> </bean> ➜ Injektion der Konfiguration 17 Code im DAO public class WareDAO extends SqlMapClientDaoSupport implements IWareDAO { private static Map getParameterAsMap( String bezeichnung, double preis, int id) { Map result = new HashMap(); result.put("bezeichnung", bezeichnung); result.put("preis", new Double(preis)); result.put("id", new Integer(id)); return result; } public void save(Ware ware) { Map params = getParameterAsMap(ware.getBezeichnung(), ware.getPreis()); getSqlMapClientTemplate().insert("saveWare", params); } Code im DAO public Ware getByID(int id) { return (Ware) getSqlMapClientTemplate(). queryForObject("getWareByID", new Integer(id)); } } ➜ SqlMapClientTemplate wird von Spring- Basisklasse verwaltet 18 iBatis Zusammenfassung ➜ Dünne Abstraktionsschicht über JDBC ➜ Direktes Mapping Objekte <-> Tabellen Σ ➜ Nicht gezeigt: Caching, Lazy Loading usw. ➜ Flexibilität von JDBC ohne die Details ➜ Einige wichtige Features schon vorhanden Transaktionen 19 Ein paar APIs für Transkationen... ➜ JDBC (Connection) ➜ Persistenz-Frameworks (Hibernate, iBATIS, JDO..) ➜ JMS (Java Messaging Service) ➜ JCA (Java Connector Architecture) ➜ JTA (Java Transaction API) ➜ ...und alle haben ein anderes Interface ➜ Also: Adapter bauen, der alle Interfaces vereinheitlicht PlatformTransactionManager ➜ TransactionDefinition ist z.B. requires_new, required ... public interface PlatformTransactionManager { TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; void commit(TransactionStatus status) throws TransactionException; } void rollback(TransactionStatus status) throws TransactionException; 20 Benutzung des PlatformTransactionManagers ➜ Direkte Benutzung ➜ TransactionTemplate: Code im Template wird in einem Transaktions-Kontext ausgeführt ➜ ...oder deklarativ (mit Aspekten hinter den Kulissen) – Interceptor fängt Methodenaufrufe ab – ...und startet / beendet Transaktionen – Konfiguration über über TransactionAttributeSource Methoden in Spring Konfiguration definieren <bean id="transactionAttributeSource" class="….NameMatchTransactionAttributeSource"> <property name="nameMap"> <map> <entry key="getBy*" > <value>PROPAGATION_REQUIRED</value> </entry> <entry key="save" > <value>PROPAGATION_REQUIRED</value> </entry> … </map> </property> </bean> 21 Methoden annotieren <bean id="transactionAttributeSource" class="….AnnotationTransactionAttributeSource" /> @Transactional(propagation=Propagation.REQUIRED, readOnly=false, rollbackFor=RuntimeException.class, isolation=Isolation.SERIALIZABLE) public void bestellenKreditkarteTransactionAnnotation( final Einkaufswagen einkaufswagen, final int kreditkartenNummer) throws BestellungException { … } Transaktionen: Zusammenfassung Σ ➜ Einheitliche Schnittstelle ➜ TransactionTemplate für automatisches Handling ➜ Deklarativ über Methodennamen oder Annotationen ➜ Flexibles Handling von Exceptions 22 Weitere Spring Themen ➜ JMS / JMX / JCA / zeitgesteuerte Aufgaben ➜ Spring MVC Framework ➜ Integration anderer Web Frameworks (JSF, Struts, ...) ➜ Spring Web Flow ➜ Trails statt Ruby on Rails ➜ Acegi Security Framework ➜ Spring IDE ➜ BeanDoc ➜ Spring Rich Client Wie fängt man an? ➜ http://www.springframework.org ➜ Verschiedene Bücher – Bald auch meins... ➜ Java Magazin Artikelserie – http://www.saxsys.de/kontakt/download_art ikel_spring.asp ➜ Saxonia bietet Consulting und Training 23 Woran Sie sich erinnern sollten... ➜ Spring ist ein tolles Framework! ➜ iBATIS: Es gibt Leben zwischen Low-Level JDBC und High-Level O/R Mappern ➜ Wer JDBC ohne JdbcTemplate macht, ist selbst schuld. 24