Für diesen Artikel hab ich mir mal das EclipseLink Projekt angeschaut, und mich darin ein bisschen eingearbeitet.
Zuerst mal eine schnelle Einführung. Was ist EclipseLink?
EclipseLink ist ein Persistenzframework für Eclipse-Applikationen und kann in verschiedensten Umgebungen arbeiten. Es bietet u.a.
- Persistenz von Objekten über XML (JAXB)
- Relationale Datenbanken (JPA)
- Objektorientierte Datenbanken!
- usw
Eine wirklich sehr gute Einführung gibts unter Link 2 (s.u)
In diesem ersten Artikel beleuchten wir mal die Installation und entwickeln eine einfache CRUD Anwendung mit Eclipse und EclipseLink.
HIerzu hol ich mir zunächst mal die benötigten Libs von hier. Das sind die OSGI-Bundles, die zum Arbeiten mit EL benötigt werden. DIes sind laut der beigelegten Readme-Datei folgende Bundles:
EclipseLink Core Functionality
——————————
– org.eclipse.persistence.core
– org.eclipse.persistence.asm
– org.eclipse.persistence.antlr
– javax.activation
– javax.jms
– javax.mail
– javax.persistence
– javax.resource
– javax.transaction
– javax.xml.rpc
– javax.xml.soap
– javax.xml.stream
– org.apache.ant
EclipseLink JPA
—————
– org.eclipse.persistence.jpa
– javax.persistence (version 1.99 required for OSGI)
EclipseLink Moxy
—————-
– org.eclipse.persistence.moxy
– javax.xml.bind
EclipseLink SDO
—————
– org.eclipse.persistence.sdo
– commonj.sdo
Schon mal eine ganze Menge. Diese entpacke ich zunächst mal in ein lokales Verzeichnis, bei mir g:developmentlibseclipse_link, und füge diese meiner TargetPlattform hinzu über
Window/Preferences/Plug-In Development / Target-Plattform/Add
Gruppiert man jetzt nach Location sollte man die zusätzlich geladenen Bundles erkennen:
Ok, denke ich mir eine einfache Demoanwendung aus, dann komme ich zwangsläufig wieder mal auf meine Adressverwaltung, die Personenobjekte in einer relationalen Datenbank speichert.
Hierzu sollte ein Formular angeboten werden, welches die Bearbeitung einer Person ermöglicht, das könnte etwa wie folgt aussehen (Über die Implementierung schweige ich mich hier mal aus, da dies nicht Gegenstand des Artikels sein soll).
Die einfache Implementierung unserer Person sieht so aus (man beachte den PropertyChangeSupport, der es uns erlaubt, Databinding zu verwenden).
package de.md.eclipselink.example.domain;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
public class Person {
private String vorname;
private String nachname;
private String email;
private String telefon;
private String notiz;
private PropertyChangeSupport support;
public Person(){
this.support = new PropertyChangeSupport(this);
}
public Person(String email, String nachname, String notiz,
String telefon, String vorname) {
this();
this.email = email;
this.nachname = nachname;
this.notiz = notiz;
this.telefon = telefon;
this.vorname = vorname;
}
public String getVorname() {
return vorname;
}
public void setVorname(String vorname) {
String old = this.vorname;
this.vorname = vorname;
support.firePropertyChange(„vorname“, old, vorname);
}
public String getNachname() {
return nachname;
}
public void setNachname(String nachname) {
String old = this.telefon;
this.nachname = nachname;
support.firePropertyChange(„nachname“, old, nachname);
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
String old = this.email;
this.email = email;
support.firePropertyChange(„email“, old, email);
}
public String getTelefon() {
return telefon;
}
public void setTelefon(String telefon) {
String old = this.telefon;
this.telefon = telefon;
support.firePropertyChange(„telefon“, old, telefon);
}
public String getNotiz() {
return notiz;
}
public void setNotiz(String notiz) {
String old = this.notiz;
this.notiz = notiz;
support.firePropertyChange(„notiz“, old, notiz);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
this.support.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
this.support.removePropertyChangeListener(listener);
}
}
Weiterhin definieren wir einen Editor nebst EditorInput und 3 Commands mit Handlern für BEarbeiten, Laden und Speichern.
Die Laden und Speichern-Commands packen wir direkt zum Editor über eine Menü-Contribution.
<extension
point=“org.eclipse.ui.menus“>
<menuContribution
locationURI=“toolbar:de.md.eclipselink.example.editor“>
<toolbar
id=“de.md.eclipselink.example.editor.toolbar“>
<command
commandId=“de.md.eclipselink.example.save“
icon=“icons/alt_window_16.gif“
label=“speichern“
style=“push“>
</command>
<command
commandId=“de.md.eclipselink.example.load“
icon=“icons/alt_window_16.gif“
label=“laden“
style=“push“>
</command>
</toolbar>
</menuContribution>
</extension>
Die new-Command packen wir in das File-Menü:
<menuContribution
locationURI=“menu:file“>
<command
commandId=“de.md.eclipselink.example.new“
label=“Neu“
style=“push“>
</command>
</menuContribution>
</extension>
Das ganze könnte dann ungefähr so aussehen:
Der Code für das Binding im Editor sieht so aus:
ctx = new DataBindingContext();
Person person = ((PersonEditorInput) getEditorInput()).getPerson();
ctx.bindValue(SWTObservables.observeText(vorname, SWT.Modify), BeansObservables
.observeValue(person, „vorname“), null, null);
ctx.bindValue(SWTObservables.observeText(nachname, SWT.Modify), BeansObservables
.observeValue(person, „nachname“), null, null);
ctx.bindValue(SWTObservables.observeText(telefon, SWT.Modify), BeansObservables
.observeValue(person, „telefon“), null, null);
ctx.bindValue(SWTObservables.observeText(email, SWT.Modify), BeansObservables
.observeValue(person, „email“), null, null);
ctx.bindValue(SWTObservables.observeText(notiz, SWT.Modify), BeansObservables
.observeValue(person, „notiz“), null, null);
Laden wir jetzt mal Probeweise eine Person bei der Instantiirung des Editors im entsprechenden Bearbeiten – Handler:
@Override
public Object execute(ExecutionEvent event) throws ExecutionException {
try {
HandlerUtil
.getActiveWorkbenchWindow(event)
.getActivePage()
.openEditor(
new PersonEditorInput(new Person(„myemail@web.de“,
„mueller“, „eine wichtige NOtiz“,
„089/123455“, „heinrich“)), PersonEditor.ID);
} catch (PartInitException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
sieht das ganze so aus:
Offenbar funktionierts. Nun müssen wir die Logik für das Laden und Speichern in den entsprechenden Handlern
implementieren. Hierfür implementieren wir ein DAO.
package de.md.eclipselink.example.domain;
public interface IPersonService {
public abstract Person loadPersonByName(String name);
public abstract void savePerson(Person person);
public abstract void deletePerson(Person person);
}
Nun implementieren wir die EclipseLink Unterstützung. Hierfür gibt es hier
eine entsprechende Anleitung, die uns aber primär noch nicht weiterbringt, fangen wir mal mit kleinen
Schritten an.
Zuerst müssen wir eine dependency auf das Plugin javax.persistence definieren, damit
wir Zugriff auf die entsprechenden Annotationen haben.
Danach können wir unsere Person-Bean annotieren:
Dank Lovely JPA reicht folgendes:
@Entity
public class Person {
@Id
@GeneratedValue(strategy=GenerationType.SEQUENCE)
private Long id;
private String vorname;
private String nachname;
private String email;
private String telefon;
private String notiz;
private PropertyChangeSupport support;
public Person(){
this.support = new PropertyChangeSupport(this);
}
public Person(String email, String nachname, String notiz,
String telefon, String vorname) {
this();
this.email = email;
this.nachname = nachname;
this.notiz = notiz;
this.telefon = telefon;
this.vorname = vorname;
}
public String getVorname() {
return vorname;
}
public void setVorname(String vorname) {
String old = this.vorname;
this.vorname = vorname;
support.firePropertyChange(„vorname“, old, vorname);
}
public String getNachname() {
return nachname;
}
public void setNachname(String nachname) {
String old = this.telefon;
this.nachname = nachname;
support.firePropertyChange(„nachname“, old, nachname);
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
String old = this.email;
this.email = email;
support.firePropertyChange(„email“, old, email);
}
public String getTelefon() {
return telefon;
}
public void setTelefon(String telefon) {
String old = this.telefon;
this.telefon = telefon;
support.firePropertyChange(„telefon“, old, telefon);
}
public String getNotiz() {
return notiz;
}
public void setNotiz(String notiz) {
String old = this.notiz;
this.notiz = notiz;
support.firePropertyChange(„notiz“, old, notiz);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
this.support.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
this.support.removePropertyChangeListener(listener);
}
Die einfachst mögliche Implementierung des DAO ist:
public class PersonService implements IPersonService {
private EntityManager mgr;
private static PersonService INSTANCE = new PersonService();
private PersonService() {
mgr = Persistence.createEntityManagerFactory(„personUnit“)
.createEntityManager();
}
public static PersonService getInstance(){
return INSTANCE;
}
/*
* (non-Javadoc)
*
* @see
* de.md.eclipselink.example.domain.IPersonService#loadPersonByName(java
* .lang.String)
*/
public Person loadPersonByName(String name) {
return mgr.find(Person.class,name);
}
/*
* (non-Javadoc)
*
* @see
* de.md.eclipselink.example.domain.IPersonService#savePerson(de.md.eclipselink
* .example.domain.Person)
*/
public void savePerson(Person person) {
mgr.merge(person);
}
/*
* (non-Javadoc)
*
* @seede.md.eclipselink.example.domain.IPersonService#deletePerson(de.md.
* eclipselink.example.domain.Person)
*/
public void deletePerson(Person person) {
mgr.remove(person);
}
}
Einfacher gehts nicht. Für den Zugriff bieten wir eine statische „getInstance“ Methode an, SIngleton
lässt grüssen, normalerweise würde ich das nciht so machen, und jedem auf die Finger klopfen
der es probiert, aber „for the sake of demonstration, why not“, wie einmal ein kluger Mann gesagt hat.
Hierfür müssen wir noch unsere Persistence.xml definieren, diese könnte so aussehen:
<persistence>
<persistence-unit name=“examplePersistenceUnit“
transaction-type=“RESOURCE_LOCAL“>
<provider>org.eclipse.persistence.jpa.PersistenceProvider<provider/>
</persistence-unit>
</persistence>
Doch halt, das wichtigste fehlt uns ja noch, für Testzwecke brauchen wir eine Datenbank, wir verwenden hierzu
im ersten Schritt eine relationale Datenbank, und am einfachsten die HSQLDB und betreiben diese InMemory.
Hierzu erst mal das entsprechende Jar von hier runterladen und in den Classpath aufnehmen.
Da dies hier nur zu Demozwecken passieren soll, ist es auf jedenfall in ORdnung , alles in ein Plug-In
zu packen.
Um EclipseLink mit HSQL betreiben zu können, erweitern wir die Persistence.xml um folgende Einträge:
<persistence>
<persistence-unit name=“examplePersistenceUnit“
transaction-type=“RESOURCE_LOCAL“>
<provider>org.eclipse.persistence.jpa.PersistenceProvider<provider/>
<properties>
<property name=„eclipselink.target-database“ value=„HSQL“/>
<property name=„eclipselink.ddl-generation“ value=„drop-and-create-tables“/>
<property name=„eclipselink.ddl-generation.output-mode“ value=„database“/>
<property name=„eclipselink.logging.level“ value=„FINEST“/>
</properties>
</persistence-unit>
</persistence>
Damit haben können wir die HSQL ansprechen, ausserdem werden unsere Tables jedesmal neu generiert.
Starten ich aber jetzt einfach mal die Anwendung, sehe ich folgendes:
Exception Description: An attempt has been made to use PersistenceUnit [personUnit],
but no bundle is available that defines that persistence unit.
Was ist passiert? Scheinbar ist alles an Ort und Stelle…
1. persistence.xml im Ordner „META-INF“
2. Attribute korrekt gemappt (behaupte ich jetzt mal)
Trotzdem kann scheinbar die Konfiguration nicht geladen werden. Wirft man mal einen Blick in die Klasse
org.eclipse.persistence.jpa.osgi.PersistenceProvider – übrigens die Klasse, die über den
Aufruf Persistence.createEntityManagerFactory… angesprochen wird, dann sieht man das hier
nach einem Parameter der folgenden Art gesucht wird:
PersistenceUnitProperties.CLASSLOADER
Ich setzt mal voraus, das jeder der sich schon ein wenig mit der Materie auseinandergesetzt hat, darüber
Bescheid weiß, das innerhalb OSGi, jedes Bundle seinen eigenen Classloader besitzt.
Scheinbar muss hier der Classloader zum Laden der persistence.xml gesetzt werden.
Versuchen wir das doch einfach mal, da die Datei persistence.xml im einzigen Plug-In unserer Anwendung liegt,
versuchen wir einfach mal, den Classloader der PersonService-Klasse zu setzen (da dieser Classloader
dem BundleClassLoader entspricht).
(Um Zugriff auf die Konstanten zu haben, muss das entspechende Package importiert werden)
Map map = new HashMap();
map.put(PersistenceUnitProperties.CLASSLOADER, Person.class
.getClassLoader());
Gleicher Aufruf:
Caused by: javax.persistence.PersistenceException:
No Persistence provider for EntityManager named personUnit
Verdammt! Scheinbar kann die persistence.xml noch immer nicht geladen werden.
Versuchen wir direkt die Implementierung und ignorieren die API:
org.eclipse.persistence.jpa.osgi.PersistenceProvider p =
new org.eclipse.persistence.jpa.osgi.PersistenceProvider();
Map map = new HashMap();
map.put(PersistenceUnitProperties.CLASSLOADER, Person.class
.getClassLoader());
EntityManagerFactory fac = p.createEntityManagerFactory(„personUnit“,
map);
EntityManager manager = fac.createEntityManager();
Und?[EL Warning]: 2008.11.21 21:57:09.046–ServerSession(1535180)–Thread(Thread[main,6,main])–java.sql.SQLException:
No suitable driver found for jdbc:hsqldb:mem:personDb
Aha!! Scheinbar wurde der EntityManager erzeugt, und es wurde versucht, auf die HSQL-Datenbank zuzugreifen.
Klingt schonmal besser, scheinbar wurde der Treiber nicht geladen.
Ok, soweit so gut, was jetzt ein Problem sein dürfte ist, das der entsprechende JDBC-Treiber nicht geladen wurde, was aber bekannt ist, ist das seit Java6 kein manuelles Laden von JDBC-Treibern mehr nötig ist, sondern dies wird automatisch über den ConnectionManager gehandhabt.
Zur leichteren Nachvollziehbarkeit machen wir jetzt folgendes:
Statt die DB direkt in-Memory zu erzeugen speichern wir die Daten lokal auf der Festplatte. Hierfür ändern wir den ConnectionString folgendermassen ab:
jdbc:hsqldb:file:d:/myperson/person“
Scheinbar hat EclipseLink auch ein Problem, wenn PersitentEntities über Plug-Ins verteilt sind (was ich ziemlich komisch finde, da dies ja genau einer DER Knackpunkte in Bezug auf OSGi ist, und das dies eigentlich ein Grund ist, das EclipseLink überhaupt eine Daseinsberechtigung hat.
Um das zu testen, erstelle ich ein zweites Plug-In, welches nichts anderes bietet als ein package „de.md.eclipselink.domain.ext“ und hierin eine Klasse „Car“, die einer bestimmten Person zugeordnet werden kann. Hierfür bekommt die Person-Klasse ein Attribut „Car“, welches gemappt wird mit „@OneToOne“.
@OneToOne
private Car auto;
Starten wir die Anwendung und versuchen eine Person zu laden bekommen wir wie erwartet:
Caused by: javax.persistence.PersistenceException: Exception [EclipseLink-28018] (Eclipse Persistence Services – 1.0.2 (Build 20081024)): org.eclipse.persistence.exceptions.EntityManagerSetupException
Klar, wir haben Car ja noch nicht als Entity markiert. Das machen wir gleich mal in der persistence.xml
<persistence-unit name=“personUnit“ transaction-type=“RESOURCE_LOCAL“>
<provider>org.eclipse.persistence.jpa.PersistenceProvider
</provider>
<class>de.md.eclipselink.example.domain.Person
</class>
<class>de.md.eclipselink.example.domain.ext.Car</class>
<properties>
<property name=“eclipselink.target-database“ value=“HSQL“ />
<property name=“eclipselink.ddl-generation“ value=“drop-and-create-tables“ />
<property name=“eclipselink.ddl-generation.output-mode“
value=“database“ />
<property name=“eclipselink.logging.level“ value=“FINEST“ />
<property name=“eclipselink.jdbc.driver“ value=“org.hsqldb.jdbcDriver“ />
<property name=“eclipselink.jdbc.url“ value=“jdbc:hsqldb:file:d:/myperson/person“ />
<property name=“eclipselink.jdbc.user“ value=“sa“ />
<property name=“eclipselink.jdbc.password“ value=““ />
</properties>
</persistence-unit>
Starten wir jetzt die Anwendung und speichern eine Person und überprüfen hinterher die Logs der HSQl sieht man folgendes:
INSERT INTO CAR VALUES(2,’rv-md‘,’audi‘,’pkw‘)
D.h. eine Entity wurde automatisch aus einem anderen Plug-In geladen und gespeichert. Dies würde mit beispielsweise Hibernate als PersistenceProvider nicht funktionieren (Hier würde man stattdessen eine Buddy-Policy definieren um den Classpath zu erweitern).
D.h. die Gerüchte, die in den Foren umtreiben scheinen sich nicht zu bewahrheiten und EclipseLink scheint durchaus in der Lage, Entities aus beliebigen Plug-Ins zu persistieren.
Ok, bauen wir mal eine einfach e Laden funktion ein , wir definieren im package „de.md.eclipselink.example.commands“ die Klasse „LoadHandler“ mit folgendem Codefragment:
public class LoadHandler extends AbstractHandler {
@Override
public Object execute(ExecutionEvent event) throws ExecutionException {
String name = event.getParameter(„name“);
Person person = PersonService.getInstance().loadPersonByName(name);
if (person != null) {
try {
HandlerUtil.getActiveWorkbenchWindow(event).getActivePage()
.openEditor(new PersonEditorInput(person),
PersonEditor.ID);
} catch (PartInitException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return null;
}
}
Hierfür brauchen wir ein Parametrisiertes Command.
Wie das funktioniert erkläre ich mal im schnellgang, denn zum Einen ist es heute bereits sehr spät
zum Anderen will ich darüber irgendwann einen eigenen Blogeintrag machen.
Für das Command zum Laden einer Person definieren wir folgendes:
<command
categoryId=“de.md.eclipselink.example“
id=“de.md.eclipselink.example.load“
name=“load“>
<commandParameter
id=“de.md.eclipselink.example.personName“
name=“personName“
optional=“true“>
<values
class=“de.md.eclipselink.example.commands.LoadCommandParameter“>
</values>
</commandParameter>
</command>
Wir definieren also einen Parameter für unser Command, wie aber rufe ich jetzt einen solchen Parameter auf..
Die Dokumentation hierzu ist denkbar schlecht und ich habe echt lange gebraucht, das Ding
zum LAufen zu kriegen. Im Prinzip funktioniert das ganze innerhalb des Handlers so:
public Object execute(ExecutionEvent event) throws ExecutionException {
String name = event.getParameter(„name“);
Person person = PersonService.getInstance().loadPersonByName(name);
…
Wie aber kommt der Name in das ExecutionEvent?
IHandlerService service = (IHandlerService) getSite()
.getService(IHandlerService.class);
ICommandService cS = (ICommandService) getSite()
.getService(ICommandService.class);
try {
Command com = cS.getCommand(„de.md.eclipselink.example.load“);
IParameter t = com.getParameter(„de.md.eclipselink.example.personName“);
Parameterization par = new Parameterization(t,“franz mueller“);
ParameterizedCommand pc = new ParameterizedCommand(com,new Parameterization[]{par});
service.executeCommand(pc, null);
} catch (Exception e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
Links:
http://wiki.eclipse.org/Packaging_and_Deploying_EclipseLink_JPA_Applications_(ELUG)#How_to_Specify_the_Persistence_Unit_Name
http://wiki.eclipse.org/Introduction_to_EclipseLink_Application_Development_(ELUG)
http://wiki.eclipse.org/Introduction_to_Data_Access_(ELUG)#External_Connection_Pools
http://wiki.eclipse.org/Introduction_to_EclipseLink_Transactions_(ELUG)
http://wiki.eclipse.org/Introduction_to_EclipseLink_JPA_%28ELUG%29
http://wiki.eclipse.org/Using_EclipseLink_JPA_Extensions_(ELUG)
http://www.weheartcode.com/2008/08/27/eclipselink-in-j2se-rcp-applications/
http://www.jughh.org/download/attachments/1605843/EclipseLink.pdf?version=1