Archiv für Kategorie Java

Manche Dinge blieben besser unentdeckt

Softwareprojekte haben die unangenehme Eigenschaft bei längerer Lagerzeit in der Versionsverwaltung zu degenerieren. Dieses Phänomen liegt allerdings eher daran, dass man sich als Programmierer weiterentwickelt und dazulernt, während der Code gleich (schlecht) bleibt.

Manchmal gibt es aber auch Codeteile, die von vorneherein sehr merkwürdig sind:

JAVA:
  1. final Date heute = new SimpleDateFormat("HH:mm dd.MM.yyyy").parse(new
  2. SimpleDateFormat("HH:mm dd.MM.yyyy").format(new Date()));

Es ginge auch etwas einfacher:

JAVA:
  1. final Date heute = new Date();

Solche Dinge will man dann doch lieber nie wieder sehen...

, ,

1 Kommentar

Realistische Betrachung: Administratoren vs. Entwickler

Im ONJava.com-Blog von O'Reilly wurde heute ein interessanter Artikel Timothy O'Brien veröffentlicht, der sich mit den häufig auftretenden Konflikten zwischen Entwicklern und Administratoren beschäftigt. Er nennt sich Java's "Operations" Problems und bietet eine sehr interessante Betrachtungsweise eines typischen Konflikts, den vermutlich jeder Entwickler in größeren Unternehmen schon einmal erlebt hat: der Entwickler möchte etwas, der Administrator möchte genau das nicht oder auch umgekehrt. Dies führt meist zu größeren Konflikten, die mehr einem Glaubenskrieg ähneln als einer sachlichen Diskussion.

Der Artikel handelt in diesem Fall speziell von Java, dass durch seine Abstraktion von den eigentlichen Systemgrundlagen eine zusätzliche Hürde mit einbringt, die vorgestellten Thesen und Lösungsansätze sind allerdings allgemeingültig.
O'Brien sieht den Grund für die Konflikte in einer zu starren Abgrenzung zwischen den beiden Personengruppen, deren Tätigkeit thematisch überlappt, deren Zuständigkeiten aber hart getrennt sind. Er skizziert eine ideale Verfahrensweise und und stellt einen zweistufigen Plan vor, wie man die Reibung zwischen Administration und Entwicklung verringert.

The Short-term Solution: Hold Hands and Sing a Song

If your organization has friction between Operations and Development, a quick short-term fix is to nominate one person from each team to serve as a liaison to the other group.

Diesen Schritt haben wir in unserer Firma bereits vollzogen und es hat sich als sehr gut heraus gestellt, eine direkte Schnittstelle zur anderen Abteilung zu haben. Probleme aber auch Anforderungen sind so viel einfacher zu klären.

The Long-term Solution: Stop Developing Applications, Stop Administering Systems

â?¦do away with System Administrators and Application Developers. No, really, I mean that.

Der Ausschnitt selber klingt recht drastisch, er beschreibt aber eine thematische Annäherung von Administratoren und Entwicklern und den Wandel der klassischen Rollen im Verlauf der Zeit und im Zuge der technischen Entwicklung.

Insgesamt ein sehr lesenswerter Artikel, den ich jedem Administrator und jedem Entwickler nur empfehlen kann.

Keine Kommentare

JPA: Bidirektionale OneToMany/ManyToOne Beziehungen

Dieser Beitrag soll in erster Linie für mich als Gedächtnisstütze dienen. Als Nachwirkung der Schulung beschäftige ich mich nicht nur mit Enterprise Java Beans (EJB) sondern auch mit der Java Persistence API (JPA), dem "neuen" Persistenzstandard, der im gleichen Prozess wie EJB entworfen wurde. Im Entwicklerforum von java.net findet sich ein passender Satz zu dem Thema, den jemand als Antwort bekam, der sich darüber beklagt, dass JPA so unvorhersehbar arbeiten würde:

I doubt that it's a matter of it being "unpredictable" so much as it is getting over the learning curve.

Genau in dieser Lernkurve stecke ich auch derzeit noch und erlerne JPA gerade im Eigenstudium. Für erfahrene Entwickler ist dieser Beitrag daher nicht gedacht.

Das Problem

