Also dann will ich mal loslegen:
Vorbereitung
Zuerst einmal solltest du dir den neuesten JBoss Application Server (Version 4.2.1GA)
herunterladen.
Ich verwende immer ein Standard-Eclipse bzw. ein Eclipse, dass zwar gewisse Plugins mit sich bringt, von denen ich aber selten welche nutze. Zumindest nutze ich die Plugins nicht zur Generierung von Beans.
Ich nutze im Eclipse immer eine bestimmte Verzeichnis-Struktur:
- als source-folder benutze ich "src/main"
- als output folder für eclipse benutze ich "output/classes-eclipse"
- als library-folder benutze ich "lib"
Zudem wende ich die folgende Package-Struktur an, um Server und Client logisch von einander zu kapseln:
- client: Hier kommen alle Klassen rein, die nur vom Client verwendet werden
- server: Hier kommen nur die Klassen rein, die vom Server verwendet werden, also die Beans sowie die LocalInterfaces, da die nur server-seitig anzusprechen sind
- commons: Hier kommen die Klassen rein, die vom Client und vom Server instantiiert werden können. Zum Beispiel das RemoteInterface oder auch das ValueObjekt (sehen wir später noch genauer)
Nun zu dem wesentlichen Part:
Session-Bean
Ich nutze immer ein Interface, in dem die Methoden definiert sind, die später in der SessionBean implementiert werden:
Verwaltung.java
Java:
package tutorials.ejb3.common;
import java.util.List;
public interface Verwaltung
{
public void insertPerson(String name);
public void removePerson(int id);
public Person findPersonById(int id);
public List<Person> findPersonByName(String name);
public List<Person> findAll();
}
Von dieser SessionBean erben die beiden Interfaces VerwaltungLocal und VerwaltungRemote
VerwaltungRemote.java
Java:
package tutorials.ejb3.common;
public interface VerwaltungRemote extends Verwaltung
{
public static final String JNDI_NAME = "VerwaltungBean/remote";
}
VerwaltungLocal.java
Java:
package tutorials.ejb3.server;
import tutorials.ejb3.common.Verwaltung;
public interface VerwaltungLocal extends Verwaltung
{
}
Das mache ich aus dem Grund, um den Code für die zu implementierenden Klassen nur einmal zu schreiben. Zu dem ist der Unterschied zwischen Local und Remote nur der, dass das Remote-Interface den JNDI-Namen preis gibt unter dem ich die Bean laden kann.
VerwaltungBean.java
Java:
package tutorials.ejb3.server;
import java.util.ArrayList;
import java.util.List;
import javax.ejb.Local;
import javax.ejb.Remote;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import tutorials.ejb3.common.Person;
import tutorials.ejb3.common.Verwaltung;
import tutorials.ejb3.common.VerwaltungRemote;
/*
* @Stateless gibt an, dass es sich um eine Stateless Session Bean handelt
* @Remote gibt das Remote-Interface an, auf das der Client zugreift
* @Local gibt das Local-Interface an, den der Server intern verwendet
*/
@Stateless
@Remote(VerwaltungRemote.class)
@Local(VerwaltungLocal.class)
public class VerwaltungBean implements Verwaltung
{
@PersistenceContext(unitName = "PPC")
private EntityManager manager;
public void insertPerson(String name)
{
PersonBean person = new PersonBean();
person.setName(name);
}
public void removePerson(int id)
{
PersonBean person = manager.find(PersonBean.class, id);
manager.remove(person);
}
public Person findPersonById(int id)
{
PersonBean bean = this.manager.find(PersonBean.class, id);
Person person = new Person();
person.setId(bean.getId());
person.setName(bean.getName());
return person;
}
public List<Person> findPersonByName(String name)
{
Query query = manager.createNamedQuery("findAllPersonsByName");
query.setParameter("name", name);
@SuppressWarnings("unchecked")
List<PersonBean> beans = query.getResultList();
List<Person> personen = new ArrayList<Person>();
for (PersonBean pb : beans)
{
Person person = new Person();
person.setId(pb.getId());
person.setName(pb.getName());
personen.add(person);
}
return personen;
}
public List<Person> findAll()
{
Query query = manager.createNamedQuery("findAllPersons");
@SuppressWarnings("unchecked")
List<PersonBean> beans = query.getResultList();
List<Person> personen = new ArrayList<Person>();
for (PersonBean pb : beans)
{
Person person = new Person();
person.setId(pb.getId());
person.setName(pb.getName());
personen.add(person);
}
return personen;
}
}
Die Annotation @Stateless gibt an, dass es sich bei der Bean um eine StatelessSessionBean handelt. Die beiden Annotations @Remote und @Local zeigen auf die Interfaces für den Remote- und den lokalen Zugriff. Diese Bean ist nun von einem Client per JNDI-lookup ansprechbar.
Java:
@PersistenceContext(unitName = "PPC")
Der PersistenceContext wird über die persistence.xml festgelegt, in der die DataSource angegeben wird, auf die ich im JBoss zugreifen möchte.
Code:
<?xml version="1.0" encoding="UTF-8"?>
<persistence>
<persistence-unit name="PPC">
<jta-data-source>java:/DefaultDS</jta-data-source>
<properties>
<property name="hibernate.hbm2ddl.auto" value="update"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/>
</properties>
</persistence-unit>
</persistence>
Der EntityManager verwaltet die Entitäten und hat nützliche Methoden um Entitys zu laden.
Java:
EntityManager.find(Class<PersonBean> arg0, Object arg1);
EntityManager.createNamedQuery(String arg0);
find() erfordert zwei Parameter:
- die Entität die geladen werden soll. hier: PersonBean.class
- und den PrimaryKey in unserem Fall die "id"
createNamedQuery() ruft eine NamedQuery auf, die wir per Annotation festlegen. Beispiel dazu folgt unter dem Punkt "Entity-Bean".
Entity-Bean
Die Entity-Bean kann ebenfalls mit Annotations stark vereinfacht werden:
PersonBean.java
Java:
package tutorials.ejb3.server;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
@Entity
@Table(name = "Person")
@NamedQueries( { @NamedQuery(name = "findAllPersons", query = "from PersonBean"),
@NamedQuery(name = "findAllPersonsByName", query = "from PersonBean where name = ?") })
public class PersonBean
{
private int id;
private String name;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
public int getId()
{
return id;
}
public void setId(int newId)
{
this.id = newId;
}
public String getName()
{
return this.name;
}
public void setName(String newName)
{
this.name = newName;
}
}
Die Annotation @Entity gibt an das diese Bean eine Entity-Bean ist. Die Annotation @Table gibt an welche Tabelle in der Datenbank genutzt werden soll. Der Name der Bean wird somit auf die Tabelle gemapped. Gibt man die Annotation nicht an, wird der Name der Bean genommen.
Mit der Annotation @NamedQueries können verschiedene @NamedQuery-Annotations vereint werden.
Eine @NamedQuery hat den einen Namen und die Abfrage (query) die sie auf DB-Level ausführt.
Java:
@NamedQuery(name = "findAllPersons", query = "from PersonBean")
Diese Query fragt in der Tabelle die auf die PersonBean gemapped ist alle Daten ab. Also wie
Java:
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
Diese beiden Annotations legen zum einen das ID-Feld (PrimaryKey) und den Typ der Generierung in der Datenbank fest. Also in dise mFall eine automatische Generierung der ID in der DB.
ValueObject
Ich habe zu dem auch ein Value-Objekt benutzt, da die Trennung zwischen Client und Server erhalten bleiben muss und ich nicht die Entität an den Client schicke sondern ein Objekt, dass die Daten der Entität zwischenspeichert und an den Client gesendet wird.
Die Klasse Person speichert wie die Entity-Bean die id und den Namen und kann vom Client verarbeitet werden, da die Entität im Server liegt und nichts auf der Client-Seite zu suchen hat.
Person.java
Java:
package tutorials.ejb3.common;
import java.io.Serializable;
public class Person implements Serializable
{
private int id;
private String name;
public void setName(String newName)
{
name = newName;
}
public String getName()
{
return name;
}
public int getId()
{
return id;
}
public void setId(int newId)
{
id = newId;
}
}
Client
Der Client macht den lookUp im JNDI des JBoss-Servers und holt sich das VerwaltungBean. Dadurch kann er nun Perosnen laden und löschen.
Java:
package tutorials.ejb3.client;
import java.util.List;
import java.util.Properties;
import javax.naming.Context;
import javax.naming.InitialContext;
import tutorials.ejb3.common.Person;
import tutorials.ejb3.common.Verwaltung;
public class Client
{
public static void main(String[] args)
{
Properties props = new Properties();
props.put(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory");
props.put(Context.PROVIDER_URL, "jnp://localhost:1099");
props.put(Context.URL_PKG_PREFIXES, "org.jboss.naming:org.jnp.interfaces");
try
{
InitialContext ctx = new InitialContext(props);
Verwaltung verwaltung = (Verwaltung) ctx.lookup("VerwaltungBean/remote");
verwaltung.insertPerson("Hans");
verwaltung.insertPerson("Franz");
verwaltung.insertPerson("Klaus");
List<Person> list = verwaltung.findAll();
for (Person person : list)
{
System.out.println(person.getName());
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
Auf der Konsole sollte dann die angelegten Personen ausgegeben werden.
Kontrolle
Neben den Ausgaben des Clients kann man auch noch mal den DatabaseManager starten.
Dazu suche in der
jmx-console nach dem Eintrag
"database=localDB,service=Hypersonic" und invoke "startDatabaseManager" drücken.
Darauf öffent sich der Hypersonic-DBManager worin man normale SQL-Statements absetzen kann.
Gruß
BlackMagician