Skip to content

6. Version 2: OpenEJB-/JPA-Architektur

6.1. Einführung in die Portierungsprinzipien

Hier stellen wir die Prinzipien vor, die für die Portierung einer JPA-/Spring-/Hibernate-Anwendung auf eine JPA-/OpenEJB-/EclipseLink-Anwendung maßgeblich sind. Wir werden bis Abschnitt 6.2 warten, um die Maven-Projekte anzulegen.

6.1.1. Die beiden Architekturen

Die aktuelle Implementierung mit Spring / Hibernate

Die zu erstellende Implementierung mit OpenEJB / EclipseLink

6.1.2. Projektbibliotheken

  • Die [DAO]- und [Business]-Schichten werden nicht mehr von Spring instanziiert. Sie werden vom OpenEJB-Container instanziiert.
  • Die Spring-Container-Bibliotheken und deren Konfiguration werden durch die OpenEJB-Container-Bibliotheken und deren Konfiguration ersetzt.
  • Die Bibliotheken der JPA-/Hibernate-Schicht werden durch die der JPA-/EclipseLink-Schicht ersetzt.

  • Die Datei [META-INF/persistence.xml], die die JPA-Schicht konfiguriert, sieht nun wie folgt aus:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
  <persistence-unit name="pam-openejb-ui-metier-dao-jpa-eclipselinkPU" transaction-type="JTA">
     <!-- entities JPA -->
    <class>jpa.Cotisation</class>
    <class>jpa.Employe</class>
    <class>jpa.Indemnite</class>
     <!-- the supplier JPA is EclipseLink -->
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
     <!-- provider properties -->
    <properties>
      <property name="eclipselink.ddl-generation" value="create-tables"/>
    </properties>
  </persistence-unit>
</persistence>
  • Zeile 3: Transaktionen in einem EJB-Container sind vom Typ JTA (Java Transaction API). Bei Spring waren sie vom Typ RESOURCE_LOCAL.
  • Zeile 9: Die verwendete JPA-Implementierung ist EclipseLink
  • Zeilen 5–7: Von der JPA-Schicht verwaltete Entitäten
  • Zeilen 11–13: Eigenschaften des EclipseLink-Anbieters
  • Zeile 12: Bei jeder Ausführung werden Tabellen angelegt

Die JDBC-Eigenschaften der vom OpenEJB-Container verwendeten JTA-Datenquelle werden durch die folgende Konfigurationsdatei [conf/openejb.conf] festgelegt:

1
2
3
4
5
6
7
8
<?xml version="1.0"?>
<openejb>
  <Resource id="Default JDBC Database">
    JdbcDriver com.mysql.jdbc.Driver
    JdbcUrl jdbc:mysql://localhost:3306/dbpam_eclipselink
    UserName root
  </Resource>
</openejb>
  • Zeile 3: Wir verwenden die ID „Default JDBC Database“, wenn wir mit einem in die Anwendung selbst eingebetteten OpenEJB-Container arbeiten.
  • Zeile 5: Wir verwenden eine MySQL-Datenbank [dbpam_eclipselink]

6.1.4. Implementierung der [DAO]-Schicht unter Verwendung von EJBs

  • Die Klassen, die die [DAO]-Schicht implementieren, werden zu EJBs. Nehmen wir das Beispiel der Klasse [CotisationDao]:

Die Schnittstelle [ICotisationDao] in der Spring-Version sah wie folgt aus:

package dao;

import java.util.List;
import jpa.Cotisation;

public interface ICotisationDao {
   // create a new contribution
  Cotisation create(Cotisation cotisation);
   // modify an existing contribution
  Cotisation edit(Cotisation cotisation);
   // delete an existing contribution
  void destroy(Cotisation cotisation);
   // search for a specific contribution
  Cotisation find(Long id);
   // get all objects Contribution
  List<Cotisation> findAll();

}

Das EJB implementiert dieselbe Schnittstelle in zwei verschiedenen Formen: einer lokalen und einer entfernten. Die lokale Schnittstelle kann von einem Client verwendet werden, der in derselben JVM läuft, während die entfernte Schnittstelle von einem Client verwendet werden kann, der in einer anderen JVM läuft.

Die lokale Schnittstelle:

1
2
3
4
5
6
7
package dao;

import javax.ejb.Local;

@Local
public interface ICotisationDaoLocal extends ICotisationDao{
}
  • Zeile 6: Die Schnittstelle [ICotisationDaoLocal] erbt von der Schnittstelle [ICotisationDao], um alle deren Methoden zu übernehmen. Sie fügt keine neuen hinzu.
  • Zeile 5: Die Annotation @Local macht sie zu einer lokalen Schnittstelle für das EJB, das sie implementieren wird.

Die Remote-Schnittstelle:

1
2
3
4
5
6
7
package dao;

import javax.ejb.Remote;

@Remote
public interface ICotisationDaoRemote extends ICotisationDao{
}
  • Zeile 6: Die Schnittstelle [ICotisationDaoRemote] erbt von der Schnittstelle [ICotisationDao], um alle deren Methoden zu übernehmen. Sie fügt keine neuen hinzu.
  • Zeile 5: Die Annotation @Remote macht es zu einer Remote-Schnittstelle für das EJB, das sie implementieren wird.

Die [DAO]-Schicht wird von einem EJB implementiert, das beide Schnittstellen implementiert (dies ist nicht zwingend erforderlich):