Es existieren zwei Entitäten, Benutzer und Gruppe. Jeder Benutzer kann einer Gruppe angehören, folglich können zu einer Gruppe mehrere Benutzer gehören. Beide Entitäten sind wie folgt definiert:

JAVA:
  1. public class Benutzer
  2. {
  3.     private String _name;
  4.     private String _vorname;
  5.     private String _benutzername;
  6.     private String _email;
  7.     private int _id;
  8.     private Gruppe _gruppe;
  9.  
  10.     /*
  11.      * Es folgen die ganze Getter und Setter fuer die
  12.      * obigen Eigenschaften.
  13.      * [...]
  14.      */
  15.    
  16.     @ManyToOne
  17.     public Gruppe getGruppe()
  18.     {
  19.         return _gruppe;
  20.     }
  21.  
  22.     public void setGruppe(final Gruppe gruppe)
  23.     {
  24.         _gruppe = gruppe;
  25.     }
  26. }

JAVA:
  1. public class Gruppe
  2. {
  3.     private int _id;
  4.     private String _name;
  5.     private String _beschreibung;
  6.     private Set<Benutzer> _benutzer = new HashSet<Benutzer>();
  7.            
  8.     /*
  9.      * Es folgen die ganze Getter und Setter fuer die
  10.      * obigen Eigenschaften.
  11.      * [...]
  12.      */            
  13.            
  14.     @OneToMany(mappedBy = "gruppe")
  15.     public Set<Benutzer> getBenutzer()
  16.     {
  17.         return _benutzer;
  18.     }
  19.  
  20.     public void setBenutzer(final Set<Benutzer> benutzer)
  21.     {
  22.         _benutzer = benutzer;
  23.     }
  24. }

Ein kleines Programm geht jetzt her und erzeugt drei Benutzer und zwei Gruppen. Zwei der Benutzer werden der ersten Gruppe zugewiesen, der dritte der anderen Gruppe.

JAVA:
  1. EntityManager em = createEntityManager();
  2.  
  3.         final Gruppe g1 = createGruppe("Demokraten", "Die Partei der Demokraten");
  4.         final Gruppe g2 = createGruppe("Republikaner", "Die Partei der Republikaner");
  5.  
  6.         em.persist(g1);
  7.         em.persist(g2);
  8.        
  9.         final Benutzer b1 = createBenutzer("Obama", "Barack", "bobama", "obama@senate.gov");
  10.         final Benutzer b2 = createBenutzer("McCain", "John", "jmccain", "mccain@senate.gov");
  11.         final Benutzer b3 = createBenutzer("Clinton", "Hillary", "hclinton", "clinton@senate.gov");
  12.  
  13.         b1.setGruppe(g1);
  14.         b2.setGruppe(g2);
  15.         b3.setGruppe(g1);
  16.  
  17.         em.persist(b1);
  18.         em.persist(b2);
  19.         em.persist(b3);

Danach geht das Programm her und lädt die Datensätze wieder aus der Datenbank und gibt sie und ihre Beziehungen aus.

JAVA:
  1. final List result = em.createQuery("SELECT b FROM Benutzer b ORDER BY b.name").getResultList();
  2.        
  3.         for (final Object b : result)
  4.         {
  5.             if (b instanceof Benutzer)
  6.             {
  7.                 final Benutzer benutzer = (Benutzer) b;
  8.                 LOG.info(benutzer);
  9.                 LOG.info("\t+--> " + benutzer.getGruppe());
  10.             }
  11.         }
  12.  
  13.         final List result2 = em.createQuery("SELECT g FROM Gruppe g").getResultList();
  14.        
  15.         for (final Object o : result2)
  16.         {
  17.             if (o instanceof Gruppe)
  18.             {
  19.                 final Gruppe g = (Gruppe) o;
  20.                 LOG.info(g);
  21.                 LOG.info("Benutzerzahl: " + g.getBenutzer().size());
  22.  
  23.                 for(final Benutzer b : g.getBenutzer())
  24.                 {
  25.                     LOG.info("\t+--> " + b);
  26.                 }
  27.             }
  28.         }

Es entsteht folgende Ausgabe:

CODE:
  1. JPAClient.showDemo()@107: Benutzer [2902]: Clinton, Hillary (hclinton, clinton@senate.gov)
  2. JPAClient.showDemo()@108:   +--> Gruppe [2800]: Demokraten (Die Partei der Demokraten)
  3. JPAClient.showDemo()@107: Benutzer [2901]: McCain, John (jmccain, mccain@senate.gov)
  4. JPAClient.showDemo()@108:   +--> Gruppe [2801]: Republikaner (Die Partei der Republikaner)
  5. JPAClient.showDemo()@107: Benutzer [2900]: Obama, Barack (bobama, obama@senate.gov)
  6. JPAClient.showDemo()@108:   +--> Gruppe [2800]: Demokraten (Die Partei der Demokraten)
  7. JPAClient.showDemo()@120: Gruppe [2800]: Demokraten (Die Partei der Demokraten)
  8. JPAClient.showDemo()@121: Benutzerzahl: 0
  9. JPAClient.showDemo()@120: Gruppe [2801]: Republikaner (Die Partei der Republikaner)
  10. JPAClient.showDemo()@121: Benutzerzahl: 0

Das Problem ist offensichtlich: Während jeder Benutzer eine Referenz auf das ihm zugeordnete Gruppenobjekt hält, kennt keine der Gruppen die Benutzer, die ihr zugeordnet sind.

Die Lösung

Bidirektionale Beziehungen sollten eigentlich anders funktionieren. Das oben geschilderte Problem ist das, was auch vielen anderen wegen seiner Struktur Probleme bereitet. Die Beziehung funktioniert beidseitig und solange der gleiche EntityManager läuft, können Objekte vom JPA-Provider zwischengespeichert werden. Daher reicht ein einseitiges Zuweisen nicht aus, vielmehr muss in diesem Fall auch der Gruppe der Benutzer zugewiesen werden. Erst dann sind die zwischengespeicherten Objekte vollständig.

JAVA:
  1. EntityManager em = createEntityManager();
  2.  
  3.         final Gruppe g1 = createGruppe("Demokraten", "Die Partei der Demokraten");
  4.         final Gruppe g2 = createGruppe("Republikaner", "Die Partei der Republikaner");
  5.  
  6.         final Benutzer b1 = createBenutzer("Obama", "Barack", "bobama", "obama@senate.gov");
  7.         final Benutzer b2 = createBenutzer("McCain", "John", "jmccain", "mccain@senate.gov");
  8.         final Benutzer b3 = createBenutzer("Clinton", "Hillary", "hclinton", "clinton@senate.gov");
  9.  
  10.         b1.setGruppe(g1);
  11.         b2.setGruppe(g2);
  12.         b3.setGruppe(g1);
  13.        
  14.         g1.getBenutzer().add(b1);
  15.         g2.getBenutzer().add(b2);
  16.         g1.getBenutzer().add(b3)
  17.  
  18.         em.persist(b1);
  19.         em.persist(b2);
  20.         em.persist(b3);
  21.        
  22.         em.persist(g1);
  23.         em.persist(g2);

Folgende Ausgabe zeigt das Ergebnis der Ã?nderung:

CODE:
  1. JPAClient.showDemo()@107: Benutzer [2952]: Clinton, Hillary (hclinton, clinton@senate.gov)
  2. JPAClient.showDemo()@108:   +--> Gruppe [2850]: Demokraten (Die Partei der Demokraten)
  3. JPAClient.showDemo()@107: Benutzer [2951]: McCain, John (jmccain, mccain@senate.gov)
  4. JPAClient.showDemo()@108:   +--> Gruppe [2851]: Republikaner (Die Partei der Republikaner)
  5. JPAClient.showDemo()@107: Benutzer [2950]: Obama, Barack (bobama, obama@senate.gov)
  6. JPAClient.showDemo()@108:   +--> Gruppe [2850]: Demokraten (Die Partei der Demokraten)
  7. JPAClient.showDemo()@112: Lade Gruppen
  8. JPAClient.showDemo()@120: Gruppe [2850]: Demokraten (Die Partei der Demokraten)
  9. JPAClient.showDemo()@121: Benutzerzahl: 2
  10. JPAClient.showDemo()@126:   +--> Benutzer [2952]: Clinton, Hillary (hclinton, clinton@senate.gov)
  11. JPAClient.showDemo()@126:   +--> Benutzer [2950]: Obama, Barack (bobama, obama@senate.gov)
  12. JPAClient.showDemo()@120: Gruppe [2851]: Republikaner (Die Partei der Republikaner)
  13. JPAClient.showDemo()@121: Benutzerzahl: 1
  14. JPAClient.showDemo()@126:   +--> Benutzer [2951]: McCain, John (jmccain, mccain@senate.gov)

Diese Art der Zuweisung im Code eines Client-Programms ist natürlich etwas unschön, sinnvoller wäre es, diese Zuweisung gleich im entsprechenden Setter der Benutzerklasse zu machen.

JAVA:
  1. public void setGruppe(final Gruppe gruppe)
  2.     {
  3.         _gruppe = gruppe;
  4.         if (null != _gruppe.getBenutzer() && !_gruppe.getBenutzer().contains(this))
  5.         {
  6.             _gruppe.getBenutzer().add(this);
  7.         }
  8.     }

Das Problem erübrigt sich auch, wenn man einen anderen EntityManager verwendet als den, der die Objekte persistiert hat.

2 Kommentare

EJB 3.0: Der steinige Weg zum Komfort

Einleitung

Bedingt durch meine Arbeit habe ich kürzlich an einer Java Enterprise Schulung teilgenommen und dort einiges neues gelernt. Der erste Teil der Schulung drehte sich um das Thema Enterprise Java Bean (EJB) 3.0. Ich spare mir jetzt mal die Erläuterungen, worum es geht, was man damit tut, usw., denn dieser Beitrag beschäftigt sich vorrangig mit den kleinen Hürden des Alltags.

Die Aufgabe: Es geht um die vermeintliche einfache Frage, wie man eine simple Bean implementiert, die Abfragen auf eine Datenbank macht und wie man diese Bean von einem Remote-Client aus anspricht. Das war die Aufgabe, die ich mir gestellt hatte, zusammen mit der Aufgabe, diese Software sowohl auf dem Glassfish als auch auf dem JBoss zum Laufen zu bringen.

Entwicklung der Bean

Vom Prinzip her ist dies der einfachste Teil. Hat man später erstmal die Hürden mit den Application Servern genommen, sollte EJB-Entwicklung eigentlich einfach bleiben, zumindest was den eigentlichen Bean-Teil angeht. Entwickelt werden soll eine Stateless SessionBean, die einige unterschiedliche Methoden hat, um Datenbankabfragen zu machen und die Ergebnisse teils als primitiven Typ, teil als Objekt zurück zu geben. Die Bean verwendet eine vom Bean Container bereitgestellte DataSource für den Zugriff auf die Datenbank. Diese DataSource wird per Injection in die Bean geholt.

Remote Business Interface

JAVA:
  1. package de.icanmakeit.blog.ejb3.client;
  2.  
  3. import de.icanmakeit.blog.ejb3.model.StatistikData;
  4. import javax.ejb.Remote;
  5. import java.util.Date;
  6.  
  7. public interface StatistikDemo
  8. {
  9.     int getUserCount();
  10.     Date getLastLogin(String username);
  11.     StatistikData getDatasetCount();
  12. }

Das Business Interface ist einfach gehalten. Um später von einem entfernten Client darauf zuzugreifen, ist die Annotation @Remote notwendig. Die Klasse StatistikData ist eine POJO-Bean (im Sinne von Java Bean, nicht im Sinne von Enterprise Bean) mit passenden Gettern und Settern. Sie sei hier mal außer Acht gelassen, da sie keine Logik oder spezielle Elemente enthält. Die Klasse implementiert allerdings das Interface Serializable, sonst wäre sie als Datentyp für den entfernten Datenaustausch nicht nutzbar.

Das Business Interface wird von der jeweiligen zuständigen Bean implementiert und dient auch später auf dem Client als Schnittstelle zur Bean.

Die Stateless SessionBean

Folgender Code spiegelt die eigentliche Bean wider. Die Logik, die zur Ermittlung der einzelnen Rückgabewerte führt, habe ich der Übersicht halber weggelassen, da sie für das Verständnis der Bean nicht wichtig ist.