1
2
3
4
5
6
@Stateless()
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class CotisationDao implements ICotisationDaoLocal, ICotisationDaoRemote {

  @PersistenceContext
  private EntityManager em;
  • Zeile 1: die Annotation @Stateless, die die Klasse zu einem EJB macht
  • Zeile 2: Die Annotation @TransactionAttribute, die sicherstellt, dass jede Methode in der Klasse innerhalb einer Transaktion ausgeführt wird.
  • Zeile 5: Die Annotation @PersistenceContext, die den EntityManager der JPA-Schicht in die Klasse [CotisationDao] einfügt. Sie ist identisch mit der in der Spring-Version.

Wenn die lokale Schnittstelle der [DAO]-Schicht verwendet wird, läuft der Client dieser Schnittstelle in derselben JVM.

Oben tauschen die [Business]- und [DAO]-Schichten Objekte per Referenz aus. Wenn eine Schicht das gemeinsam genutzte Objekt ändert, erkennt die andere Schicht diese Änderung.

Wenn die Remote-Schnittstelle der [DAO]-Schicht verwendet wird, läuft der Client dieser Schnittstelle in der Regel in einer anderen JVM.

In der obigen Abbildung tauschen die [Business]- und [DAO]-Schichten Objekte nach Wert aus (Serialisierung des ausgetauschten Objekts). Wenn eine Schicht ein gemeinsam genutztes Objekt ändert, nimmt die andere Schicht diese Änderung nur wahr, wenn das geänderte Objekt an sie zurückgegeben wird.

6.1.5. Implementierung der [Business]-Schicht unter Verwendung eines EJB

  • Die Klasse, die die [business]-Schicht implementiert, wird ebenfalls zu einem EJB, das eine lokale und eine Remote-Schnittstelle implementiert. Die ursprüngliche [IMetier]-Schnittstelle sah wie folgt aus:
package metier;

import java.util.List;
import jpa.Employe;

public interface IMetier {
   // get your payslip
  FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
   // list of employees
  List<Employe> findAllEmployes();
}

Wir erstellen eine lokale Schnittstelle und eine Remote-Schnittstelle auf Basis der vorherigen Schnittstelle:

1
2
3
4
5
6
7
package metier;

import javax.ejb.Local;

@Local
public interface IMetierLocal extends IMetier{
}
1
2
3
4
5
6
7
package metier;

import javax.ejb.Remote;

@Remote
public interface IMetierRemote extends IMetier{
}

Das EJB in der [Business]-Schicht implementiert diese beiden Schnittstellen:

@Stateless()
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class Metier implements IMetierLocal, IMetierRemote {

   // reference to local [DAO] layers
  @EJB
  private ICotisationDaoLocal cotisationDao = null;
  @EJB
  private IEmployeDaoLocal employeDao = null;
  @EJB
  private IIndemniteDaoLocal indemniteDao = null;
  • Zeilen 1–2: Definieren Sie ein EJB, in dem jede Methode innerhalb einer Transaktion ausgeführt wird.
  • Zeile 7: Eine Referenz auf die lokale Schnittstelle des EJB [CotisationDao].
  • Zeile 6: Die Annotation @EJB weist den EJB-Container an, eine Referenz auf die lokale Schnittstelle des EJB [CotisationDao] einzufügen.
  • Zeilen 8–11: Wir verfahren ebenso für die lokalen Schnittstellen der EJBs [EmployeeDao] und [CompensationDao].

Letztendlich werden bei der Instanziierung des [Metier]-EJBs die Felder in den Zeilen 7, 9 und 11 mit Verweisen auf die lokalen Schnittstellen der drei EJBs in der [DAO]-Schicht initialisiert. Wir gehen hier also davon aus, dass die [Business]- und die [DAO]-Schicht in derselben JVM ausgeführt werden.

6.1.6. EJB-Clients

In der obigen Abbildung muss die [ui]-Schicht, um mit der [business]-Schicht zu kommunizieren, eine Referenz auf die Remote-Schnittstelle des EJB in der [business]-Schicht abrufen.

Im obigen Diagramm muss die [UI]-Schicht, um mit der [Business]-Schicht zu kommunizieren, eine Referenz auf die lokale Schnittstelle des EJBs der [Business]-Schicht abrufen. Die Methode zum Abrufen dieser Referenzen variiert von Container zu Container. Für den OpenEJB-Container können Sie wie folgt vorgehen:

Referenz auf die lokale Schnittstelle:

1
2
3
4
5
6
7
8
9
     // configure the embedded Open EJB container
    Properties properties = new Properties();
    properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
      // initialization of JNDI context with previous properties
    InitialContext initialContext = new InitialContext(properties);
     // instantiation layers DAO
    employeDao = (IEmployeDaoLocal) initialContext.lookup("EmployeDaoLocal");
    cotisationDao = (ICotisationDaoLocal) initialContext.lookup("CotisationDaoLocal");
indemniteDao = (IIndemniteDaoLocal) initialContext.lookup("IndemniteDaoLocal");
  • Zeilen 2–5: Der OpenEJB-Container wird initialisiert.
  • Zeile 5: Wir verfügen über einen JNDI-Kontext (Java Naming and Directory Interface), der es uns ermöglicht, Referenzen auf die EJBs abzurufen. Jedes EJB wird durch einen JNDI-Namen identifiziert:
  • (Fortsetzung)
    • Für die lokale Schnittstelle fügen Sie „Local“ zum EJB-Namen hinzu (Zeilen 7–9)
    • Für die Remote-Schnittstelle fügen wir „Remote“ zum EJB-Namen hinzu

Bei Java EE 5 variieren diese Regeln je nach EJB-Container. Dies stellt eine Herausforderung dar. Mit Java EE 6 wurde eine JNDI-Notation eingeführt, die auf allen Anwendungsservern portabel ist.

Der vorstehende Code ruft Referenzen auf die lokalen Schnittstellen von EJBs über deren JNDI-Namen ab. Wir haben bereits erwähnt, dass diese auch über die Annotation @EJB abgerufen werden können. Daher könnten wir folgendes schreiben:

@EJB
private IemployeDaoLocal employeDaoLocal ;

Die Annotation @EJB wird nur berücksichtigt, wenn sie zu einer Klasse gehört, die vom EJB-Container geladen wird. Dies ist beispielsweise bei der Klasse [Metier] der Fall. Der obige Code gehört jedoch zu einer Konsolenklasse, die nicht vom EJB-Container geladen wird. Wir sind daher gezwungen, die JNDI-Namen der EJBs zu verwenden.

Nachfolgend finden Sie den Code, um eine Referenz auf die Remote-Schnittstelle des [Metier]-EJBs zu erhalten:

1
2
3
4
5
6
7
8
     // configure the embedded Open EJB container
    Properties properties = new Properties();
    properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
     // context initialization JNDI of container EJB
    InitialContext initialContext = new InitialContext(properties);

     // remote business layer instantiation
metier = (IMetierRemote) initialContext.lookup("MetierRemote");

6.2. Praktische Übung

Wir schlagen vor, die NetBeans-Spring/Hibernate-Anwendung auf eine OpenEJB/EclipseLink-Architektur zu migrieren.

Die aktuelle Implementierung mit Spring / Hibernate

Die zu erstellende Implementierung mit OpenEJB / EclipseLink

Falls sie noch nicht existiert, erstellen Sie die MySQL-Datenbank [dbpam_eclipselink]. Falls sie bereits existiert, löschen Sie alle darin enthaltenen Tabellen. Erstellen Sie eine NetBeans-Verbindung zu dieser Datenbank, wie in Abschnitt 6.2.1 beschrieben.

6.2.2. Erstkonfiguration des NetBeans-Projekts

  • Laden Sie das Maven-Projekt [mv-pam-spring-hibernate]
  • Erstellen Sie ein neues Maven-Java-Projekt [mv-pam-openejb-eclipselink] [1]
  • Erstellen Sie auf der Registerkarte [Dateien] [2] einen Ordner [conf] [3] unter dem Projektstammverzeichnis
  • Legen Sie die folgende Datei [openejb.conf] [4] in diesem Ordner ab:
1
2
3
4
5
6
7
8
<?xml version="1.0"?>
<openejb>
  <Resource id="Default JDBC Database">
    JdbcDriver com.mysql.jdbc.Driver
    JdbcUrl jdbc:mysql://localhost:3306/dbpam_eclipselink
    UserName root
  </Resource>
</openejb>
  • Erstellen Sie den Ordner [src/main/resources/META-INF] [5]
  • Legen Sie darin die folgende Datei [persistence.xml] [6] ab:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence              http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
  <persistence-unit name="dbpam_eclipselinkPU" transaction-type="JTA">
    <!-- the supplier JPA is EclipseLink -->
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <!-- jpa entities -->
    <class>jpa.Cotisation</class>
    <class>jpa.Employe</class>
    <class>jpa.Indemnite</class>
    <!-- properties provider EclipseLink -->
    <properties>
      <property name="eclipselink.logging.level" value="FINE"/>
      <property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
    </properties>
  </persistence-unit>
</persistence>
  • Zeile 12: Wir fordern detaillierte Protokolle von EclipseLink an,
  • Zeile 13: Tabellen werden bei der Instanziierung der JPA-Schicht erstellt,
  • Fügen Sie die OpenEJB- und EclipseLink-Bibliotheken sowie den MySQL-JDBC-Treiber zur [pom.xml]-Datei des Projekts hinzu:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
 
  <groupId>istia.st</groupId>
  <artifactId>mv-pam-openejb-eclipselink</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>
 
  <name>mv-pam-openejb-eclipselink</name>
  <url>http://maven.apache.org</url>
 
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
 
  <dependencies>
    <dependency>
      <groupId>org.apache.openejb</groupId>
      <artifactId>openejb-core</artifactId>
      <version>4.0.0</version>
    </dependency>                
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.10</version>
      <scope>test</scope>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>org.eclipse.persistence</groupId>
      <artifactId>eclipselink</artifactId>
      <version>2.3.0</version>
    </dependency>
    <dependency>
      <groupId>org.eclipse.persistence</groupId>
      <artifactId>javax.persistence</artifactId>
      <version>2.0.3</version>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.6</version>
    </dependency>
    <dependency>
      <groupId>org.swinglabs</groupId>
      <artifactId>swing-layout</artifactId>
      <version>1.0.3</version>
    </dependency>
  </dependencies>
 
  <repositories>
    <repository>
      <url>http://download.eclipse.org/rt/eclipselink/maven.repo/</url>
      <id>eclipselink</id>
      <layout>default</layout>
      <name>Repository for library Library[eclipselink]</name>
    </repository>
  </repositories>
 
</project>
  • Zeilen 18–22: die OpenEJB-Abhängigkeit,
  • Zeilen 30–39: die EclipseLink-Abhängigkeiten,
  • Zeilen 41–44: die Abhängigkeit vom MySQL-JDBC-Treiber

6.2.3. Portierung der [DAO]-Schicht

Wir werden die [DAO]-Schicht portieren, indem wir Pakete aus dem Projekt [mv-pam-spring-hibernate] in das Projekt [mv-pam-openejb-eclipselink] kopieren.

  • Kopieren Sie die Pakete [dao, exception, jpa]
 

Die oben gemeldeten Fehler sind darauf zurückzuführen, dass die kopierte [DAO]-Schicht Spring verwendet und die Spring-Bibliotheken nicht mehr Teil des Projekts sind.

6.2.3.1. Die EJB [CotisationDao]

Wir erstellen die lokalen und Remote-Schnittstellen für das zukünftige EJB [CotisationDao]:

Die lokale Schnittstelle ICotisationDaoLocal:

1
2
3
4
5
6
7
package dao;

import javax.ejb.Local;

@Local
public interface ICotisationDaoLocal extends ICotisationDao{
}

Um sicherzustellen, dass die Pakete korrekt importiert werden, klicken Sie mit der rechten Maustaste auf den Code und wählen Sie [Importe korrigieren].

Die Remote-Schnittstelle ICotisationDaoRemote:

1
2
3
4
5
6
7
package dao;

import javax.ejb.Remote;

@Remote
public interface ICotisationDaoRemote extends ICotisationDao{
}

Anschließend ändern wir die Klasse [CotisationDao], um sie in ein EJB umzuwandeln:

...
import javax.persistence.PersistenceContext;
import jpa.Cotisation;

@Stateless
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class CotisationDao implements ICotisationDaoLocal, ICotisationDaoRemote {

  @PersistenceContext
  private EntityManager em;
...  

Der Import, den diese Klasse aus dem Spring-Framework vorgenommen hat, verschwindet. Führen Sie im Projekt einen [Clean and Build] durch:

In [1] gibt es keine Fehler mehr in der Klasse [CotisationDao].

6.2.3.2. Die EJBs [EmployeDao] und [IndemniteDao]

Wir wiederholen denselben Vorgang für die anderen Elemente der [DAO]-Schicht:

  • Schnittstellen IEmployeDaoLocal und IEmployeDaoRemote, abgeleitet von IEmployeDao
  • EJB `EmployeDao`, das diese beiden Schnittstellen implementiert
  • Schnittstellen IIndemniteDaoLocal und IIndemniteDaoRemote, abgeleitet von IIndemniteDao
  • EJB IndemniteDao, das diese beiden Schnittstellen implementiert

Sobald dies erledigt ist, gibt es keine Fehler mehr im Projekt [2].

6.2.3.3. Die Klasse [PamException]

Die Klasse [PamException] bleibt unverändert, mit einer kleinen Änderung:

package exception;

import javax.ejb.ApplicationException;

@ApplicationException(rollback=true)
public class PamException extends RuntimeException {

   // error code
  private int code;
...

Zeile 5 wurde hinzugefügt. Um die richtigen Importe zu erhalten, klicken Sie auf [Importe korrigieren].

Um die Anmerkung in Zeile 5 zu verstehen, denken Sie daran, dass jede Methode in den EJBs unserer [DAO]-Schicht:

  • innerhalb einer Transaktion ausgeführt wird, die vom EJB-Container gestartet und beendet wird
  • eine [PamException] auslöst, sobald etwas schiefgeht

Wenn die [Business]-Schicht eine Methode M der [DAO]-Schicht aufruft, wird dieser Aufruf vom EJB-Container abgefangen. Es ist, als gäbe es eine Zwischenklasse zwischen der [Business]-Schicht und der [DAO]-Schicht – hier [EJB-Proxy] genannt –, die alle Aufrufe an die [DAO]-Schicht abfängt. Wenn der Aufruf der Methode M der [DAO]-Schicht abgefangen wird, startet der EJB-Proxy eine Transaktion und übergibt dann die Kontrolle an die Methode M der [DAO]-Schicht, die dann innerhalb dieser Transaktion ausgeführt wird. Die Methode M kann mit oder ohne Ausnahme beendet werden.

  • Wenn die Methode M ohne Ausnahme beendet wird, kehrt die Kontrolle zum EJB-Proxy zurück, der die Transaktion durch ein Commit beendet. Die Kontrolle wird dann an die aufrufende Methode in der [Business]-Schicht zurückgegeben
  • Wenn die Methode M mit einer Ausnahme beendet wird, kehrt die Ausführung zum EJB-Proxy zurück, der die Transaktion durch einen Rollback beendet. Zusätzlich verpackt er diese Ausnahme in eine EJBException. Die Ausführung kehrt dann zur aufrufenden Methode in der [Business]-Schicht zurück, die somit eine EJBException erhält. Die Annotation in Zeile 5 oben verhindert diese Kapselung. Die [Business]-Schicht erhält daher eine PamException. Darüber hinaus weist das Attribut rollback=true den EJB-Proxy an, dass er bei Erhalt einer PamException die Transaktion zurückrollen muss.

6.2.3.4. Testen der [DAO]-Schicht

Unsere durch EJBs implementierte [DAO]-Schicht kann getestet werden. Wir beginnen damit, das [dao]-Paket aus [Test Packages] im Projekt [mv-pam-springhibernate] in das aktuell in Entwicklung befindliche Projekt zu kopieren [1]:

Wir behalten nur den Test [JUnitInitDB] bei, der die Datenbank mit einigen Daten initialisiert [2]. Wir benennen die Klasse [JUnitInitDbLocal] um [3]. Die Klasse [JUnitInitDBLocal] wird die lokale Schnittstelle der EJBs der [DAO]-Schicht verwenden.

Zunächst ändern wir die Klasse [JUnitInitDBLocal] wie folgt:

public class JUnitInitDBLocal {

  static private IEmployeDaoLocal employeDao = null;
  static private ICotisationDaoLocal cotisationDao = null;
  static private IIndemniteDaoLocal indemniteDao = null;

  @BeforeClass
  public static void init() throws Exception {
     // configure the embedded Open EJB container
    Properties properties = new Properties();
    properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
      // initialization of JNDI context with previous properties
    InitialContext initialContext = new InitialContext(properties);
     // instantiation of local DAO layers
    employeDao = (IEmployeDaoLocal) initialContext.lookup("EmployeDaoLocal");
    cotisationDao = (ICotisationDaoLocal) initialContext.lookup("CotisationDaoLocal");
    indemniteDao = (IIndemniteDaoLocal) initialContext.lookup("IndemniteDaoLocal");
}
  • Zeilen 3–5: Verweise auf die lokalen Schnittstellen der EJBs in der [DAO]-Schicht
  • Zeile 7: @BeforeClass kennzeichnet die Methode, die beim Start des JUnit-Tests ausgeführt wird
  • Zeilen 10–13: Initialisierung des OpenEJB-Containers. Diese Initialisierung ist proprietär und variiert je nach EJB-Container.
  • Zeile 13: Wir verfügen über einen JNDI-Kontext (Java Naming and Directory Interface), der den Zugriff auf EJBs über Namen ermöglicht. Bei OpenEJB wird die lokale Schnittstelle eines EJBs durch ELocal und die Remote-Schnittstelle durch ERemote bezeichnet.
  • Zeilen 15–17: Wir fordern eine Referenz auf die lokalen Schnittstellen der EJBs [EmployeDao, CotisationDao, IndemniteDao] aus dem JNDI-Kontext an.

Erstellen Sie das Projekt, starten Sie gegebenenfalls den MySQL-Server und führen Sie den Test JUnitInitDBLocal aus. Beachten Sie, dass die Datei [persistence.xml] so konfiguriert wurde, dass die Tabellen bei jedem Durchlauf neu erstellt werden. Vor dem Ausführen des Tests sollten Sie alle Tabellen in der MySQL-Datenbank [dbpam_eclipselink] löschen.

  • Löschen Sie in [1] auf der Registerkarte [Services] die Tabellen aus der in Abschnitt 6.2.1 eingerichteten NetBeans-Verbindung.
  • In [2] enthält die Datenbank [dbpam_eclipselink] keine Tabellen mehr
  • In [3] wird das Projekt erstellt
  • In [4] wird der Test „JUnitInitDBLocal“ ausgeführt
  • In [5] wurde der Test bestanden
  • In [6] aktualisieren Sie die NetBeans-Verbindung
  • In [7] sehen wir die 4 Tabellen, die von der JPA-Schicht erstellt wurden. Der Zweck des Tests bestand darin, diese zu füllen. Wir sehen uns den Inhalt einer davon an
  • in [8] den Inhalt der Tabelle [EMPLOYEES]

Der OpenEJB-Container zeigte Protokolle in der Konsole an:

...
  • Zeilen 2–3: die beiden JNDI-Namen des EJB [CotisationDaoLocal],
  • Zeilen 4–5: die beiden JNDI-Namen des EJB [CotisationDaoRemote],
  • Zeilen 7–8: die beiden JNDI-Namen des EJB [EmployeDaoLocal],
  • Zeilen 9–10: die beiden JNDI-Namen des EJB [EmployeDaoRemote],
  • Zeilen 12–13: die beiden JNDI-Namen des EJB [IndemniteDaoLocal],
  • Zeilen 14–15: die beiden JNDI-Namen des EJB [EmployeeDaoRemote].

Wir führen denselben Test erneut durch, diesmal unter Verwendung der Remote-Schnittstelle der EJBs.

In [1] wurde die Klasse [JUnitInitDBLocal] kopiert und in [JUnitInitDBRemote] eingefügt. In dieser Klasse ersetzen wir die lokalen Schnittstellen durch die Remote-Schnittstellen:

Infos - PersistenceUnit(name=dbpam_eclipselinkPU, provider=org.eclipse.persistence.jpa.PersistenceProvider) - provider time 396ms
Infos - Jndi(name=CotisationDaoLocal) --> Ejb(deployment-id=CotisationDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/CotisationDao!dao.ICotisationDaoLocal) --> Ejb(deployment-id=CotisationDao)
Infos - Jndi(name=CotisationDaoRemote) --> Ejb(deployment-id=CotisationDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/CotisationDao!dao.ICotisationDaoRemote) --> Ejb(deployment-id=CotisationDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/CotisationDao) --> Ejb(deployment-id=CotisationDao)
Infos - Jndi(name=EmployeDaoLocal) --> Ejb(deployment-id=EmployeDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/EmployeDao!dao.IEmployeDaoLocal) --> Ejb(deployment-id=EmployeDao)
Infos - Jndi(name=EmployeDaoRemote) --> Ejb(deployment-id=EmployeDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/EmployeDao!dao.IEmployeDaoRemote) --> Ejb(deployment-id=EmployeDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/EmployeDao) --> Ejb(deployment-id=EmployeDao)
Infos - Jndi(name=IndemniteDaoLocal) --> Ejb(deployment-id=IndemniteDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/IndemniteDao!dao.IIndemniteDaoLocal) --> Ejb(deployment-id=IndemniteDao)
Infos - Jndi(name=IndemniteDaoRemote) --> Ejb(deployment-id=IndemniteDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/IndemniteDao!dao.IIndemniteDaoRemote) --> Ejb(deployment-id=IndemniteDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/IndemniteDao) --> Ejb(deployment-id=IndemniteDao)
Infos - existing thread singleton service in SystemInstance() org.apache.openejb.cdi.ThreadSingletonServiceImpl@624a240d
Infos - OpenWebBeans Container is starting...
Infos - Adding OpenWebBeansPlugin : [CdiPlugin]
Infos - All injection points were validated successfully.
Infos - OpenWebBeans Container has started, it took [70] ms.
Infos - Created Ejb(deployment-id=IndemniteDao, ejb-name=IndemniteDao, container=Default Stateless Container)
Infos - Created Ejb(deployment-id=EmployeDao, ejb-name=EmployeDao, container=Default Stateless Container)
Infos - Created Ejb(deployment-id=CotisationDao, ejb-name=CotisationDao, container=Default Stateless Container)
Infos - Started Ejb(deployment-id=IndemniteDao, ejb-name=IndemniteDao, container=Default Stateless Container)
Infos - Started Ejb(deployment-id=EmployeDao, ejb-name=EmployeDao, container=Default Stateless Container)
Infos - Started Ejb(deployment-id=CotisationDao, ejb-name=CotisationDao, container=Default Stateless Container)
Infos - Deployed Application(path=D:\data\istia-1112\netbeans\glassfish\mv-pam\tmp\mv-pam-openejb-eclipselink\classpath.ear)

Sobald dies erledigt ist, kann die neue Testklasse ausgeführt werden. Löschen Sie zuvor über die NetBeans-Verbindung [dbpam_eclipselink] die Tabellen aus der Datenbank [dbpam_eclipselink].

 

Überprüfen Sie mithilfe der NetBeans-Verbindung [dbpam_eclipselink], ob die Datenbank gefüllt wurde.

6.2.4. Portierung der [Business]-Schicht

Wir werden die [Business]-Schicht portieren, indem wir Pakete aus dem Projekt [mv-pam-spring-hibernate] in das Projekt [mv-pam-openejb-eclipselink] kopieren.

Die oben [1] gemeldeten Fehler sind darauf zurückzuführen, dass die kopierte [Business]-Schicht Spring verwendet und die Spring-Bibliotheken nicht mehr Teil des Projekts sind.

6.2.4.1. Die [Business]-EJB

Wir gehen genauso vor wie beim [CotisationDao]-EJB beschrieben. Zunächst erstellen wir in [2] die lokalen und Remote-Schnittstellen für das zukünftige [Metier]-EJB. Beide leiten sich von der ursprünglichen [IMetier]-Schnittstelle ab.

public class JUnitInitDBRemote {

  static private IEmployeDaoRemote employeDao = null;
  static private ICotisationDaoRemote cotisationDao = null;
  static private IIndemniteDaoRemote indemniteDao = null;

  @BeforeClass
  public static void init() throws Exception {
     // configure the embedded Open EJB container
    Properties properties = new Properties();
    properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
      // initialization of JNDI context with previous properties
    InitialContext initialContext = new InitialContext(properties);
     // instantiation of remote DAO layers
    employeDao = (IEmployeDaoRemote) initialContext.lookup("EmployeDaoRemote");
    cotisationDao = (ICotisationDaoRemote) initialContext.lookup("CotisationDaoRemote");
    indemniteDao = (IIndemniteDaoRemote) initialContext.lookup("IndemniteDaoRemote");
}
1
2
3
4
5
6
7
8
package metier;

import javax.ejb.Local;

@Local
public interface IMetierLocal extends IMetier{

}

Sobald dies erledigt ist, ändern wir in [3] die Klasse [Business] so, dass sie zu einem EJB wird:

1
2
3
4
5
6
7
8
package metier;

import javax.ejb.Remote;

@Remote
public interface IMetierRemote extends IMetier{

}
  • Zeile 1: Die Annotation @Stateless macht die Klasse zu einem EJB
  • Zeile 2: Jede Methode der Klasse wird innerhalb einer Transaktion ausgeführt
  • Zeile 3: Die [Metier]-EJB implementiert sowohl die lokale als auch die Remote-Schnittstelle, die wir gerade definiert haben
  • Zeile 7: Die [Metier]-EJB nutzt die [CotisationDao]-EJB über deren lokale Schnittstelle. Das bedeutet, dass die [Business]- und die [DAO]-Schicht in derselben JVM laufen müssen.
  • Zeile 6: Die Annotation @EJB stellt sicher, dass der EJB-Container die Referenz auf die lokale Schnittstelle des [CotisationDao]-EJBs selbst einfügt. Der andere Ansatz, auf den wir gestoßen sind, ist die Verwendung eines JNDI-Kontexts.
  • Zeilen 8–11: Der gleiche Mechanismus wird für die beiden anderen EJBs in der [DAO]-Schicht verwendet.

6.2.4.2. Testen der [Business]-Schicht

Unsere [business]-Schicht, die durch ein EJB implementiert wird, kann getestet werden. Wir beginnen damit, das [business]-Paket aus [Test Packages] im Projekt [mv-pam-spring-hibernate] in das derzeit im Aufbau befindliche Projekt [1] zu kopieren:

  • in [1], das Ergebnis des Kopiervorgangs
  • in [2] löschen wir den ersten Test
  • in [3] wird der verbleibende Test in [JUnitMetierLocal] umbenannt

Die Klasse [JUnitMetierLocal] sieht nun wie folgt aus:

@Stateless
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class Metier implements IMetierLocal, IMetierRemote {

   // references to local [DAO] layer
  @EJB
  private ICotisationDaoLocal cotisationDao = null;
  @EJB
  private IEmployeDaoLocal employeDao = null;
  @EJB
  private IIndemniteDaoLocal indemniteDao = null;

   // get your payslip
  public FeuilleSalaire calculerFeuilleSalaire(String SS,
          double nbHeuresTravaillées, int nbJoursTravaillés) {
     // retrieve employee information
...
  • Zeile 4: Verweis auf die lokale Schnittstelle des EJB [Metier]
  • Zeilen 8–12: OpenEJB-Container-Konfiguration identisch mit der im [DAO]-Schicht-Test verwendeten
  • Zeilen 15–19: Wir fordern in Zeile 12 Referenzen aus dem JNDI-Kontext auf die drei EJBs in der [DAO]-Schicht und auf die EJB in der [Business]-Schicht an. Die EJBs in der [DAO]-Schicht werden zur Initialisierung der Datenbank verwendet, und die EJB in der [Business]-Schicht wird zur Durchführung von Tests zur Gehaltsberechnung verwendet.

Die Ausführung des [JUnitMetierLocal]-Tests liefert das folgende Ergebnis [1]:

In [2] duplizieren wir [JUnitMetierLocal] als [JUnitMetierRemote], um diesmal die Remote-Schnittstelle des [Metier]-EJB zu testen. Der Code für [JUnitMetierRemote] wird so geändert, dass diese Remote-Schnittstelle verwendet wird. Der Rest bleibt unverändert.

public class JUnitMetierLocal {

// local business layer
  static private IMetierLocal metier;

  @BeforeClass
  public static void init() throws NamingException {
     // configure the embedded Open EJB container
    Properties properties = new Properties();
    properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
     // initialization of JNDI context with previous properties
    InitialContext initialContext = new InitialContext(properties);

     // instantiation of local DAO layers
    IEmployeDaoLocal employeDao = (IEmployeDaoLocal) initialContext.lookup("EmployeDaoLocal");
    ICotisationDaoLocal cotisationDao = (ICotisationDaoLocal) initialContext.lookup("CotisationDaoLocal");
    IIndemniteDaoLocal indemniteDao = (IIndemniteDaoLocal) initialContext.lookup("IndemniteDaoLocal");
     // local business layer instantiation
    metier = (IMetierLocal) initialContext.lookup("MetierLocal");

     // empty the base
...
}
  • Zeilen 4 und 19: Wir verwenden die Remote-Schnittstelle des [Business]-EJB.
  • Zeilen 15–17: Wir verwenden die Remote-Schnittstellen der [DAO]-Schicht
  • Zeilen 34–35: Da bei Remote-Schnittstellen Objekte, die zwischen Client und Server ausgetauscht werden, als Wert übergeben werden, müssen wir das von der Methode create(Indemnite i) zurückgegebene Ergebnis abrufen. Bei lokalen Schnittstellen, bei denen Objekte als Referenz übergeben werden, war dies nicht erforderlich.

Sobald dies erledigt ist, kann das Projekt kompiliert und der [JUnitMetierRemote]-Test ausgeführt werden:

  

6.2.5. Portierung der [Konsolen]-Ebene

Wir werden die [console]-Schicht portieren, indem wir Pakete aus dem Projekt [mv-pam-spring-hibernate] in das Projekt [mv-pam-openejb-eclipselink] kopieren.

Die oben [1] gemeldeten Fehler sind darauf zurückzuführen, dass die kopierte [business]-Schicht Spring verwendet und die Spring-Bibliotheken nicht mehr Teil des Projekts sind. In [2] wird die Klasse [Main] in [MainLocal] umbenannt. Sie wird die lokale Schnittstelle des [Business]-EJB verwenden.

Der Code für die [MainLocal]-Klasse ändert sich wie folgt:

public class JUnitMetierRemote {

   // remote business layer
  static private IMetierRemote metier;

  @BeforeClass
  public static void init() throws NamingException {
     // configure the embedded Open EJB container
    Properties properties = new Properties();
    properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
     // initialization of JNDI context with previous properties
    InitialContext initialContext = new InitialContext(properties);

     // instantiation of remote DAO layers
    IEmployeDaoRemote employeDao = (IEmployeDaoRemote) initialContext.lookup("EmployeDaoRemote");
    ICotisationDaoRemote cotisationDao = (ICotisationDaoRemote) initialContext.lookup("CotisationDaoRemote");
    IIndemniteDaoRemote indemniteDao = (IIndemniteDaoRemote) initialContext.lookup("IndemniteDaoRemote");
     // remote business layer instantiation
    metier = (IMetierRemote) initialContext.lookup("MetierRemote");

     // empty the base
    for(Employe employe:employeDao.findAll()){
      employeDao.destroy(employe);
    }
    for(Cotisation cotisation:cotisationDao.findAll()){
      cotisationDao.destroy(cotisation);
    }
    for(Indemnite indemnite : indemniteDao.findAll()){
      indemniteDao.destroy(indemnite);
    }
     // fill it
    Indemnite indemnite1=new Indemnite(1,1.93,2,3,12);
    Indemnite indemnite2=new Indemnite(2,2.1,2.1,3.1,15);
    indemnite1=indemniteDao.create(indemnite1);
    indemnite2=indemniteDao.create(indemnite2);
    employeDao.create(new Employe("254104940426058","Jouveinal","Marie","5 rue des oiseaux","St Corentin","49203",indemnite2));
    employeDao.create(new Employe("260124402111742","Laverti","Justine","La brûlerie","St Marcel","49014",indemnite1));
    cotisationDao.create(new Cotisation(3.49,6.15,9.39,7.88));
  }
}

Die Änderungen befinden sich in den Zeilen 13–25. Auf diese Weise erhalten wir eine Referenz auf die [business]-Schicht, die sich geändert hat (Zeilen 17–22). Wir werden den neuen Code nicht erläutern, da er bereits in früheren Beispielen behandelt wurde. Sobald diese Änderungen vorgenommen wurden, weist das Projekt keine Fehler mehr auf (siehe [3]).

Wir konfigurieren das Projekt so, dass es mit Argumenten ausgeführt wird [1]:

Damit die Konsolenanwendung normal läuft, müssen Daten in der Datenbank vorhanden sein. Dazu müssen Sie die Datei [META-INF/persistence.xml] ändern:

  public static void main(String[] args) {
     // local data
    final String syntaxe = "pg num_securite_sociale nb_heures_travaillées nb_jours_travaillés";
...
     // mistakes?
    if (erreurs.size() != 0) {
      for (int i = 0; i < erreurs.size(); i++) {
        System.err.println(erreurs.get(i));
      }
      return;
    }
     // it's OK - we can ask for the payslip at the [trade] layer
    IMetierLocal metier = null;
    FeuilleSalaire feuilleSalaire = null;
    try {
       // configure the embedded Open EJB container
      Properties properties = new Properties();
      properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
       // initialization of JNDI context with previous properties
      InitialContext initialContext = new InitialContext(properties);
       // local business layer instantiation
      metier = (IMetierLocal) initialContext.lookup("MetierLocal");
       // wage sheet calculation
      feuilleSalaire = metier.calculerFeuilleSalaire(args[0], nbHeuresTravaillées, nbJoursTravaillés);
    } catch (PamException ex) {
      System.err.println("L'erreur suivante s'est produite : " + ex.getMessage());
      return;
    } catch (Exception ex) {
      System.err.println("L'erreur suivante s'est produite : " + ex.toString());
      return;
    }
     // detailed display
    String output = "Valeurs saisies :\n";
    output += ajouteInfo("N° de sécurité sociale de l'employé", args[0]);
....

Zeile 14, die dazu führte, dass die Datenbanktabellen bei jedem Durchlauf neu erstellt wurden, ist auskommentiert. Das Projekt muss neu erstellt werden (Clean und Build), damit diese Änderung wirksam wird. Sobald dies geschehen ist, kann das Programm ausgeführt werden. Wenn alles gut geht, sieht die Konsolenausgabe in etwa wie folgt aus:


<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence              http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
  <persistence-unit name="dbpam_eclipselinkPU" transaction-type="JTA">
    <!-- the supplier JPA is EclipseLink -->
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <!-- jpa entities -->
    <class>jpa.Cotisation</class>
    <class>jpa.Employe</class>
    <class>jpa.Indemnite</class>
    <!-- properties provider EclipseLink -->
    <properties>
      <property name="eclipselink.logging.level" value="FINE"/>
      <!--
      <property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
      -->
    </properties>
  </persistence-unit>
</persistence>

Hier haben wir die lokale Schnittstelle der [Business]-Schicht verwendet. In einer zweiten Konsolenklasse verwenden wir nun deren Remote-Schnittstelle:

In [1] wurde die Klasse [MainLocal] in [MainRemote] dupliziert. Der Code in [MainRemote] wurde so geändert, dass er die Remote-Schnittstelle der [business]-Schicht verwendet:

.......
INFO - Created EJB(deployment-id=IndemniteDao, ejb-name=IndemniteDao, container=Default Stateless Container)
INFO - Deployed Application(path=classpath.ear)
[EL Info]: 2009-09-30 15:09:21.109--ServerSession(16658781)--EclipseLink, version: Eclipse Persistence Services - 1.1.2.v20090612-r4475
[EL Info]: 2009-09-30 15:09:21.937--ServerSession(16658781)--file:/C:/temp/09-09-28/pam-console-metier-dao-openejb-eclipselink-0910/build/classes/-jpa login successful
Valeurs saisies :
N° de sécurité sociale de l'employé : 254104940426058
Nombre d'heures travaillées : 150
Nombre de jours travaillés : 20

Informations Employé : 
Nom : Jouveinal
Prénom : Marie
Adresse : 5 rue des oiseaux
Ville : St Corentin
Code Postal : 49203
Indice : 2

Informations Cotisations : 
CSGRDS : 3.49 %
CSGD : 6.15 %
Retraite : 7.88 %
Sécurité sociale : 9.39 %

Informations Indemnités : 
Salaire horaire : 2.1 euro
Entretien/jour : 2.1 euro
Repas/jour : 3.1 euro
Congés Payés : 15.0 %

Informations Salaire : 
Salaire de base : 362.25 euro
Cotisations sociales : 97.48 euro
Indemnités d'entretien : 42.0 euro
Indemnités de repas : 62.0 euro
Salaire net : 368.77 euro

BUILD SUCCESSFUL (total time: 4 seconds)

Es wurden Änderungen an den Zeilen 2 und 8 vorgenommen. Das Projekt ist so konfiguriert [2], dass die Klasse [MainRemote] ausgeführt wird. Die Ausführung liefert die gleichen Ergebnisse wie zuvor.

6.3. Fazit

Wir haben gezeigt, wie eine Spring/Hibernate-Architektur auf eine OpenEJB/EclipseLink-Architektur migriert wird.

Die Spring/Hibernate-Architektur

Die OpenEJB/EclipseLink-Architektur

Der Portierungsprozess verlief reibungslos, da die ursprüngliche Anwendung in Schichten strukturiert war. Dieser Punkt ist wichtig zu verstehen.