JAVA:
  1. package de.icanmakeit.blog.ejb3.server;
  2.  
  3. import de.icanmakeit.blog.ejb3.client.StatistikDemo;
  4. import de.icanmakeit.blog.ejb3.model.StatistikData;
  5. import javax.ejb.Stateless;
  6. import javax.sql.DataSource;
  7. import javax.annotation.Resource;
  8. import java.util.Date;
  9.  
  10. @Stateless(name = "StatistikDemoEJB", mappedName = "StatistikDemoEJB-MappedName")
  11. public class StatistikDemoBean implements StatistikDemo
  12. {
  13.     private DataSource _dataSource;
  14.  
  15.     @Resource(name = "", mappedName = "", type = DataSource.class)
  16.     public void setDataSource(final DataSource ds)
  17.     {
  18.         this._dataSource = ds;
  19.     }
  20.  
  21.     public StatistikDemoBean()
  22.     {
  23.     }
  24.  
  25.     public int getUserCount()
  26.     {
  27.         final int userCount;
  28.         // Ergebnis bestimmen
  29.         // ...
  30.         return userCount;
  31.     }
  32.  
  33.     public Date getLastLogin(final String username)
  34.     {
  35.         final Date lastLogin;
  36.         // Ergebnis bestimmen
  37.         // ...
  38.         return lastLogin;
  39.     }
  40.  
  41.     public StatistikData getDatasetCount()
  42.     {
  43.         final StatistikData result;
  44.         // Ergebnis bestimmen
  45.         // ...
  46.         return result;
  47.     }
  48. }

Die Annotation @Stateless signalisiert, dass es sich um eine Stateless SessionBean handelt, die einen Namen und einen MappedName hat. Je nach Application Server wird der eine oder der andere für den späteren Lookup benötigt.

Über die @Resource Annotation wird der Container angewiesen, eine Instanz vom angegebenen Typ durch einen Lookup über den angegebenen Namen oder MappedName zu bestimmen und der Methode als Parameter mit zu geben. Dies geschieht, bevor ein Client irgendeine der definierten Methoden aufruft. Welche Angaben für name und mappedName eingetragen werden müssen, hängt vom jeweiligen Server ab, auf dem die Bean später läuft. Beispiele für JBoss und Glassfish folgen weiter unten.
Die @Resource Annotation könnte auch direkt an der Variable stehen, der Setter könnte dann wegfallen. Allerdings widerspricht das meinem Verständnis von Kapselung, außerdem ist der Getter für einen späteren Test ohne Container notwendig. Es bleibt aber festzuhalten, dass

JAVA:
  1. private DataSource _dataSource;
  2.  
  3. @Resource(name = "", mappedName = "", type = DataSource.class)
  4. public void setDataSource(final DataSource ds)
  5. {
  6.     this._dataSource = ds;
  7. }

und

JAVA:
  1. @Resource(name = "", mappedName = "", type = DataSource.class)
  2. private DataSource _dataSource;

den gleichen Effekt haben im EJB 3 Container.

EJB-Descriptor ejb-jar.xml

EJB 3 lässt dem Entwickler die Wahl, alle wichtigen Einstellungen der Beans auch in einer XML-Datei namens ejb-jar.xml zu definieren. Diese kann zusätzlich zu oder anstelle der Annotations in der Java-Klasse verwendet werden. Im fertigen JAR für das Deployment der EJB ist diese Datei im Verzeichnis META-INF unter zu bringen.
Eine entsprechende Datei für die oben definierte Bean sähe aus wie folgt.

XML:
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <ejb-jar xmlns="http://java.sun.com/xml/ns/javaee"
  3.          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4.          xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
  5.           http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd"
  6.          version="3.0">
  7.     <enterprise-beans>
  8.         <session>
  9.             <ejb-name>StatistikDemoEJB</ejb-name>
  10.             <ejb-class>de.icanmakeit.blog.ejb3.server.StatistikDemoBean</ejb-class>
  11.             <session-type>Stateless</session-type>
  12.             <transaction-type>Container</transaction-type>
  13.             <resource-ref>
  14.                 <res-ref-name><!-- JNDI-Name --></res-ref-name>
  15.                 <res-type>javax.sql.DataSource</res-type>
  16.                 <injection-target>
  17.                     <injection-target-class>de.icanmakeit.blog.ejb3.server.StatistikDemoBean</injection-target-class>
  18.                     <injection-target-name>setDataSource</injection-target-name>
  19.                 </injection-target>
  20.             </resource-ref>
  21.         </session>
  22.     </enterprise-beans>
  23. </ejb-jar>

Aufruf im Remote-Client

Grundsätzlich funktioniert folgender Quellcode für jeden Remote-Lookup einer Bean. Allerdings gibt es Unterschiede in Abhängigkeit davon, auf was für einem Server die Bean läuft. Diese Unterschiede machen sich in zwei Punkten bemerkbar:

  • Für die Erzeugung eines InitialContext-Objektes mit dem parameterlosen Konstruktor wird eine Datei jndi.properties im Klassenpfad benötigt, die festlegt, welche Factory den Context mit welcher URL erzeugen soll. Der Inhalt dieser Datei ist server-spezifisch. Details zur Konfiguration folgen in den konkreten Beispielen weiter unten.
  • Der JNDI-Name der zu verwendenden Bean variiert ebenfalls je nach Server.

Folgender Code verdeutlich aber den grundlegenden Ablauf:

JAVA:
  1. package de.icanmakeit.blog.ejb.clientapp;
  2.  
  3. import de.icanmakeit.blog.ejb.client.StatistikDemo;
  4. import de.icanmakeit.blog.ejb.model.StatistikData;
  5. import java.util.Date;
  6. import javax.naming.InitialContext;
  7. import javax.naming.NamingException;
  8.  
  9. public class EJBClient
  10. {
  11.     public static void main(final String[] args) throws NamingException
  12.     {
  13.         final InitialContext ic = new InitialContext();
  14.         final StatistikDemo stat = (StatistikDemo) ic.lookup("...");
  15.         System.out.println("Anzahl Benutzer: " + stat.getUserCount());
  16.         final StatistikData data = stat.getDatasetCount();
  17.         System.out.println("Anzahl Datensätze: " + data.getOverallCount() + ", davon " + data.getOutdatedCount() + " nicht akutell.");
  18.         System.out.println("Letzer Login des Benutzers 'test': " + stat.getLastLogin("test"));
  19.     }
  20. }

Deployment im Glassfish

Glassfish ist der OpenSource Application Server, den Sun für sich als Sun Application Server 9 verwendet. Er bietet ein komfortables Frontend zur Konfiguration vieler Einstellungen, unter anderem auch für Datenbankverbindungen.

Datenbank-Verbindung einrichten

Zunächst muss ein Datenbankpool eingerichtet werden. Dies kann man unter Resources > JDBC > Connection Pools tun. Die Einrichtung sollte selbsterklärend sein. Bitte beachten, dass der Datenbanktreiber dem Server noch im entsprechenden lib-Verzeichnis zugänglich gemacht werden muss. Nachdem der Pool eingerichtet wurde, kann mit ihm eine JDBC-Ressource für den Container bereit gestellt werden. Dies geht im Menüpunkt Resources > JDBC > JDBC Resources und ist ähnlich einfach. Diese Einstellungen fügen Zeilen wie die folgenden in die Datei domain.xml im Verzeichnis domains/config/ein:

XML:
  1. <domain>
  2.     ...
  3.     <resources>
  4.         ...
  5.         <jdbc-resource enabled="true" jndi-name="jdbc/__testdb" object-type="user" pool-name="TestMySQLPool"/>
  6.         ...
  7.         <jdbc-connection-pool
  8.                 allow-non-component-callers="false"
  9.                 connection-validation-method="auto-commit"
  10.                 datasource-classname="com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource"
  11.                 fail-all-connections="false"
  12.                 idle-timeout-in-seconds="300"
  13.                 is-connection-validation-required="true"
  14.                 is-isolation-level-guaranteed="false"
  15.                 max-pool-size="4"
  16.                 max-wait-time-in-millis="60000"
  17.                 name="TestMySQLPool"
  18.                 non-transactional-connections="false"
  19.                 pool-resize-quantity="2"
  20.                 res-type="javax.sql.ConnectionPoolDataSource"
  21.                 steady-pool-size="1">
  22.             <description>Testverbindungspool</description>
  23.             <property name="user" value="username"/>
  24.             <property name="port" value="3306"/>
  25.             <property name="password" value="secret"/>
  26.             <property name="databaseName" value="test_db"/>
  27.             <property name="serverName" value="myserver.local.net"/>
  28.         </jdbc-connection-pool>
  29.         ...
  30.     </resources>
  31.     ...
  32. </domain>

Bean anpassen & deployen

Da die DataSource jetzt konfiguriert ist, kann ein entsprechender Name in die @Resource Annotation der Bean eingetragen werden. Für den Glassfish sieht dieser dann aus wie folgt:

JAVA:
  1. @Resource(name = "jdbc/__testdb", type = DataSource.class)
  2. public void setDataSource(final DataSource ds)
  3. {
  4.     this._dataSource = ds;
  5. }

Diese Regelungen gelten analog für Einträge in die ejb-jar.xml. Das Deployment der Bean kann nun über die Administrationswebseite erfolgen.

Client anpassen und starten

Um eine Bean auf dem Glassfish Server anzusprechen, muss auf dem Client ihr mapped name verwendet werden. Der Client wird also wie folgt angepasst:

JAVA:
  1. final InitialContext ic = new InitialContext();
  2. final StatistikDemo stat = (StatistikDemo) ic.lookup("StatistikDemoEJB-MappedName");

Der Client läuft nur mit server-spezifischen Klassen im Klassenpfad, es muss die JAR-Datei appserv-rt.jar aus dem lib-Verzeichnis des Servers eingebunden werden. Dies ist leider ein echtes Manko, denn durch fehlende Aufteilung dieser Bibliothek muss jeder Client die vollen 16 MB dieser Datei verwenden. In diese Bibliothek ist auch eine jndi.properties enthalten, deren Einstellungen vollkommen ausreichen, wenn Glassfish auf dem lokalen Rechner läuft. Läuft er woanders, muss eine eigene jndi.properties erstellt und die URL angepasst werden:

CODE:
  1. java.naming.factory.initial=com.sun.enterprise.naming.SerialInitContextFactory
  2. java.naming.provider.url=iiop://localhost:1050
  3. java.naming.factory.url.pkgs=com.sun.enterprise.naming:com.sun.enterprise.naming

Danach sollte einem erfolgreichen Testlauf nichts mehr im Wege stehen.

Deployment im JBoss

Um EJB 3.0 mit dem JBoss zu nutzen, muss man den Installer der derzeit aktuellsten Version (4.0.4) herunterladen und den Server darüber installieren. Vollwertige EJB 3 Unterstützung enthält nur das EJB 3.0 Installationsprofil, selbst das Installationsprofil all kann mit EJB 3 nichts anfangen.

Datenbankverbindung einrichten

Da der JBoss leider nicht über eine Administrationsoberfläche verfügt, muss man die Datenbank mit Hilfe einer Datei konfigurieren. Zahlreiche Beispiele für unterschiedliche Datenbanken liefert der JBoss direkt mit (Verzeichnis docs/examples/jca). Die Konfigurationsdateien für Datenbankverbindungen müssen auf den Namen -ds.xml enden, damit der JBoss sie als das erkennt, was sie sind. Sie können entweder im Verzeichnis deploy des jeweiligen Profils abgelegt werden (üblicherweise server/default/deploy) oder in die JAR-Datei der Bean integriert werden. Ich benutze für dieses Beispiel eine MySQL-Datenquelle, die wie folgt konfiguriert ist:

XML:
  1. <?xml version="1.0" encoding="ISO-8859-1"?>
  2. <!DOCTYPE datasources
  3.        PUBLIC "-//JBoss//DTD JBOSS JCA Config 1.5//EN"
  4.        "http://www.jboss.org/j2ee/dtd/jboss-ds_1_5.dtd">
  5. <datasources>
  6.     <local-tx-datasource>
  7.         <jndi-name>Test-DS</jndi-name>
  8.         <connection-url>jdbc:mysql://myserver.local.net/test_db</connection-url>
  9.         <driver-class>com.mysql.jdbc.Driver</driver-class>
  10.         <user-name>username</user-name>
  11.         <password>secret</password>
  12.         <new-connection-sql>SELECT 1</new-connection-sql>
  13.         <check-valid-connection-sql>SELECT 1</check-valid-connection-sql>
  14.         <valid-connection-checker-class-name>
  15.             org.jboss.resource.adapter.jdbc.vendor.MySQLValidConnectionChecker
  16.         </valid-connection-checker-class-name>
  17.         <exception-sorter-class-name>
  18.             org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter
  19.         </exception-sorter-class-name>
  20.         <metadata>
  21.             <type-mapping>mySQL</type-mapping>
  22.         </metadata>
  23.     </local-tx-datasource>
  24. </datasources>

Bean anpassen & deployen

Die Adressierung der Datenbankverbindung für die Injection in die Bean folgt im JBoss einer anderen Syntax als im Glassfish. Statt der Eigenschaft name muss hier die Eigenschaft mapped name verwendet werden. Hier ist der JNDI-Name der konfigurierten Datenbank mit java:/ als Präfix einzusetzen:

JAVA:
  1. @Resource(mappedName = "java:/Test-DS", type = DataSource.class)
  2. public void setDataSource(final DataSource ds)
  3. {
  4.     this._dataSource = ds;
  5. }

Diese Regelungen gelten analog für Einträge in die ejb-jar.xml. Nachdem man die Bean als JAR gepackt hat, kann sie durch Kopieren dieses JARs in den oben erwähnten Ordner deploy auf dem Server installiert werden. Etwaige Fehler dabei erscheinen in der Logdatei.

Client anpassen und starten

Um auf die im JBoss laufende Bean entfernt zugreifen zu können, muss im Client der Lookup über den Namen der Bean mit dem Zusatz /remote erfolgen:

JAVA:
  1. final InitialContext ic = new InitialContext();
  2. final StatistikDemo stat = (StatistikDemo) ic.lookup("java:/StatistikDemoEJB/remote");

Damit der Lookup klappt, muss auch eine JBoss-spezifische Konfiguration in der jndi.properties eingetragen werden. Im Falle des JBoss gibt es keine Vorgabedatei in einer der Bibliotheken, es muss also immer eine eigene angelegt werden. Es ist hierbei darauf zu achten, dass die URL nicht wie beim Glassfish mit einem Protokoll versehen wird. Die Eintragung von z.B. iiop:// würde zu einer Exception Unknown protocol iiop führen.

CODE:
  1. java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
  2. java.naming.factory.url.pkgs=org.jboss.naming :o rg.jnp.interfaces
  3. java.naming.provider.url=localhost:1099

Im Gegensatz zum Glassfish hat der JBoss sauber getrennte Client-Bibliotheken, die schon fast zu speziell unterteilt sind. Für eine Anwendung wie diese müssen folgende Bibliotheken aus dem client-Verzeichnis verwendet werden:

  • concurrent.jar
  • jboss-aop-jdk50-client.jar
  • jboss-aspect-jdk50-client.jar
  • jboss-client.jar
  • jboss-common-client.jar
  • jboss-ejb3-client.jar
  • jboss-j2ee.jar
  • jboss-remoting.jar
  • jbosssx-client.jar
  • jboss-transaction-client.jar
  • jnp-client.jar

Für andere Clients könnten noch zusätzliche Bibliotheken notwendig werden. Ein Teil der Bibliotheken ist auch zusammengefasst im jbossall-client.jar, da diese Bibliothek nicht ausreicht und alleine schon 4 MB auf die Waage bringt, hielt ich es für sinnvoller, die einzelnen Dateien aufzulisten, diese zusammen sind nur 2 MB groß (also um Faktor 8 kleiner als beim Glassfish).

Fazit

Der eigentlich EJB 3.0 Standard stellt für Enterprise-Entwickler eine deutliche Erleichterung gegenüber EJB 2.0 dar. Für Neulinge in dem Bereich wird das Erlernen der Techniken einfacher, weil komplizierte und abstrakte Konstrukte abgeschafft wurden.

Gemessen an den anfänglichen Hürden zwischen der EJB-Theorie und dem tatsächlichen praktischen Einsatz, wäre es besser gewesen, man hätte gerade im Bereich JNDI-Adressierung für die am häufigsten verwendeten Dinge wie Datenbankverbindungen, EntityManager, o.ä. ein einheitliches Adressierungssystem mit in den Standard aufgenommen. Die Einarbeitung in die unterschiedlichen Implementierungen gleicht gerade zu Beginn einem Puzzlespiel. Hat man diese Hürden aber erstmal genommen, steht dem komfortablen Einsatz von EJB 3.0 nichts mehr im Wege (zumindest, bis man auf die nächste "Tretmine" tritt).

,

Keine Kommentare