Beispiel: Einfache Container Managed Entity Bean


Inhalt:

Anlegen der Enterprise Application
Anlegen der Entity Bean
persistence.xml
Logging der SQL-Parameter
Anlegen der Session Bean
Application Client
Datenbank
Ausführen des Clients
Ohne Annotations
Manueller Primary Key
Troubleshooting

Beispiel für eine Container Managed Entity Bean, auf die per Applicationclient zugegriffen wird.
Hier gibt es das Projekt zum Download (dies ist ein EAR-Export, die Importanleitung findet man im Stateless-Beispiel): KuchenSimple.ear

Aufbau des Beispieles


a) Entity Bean-Klasse
b) Zugriff auf die Entity-Bean erfolgt über eine Stateless Session Bean.
c) Ein Application Client greift auf die Session Bean zu.


Anlegen der Enterprise Application

Ein leeres "EAR Application Project" mit dem Namen "KuchenSimple" erstellen. Dabei die Option "Generate Deployment Descriptor" setzen.

Zu erzeugende Module zufügen. Dieses Beispiel benötigt ein EJB-Projekt und ein Anwendungsclientprojekt. Dabei jeweils die Option "Generate Deployment Descriptor" setzen.


Anlegen der Entity Bean

Wir fügen eine Klasse "KuchenSimpleBean" zu. Der Namenszusatz "Simple" kommt daher dass es noch weitere Beispiele kommen in denen Kuchen-Entity-Beans enthalten sind. Deshalb muss in jedem Beispiel ein eindeutiger Name für das Objekt "Kuchen" vergeben sein damit es keine Konflikte beim JNDI-Namen oder bei Tabellennamen gibt.


Es wird eine neue Klasse "KuchenSimpleBean" im Package "de.fhw.swtvertiefung.knauf.kuchen" angelegt. Wichtig ist dass diese Klasse das Interface "java.io.Serializable" implementiert !
Neue Entity Bean (1)

Die Bean-Klasse bekommt die Annotation "@javax.persistence.Entity". Da wir die EJB-QL-Strings zum Finden der Instanzen nicht hartcodiert in der Session-Bean haben wollen deklarieren wir bei der Bean-Klasse eine "@javax.persistence.NamedQuery".
@NamedQuery (name="findAllKuchen", query="select o from KuchenSimpleBean o")
@Entity()
public class KuchenSimple implements Serializable
{
Anmerkung 1:
Wenn mehr als eine Query nötig sind muss man diese in eine Annotation "@NamedQueries" packen:
@NamedQueries({
  @NamedQuery (name="findAllKuchen", query="select o from KuchenSimpleBean o"),
  @NamedQuery (name="findByName", query="select o from KuchenSimpleBean o where o.name like ?1")
  })
Anmerkung 2:
In EJB3 RC8 hätte die Query noch kürzer dargestellt werden können (so steht es auch im EJB3-Buch von Monson-Haefel/Bill Burke):
@NamedQuery (name="findAllKuchen", query="from KuchenSimpleBean") 
In EJB3 RC9 (ist in JBoss 4.2 enthalten) führt dies allerdings zu einer Fehlermeldung und ist wohl FALSCH.

Zwei Felder sowie die zugehörigen Getter und Setter werden zugefügt:
  private Integer intId;
  private String strName;
  
  @Column()
  @Id ()
  @GeneratedValue () 
  public Integer getId()
  {
    return this.intId;
  }

  public void setId(Integer int_Id)
  {
    this.intId = int_Id;
  }
  
  @Column()
  public String getName()
  {
    return this.strName;
  }

  public void setName(String str_Name)
  {
    this.strName = str_Name;
  } 
Die Properties werden mit der Annotation "@javax.persistence.Column" als persistente Bean-Felder markiert. Die Property "ID" wird mit der Annotation "@javax.persistence.Id" als Primary-Key-Feld markiert. Der Wert soll vom Container automatisch generiert werden ("@javax.persistence.GeneratedValue"), wobei hier als Default-Wert eine "Sequence" für die ID-Generierung verwendet werden soll.


persistence.xml

Für Entity Beans muss eine Persistence Unit deklariert werden. Dies geschieht über eine Datei "persistence.xml" im Unterverzeichnis "META-INF" des EJB-Projekts.
Sie hat diesen Inhalt:
	<?xml version="1.0" encoding="UTF-8"?>
	<persistence 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"
		version="1.0">
		<persistence-unit name="kuchenPersistenceUnit">
			<jta-data-source>java:/DefaultDS</jta-data-source>
			<properties>
				<!-- Setzen dieser Property aktiviert das automatische Tabellen-Generieren und Löschen beim Deploy! -->
				<property name="hibernate.hbm2ddl.auto" value="create-drop" />
				<!-- SQL-Logging einschalten: -->
				<property name="hibernate.show_sql" value="true"></property>
			</properties>
		</persistence-unit>
	</persistence> 
Es wird eine "persistence-unit" namens "kuchenPersistenceUnit" deklariert. Sie ist verbunden mit einer JDBC-Datenquelle des Servers die im JNDI unter dem Namen "java:/DefaultDS" abgelegt ist und auf die JBoss-interne Hypersonic-Datenbank zeigt.
DefaultDS

Die Property "hibernate.hbm2ddl.auto" ist JBoss-spezifisch und legt fest dass Datenbanktabelle beim Deploy einer Bean erzeugt und beim Undeploy wieder gelöscht werden sollen. Ohne diesen Parameter müssten wir die Datenbanktabellen von Hand anlegen.
Die Property "hibernate.show_sql" gibt an dass SQL-Befehle ins Server-Log geschrieben werden sollen, und als netter Nebeneffekt auch auf die Server-Console in Eclipse. Damit haben wir eine gute Diagnosemöglichkeit falls Datenbankzugriffe Probleme machen.


Logging der SQL-Parameter

Leider aktiviert die Property "hibernate.show_sql" in persistence.xml nur das Logging der SQL-Statements. Die Parameter der dabei benutzten Prepared Statements werden leider nicht ausgegeben. Wir erhalten nur solche Ausgaben:
insert into KuchenSimpleBean (id, name) values (null, ?)

Es gibt allerdings eine Lösung: mittels Log4J können wir die Ausgabe der Parameter konfigurieren.
Dies geht so: (Quelle:
http://www.javalobby.org/java/forums/t44119.html und http://www.hibernate.org/hib_docs/reference/en/html/session-configuration.html#configuration-logging).

In der Datei "\server\default\conf\jboss-log4j.xml" wird folgendes eingetragen (ACHTUNG: Datei als UTF-8 speichern !):
Das Ergebnis eines Inserts sieht jetzt so aus:
21:37:51,593 INFO  [STDOUT] Hibernate: insert into KuchenBean (id, name) values (null, ?)
21:37:51,593 TRACE [StringType] binding 'Mohnkuchen' to parameter: 1

Nachteil: jetzt werden auch bei einem Select alle zurückgelieferten Spalten ausgegeben:
21:37:51,593 INFO  [STDOUT] Hibernate: select kuchenbean0_.id as id0_, kuchenbean0_.name as name0_ from KuchenBean kuchenbean0_
21:37:51,593 TRACE [IntegerType] returning '2' as column: id0_
21:37:51,593 TRACE [StringType] returning 'Käsekuchen' as column: name0_
21:37:51,593 TRACE [IntegerType] returning '3' as column: id0_
21:37:51,593 TRACE [StringType] returning 'Mohnkuchen' as column: name0_

Hier gibt es eine modifizierte jboss-log4j.xml zum Download.


Anlegen der Session Bean

Da der Entity-Manager für den Zugriff auf die Entity-Bean nicht in einem Application Client verwendet werden kann müssen wir alle Zugriffe auf die Bean kapseln. Dazu verwenden wir eine Session Bean "KuchenWorkerBean".
Zuerst einmal legen wir das Remote Interface "KuchenWorker" an:
KuchenWorker
Das Interface wird als "Remote" markiert:
@Remote
public interface KuchenWorker
{ 
Es erhält drei Methoden:
  public void saveKuchen (KuchenSimpleBean kuchen);
  
  public void deleteKuchen (KuchenSimpleBean kuchen);
  
  public List getKuchen(); 

Jetzt legen wir die Bean-Klasse "KuchenWorkerBean" an. Wichtig ist dass die Bean das Interface "KuchenWorker" implementiert !
KuchenWorkerBean
Wir markieren die Bean-Klasse "KuchenWorkerBean" als "Stateless":
@Stateless
public class KuchenWorkerBean implements KuchenWorker 
{ 
Der Entity-Manager für den Zugriff auf die Entity Bean wird als vom Container "injected" Variable deklariert:
  @PersistenceContext(unitName="kuchenPersistenceUnit")
  EntityManager entityManager = null;
Die Implementierung von "saveKuchen" sieht so aus:
    this.entityManager.merge(kuchen);
Wichtig ist dass hier "merge" und nicht "persist" genommen wird, siehe Abschnitt
Troubleshooting.

"deleteKuchen":
    kuchen = this.entityManager.find (KuchenSimpleBean.class, kuchen.getId() );
    this.entityManager.remove(kuchen); 
Wichtig ist dass das zu löschende Objekt eventuell "detached" ist und deshalb vorher unter Container-Verwaltung gestellt werden muss, siehe Abschnitt Troubleshooting.

"getKuchen": Diese Methode verwendet die NamedQuery die wir in der Entity-Bean deklariert haben:
    Query query = this.entityManager.createNamedQuery("findAllKuchen");
    List<KuchenSimpleBean> listKuchen = query.getResultList();
    return listKuchen; 
Anmerkung: wir hätten die Query hier auch direkt erzeugen können.

Query query = this.entityManager.createQuery("select o from KuchenSimpleBean o");

Dadurch hätten wir allerdings eine Abhängigkeit vom Namen "KuchenSimpleBean" gebaut die weit entfernt von der eigentlichen Klasse ist!

Anmerkung: die Zuweisung der ResultList an "List<KuchenSimpleBean>" führt zu einer Compilerwarnung (Type safety: The expression of type List needs unchecked conversion to conform to List<KuchenSimpleBean>). Dies könnten wir umgehen indem wir vor die entsprechende Zeile folgende Annotation eintragen:
    @SuppressWarnings("unchecked")
    List<KuchenSimpleBean> listKuchen = query.getResultList(); 


Application Client

Zuallerest einmal werfen die die Klasse "Main" im Default-Package weg.

Der Client muss die EJB-Jars referenzieren (siehe dazu Anleitung in den vorherigen Beispielen).

Die GUI wurde mit dem Eclipse-Plugin "Visual Edit" erzeugt, der allerdings scheinbar im aktuellen Eclipse-Zweig nicht mit voller Kraft weiterentwickelt wird. (
http://www.eclipse.org/vep). Deshalb können wir die GUI hier nicht weiter bearbeiten, sondern müssen sie als gegeben hinnehmen, bis der Plugin irgendwann mal wieder up-to-date ist.

Folgende GUI-Elemente sind vorhanden:
-de.fhw.swtvertiefung.knauf.kuchen.FrameKuchen ist das Hauptfenster der Anwendung. Er besteht aus einer JList auf einem JScrollPane und drei Buttons "Neu", "Bearbeiten" und "Löschen".
-Ein JDialog "KuchenDialog" zum Bearbeiten eines Kuchens (Textfeld für den Namen und OK/Abbrechen-Buttons)

Die Main-Methode soll in einer Klasse "KuchenApplicationClient" liegen, die den FrameKuchen erzeugt und anzeigt.

Die EJB-Referenzen müssen im Deployment-Deskriptor des Clients deklariert werden. Dazu die Datei "application-client.xml" öffnen und folgendes einfügen:
	<ejb-ref>
		<ejb-ref-name>ejb/KuchenWorker</ejb-ref-name>
		<ejb-ref-type>Session</ejb-ref-type>
		<home>java.lang.Object</home>
		<remote>de.fhw.swtvertiefung.knauf.kuchen.KuchenWorker</remote>
	</ejb-ref> 
Auch hier müssen wir wieder ein sinnloses "home"-Tag einfügen.
Damit die JNDI-Referenz funktioniert müssen wir außerdem den JBoss-spezifischen Deployment-Deskriptor "jboss-client.xml" anlegen:
	<?xml version="1.0" encoding="UTF-8"?>
	<!DOCTYPE jboss-client PUBLIC "-//JBoss//DTD Application Client 4.0//EN" "http://www.jboss.org/j2ee/dtd/jboss-client_4_0.dtd" >
	<jboss-client>
		<jndi-name>KuchenSimpleClient</jndi-name>
		<ejb-ref>
			<ejb-ref-name>ejb/KuchenWorker</ejb-ref-name>
			<jndi-name>KuchenSimple/KuchenWorkerBean/remote</jndi-name>
		</ejb-ref>
	</jboss-client> 

Der Code für das JNDI-Lookup sieht so aus (im Konstruktor von "FrameKuchen"):
      Hashtable props = new Hashtable();
      props.put(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory");
      props.put(Context.URL_PKG_PREFIXES, "org.jboss.naming.client");
      props.put(Context.PROVIDER_URL, "jnp://localhost:1099");
      props.put("j2ee.clientName", "KuchenSimpleClient");

      InitialContext initialContext = new InitialContext(props);
      
      Object objKuchen = initialContext.lookup("java:comp/env/ejb/KuchenWorker");
      this.kuchenWorker = (KuchenWorker) PortableRemoteObject.narrow(objKuchen, KuchenWorker.class); 
Wichtig: die Property j2ee.clientName muss den Namen des Projekts haben !

Jetzt noch die Main class in "MANIFEST.MF" eintragen:
	Manifest-Version: 1.0
	Class-Path: KuchenSimpleEJB.jar
	Main-Class: de.fhw.swtvertiefung.knauf.kuchen.KuchenApplicationClient


Jetzt können wir die Bean auf den Server stellen. Dieser Prozess erzeugt die Datenbanktabelle. Im Server-Log finden sich unter anderem diese Einträge, an der wir erkennen können wie die Tabelle erzeugt wird und mit welchem Statements ein Einfügen oder Löschen erfolgt (diese werden scheinbar beim Deploy vorbereitet):
2006-09-13 20:28:24,437 DEBUG [org.hibernate.persister.entity.AbstractEntityPersister] Static SQL for entity: de.fhw.swtvertiefung.knauf.kuchen.KuchenSimpleBean
2006-09-13 20:28:24,437 DEBUG [org.hibernate.persister.entity.AbstractEntityPersister]  Version select: select id from KuchenSimpleBean where id =?
2006-09-13 20:28:24,437 DEBUG [org.hibernate.persister.entity.AbstractEntityPersister]  Snapshot select: select kuchensimp_.id, kuchensimp_.name as name0_ from KuchenSimpleBean kuchensimp_ where kuchensimp_.id=?
2006-09-13 20:28:24,437 DEBUG [org.hibernate.persister.entity.AbstractEntityPersister]  Insert 0: insert into KuchenSimpleBean (name, id) values (?, ?)
2006-09-13 20:28:24,437 DEBUG [org.hibernate.persister.entity.AbstractEntityPersister]  Update 0: update KuchenSimpleBean set name=? where id=?
2006-09-13 20:28:24,437 DEBUG [org.hibernate.persister.entity.AbstractEntityPersister]  Delete 0: delete from KuchenSimpleBean where id=?
2006-09-13 20:28:24,437 DEBUG [org.hibernate.persister.entity.AbstractEntityPersister]  Identity insert: insert into KuchenSimpleBean (id, name) values (null, ?)
2006-09-13 20:28:24,453 DEBUG [org.hibernate.loader.entity.EntityLoader] Static select for entity de.fhw.swtvertiefung.knauf.kuchen.KuchenSimpleBean: select kuchensimp0_.id as id0_0_, kuchensimp0_.name as name0_0_ from KuchenSimpleBean kuchensimp0_ where kuchensimp0_.id=?
2006-09-13 20:28:24,453 DEBUG [org.hibernate.loader.entity.EntityLoader] Static select for entity de.fhw.swtvertiefung.knauf.kuchen.KuchenSimpleBean: select kuchensimp0_.id as id0_0_, kuchensimp0_.name as name0_0_ from KuchenSimpleBean kuchensimp0_ where kuchensimp0_.id=?
2006-09-13 20:28:24,453 DEBUG [org.hibernate.loader.entity.EntityLoader] Static select for entity de.fhw.swtvertiefung.knauf.kuchen.KuchenSimpleBean: select kuchensimp0_.id as id0_0_, kuchensimp0_.name as name0_0_ from KuchenSimpleBean kuchensimp0_ where kuchensimp0_.id=?
2006-09-13 20:28:24,453 DEBUG [org.hibernate.loader.entity.EntityLoader] Static select for entity de.fhw.swtvertiefung.knauf.kuchen.KuchenSimpleBean: select kuchensimp0_.id as id0_0_, kuchensimp0_.name as name0_0_ from KuchenSimpleBean kuchensimp0_ where kuchensimp0_.id=?
2006-09-13 20:28:24,468 DEBUG [org.hibernate.loader.entity.EntityLoader] Static select for action ACTION_MERGE on entity de.fhw.swtvertiefung.knauf.kuchen.KuchenSimpleBean: select kuchensimp0_.id as id0_0_, kuchensimp0_.name as name0_0_ from KuchenSimpleBean kuchensimp0_ where kuchensimp0_.id=?
2006-09-13 20:28:24,468 DEBUG [org.hibernate.loader.entity.EntityLoader] Static select for action ACTION_REFRESH on entity de.fhw.swtvertiefung.knauf.kuchen.KuchenSimpleBean: select kuchensimp0_.id as id0_0_, kuchensimp0_.name as name0_0_ from KuchenSimpleBean kuchensimp0_ where kuchensimp0_.id=?

2006-09-13 20:28:24,484 INFO  [org.hibernate.tool.hbm2ddl.SchemaExport] Running hbm2ddl schema export
2006-09-13 20:28:24,484 DEBUG [org.hibernate.tool.hbm2ddl.SchemaExport] import file not found: /import.sql
2006-09-13 20:28:24,484 INFO  [org.hibernate.tool.hbm2ddl.SchemaExport] exporting generated schema to database
2006-09-13 20:28:24,484 DEBUG [org.hibernate.tool.hbm2ddl.SchemaExport] drop table KuchenSimpleBean if exists
2006-09-13 20:28:24,484 DEBUG [org.hibernate.tool.hbm2ddl.SchemaExport] create table KuchenSimpleBean (id integer generated by default as identity (start with 1), name varchar(255), primary key (id))
2006-09-13 20:28:24,484 INFO  [org.hibernate.tool.hbm2ddl.SchemaExport] schema export complete 


Datenbank-Adminstrations-Tool

Die Hypersonic-Datenbank bringt ein rudimentäres Administrationstool mit. Dieses erreichen wir, indem wir bei laufendem Server auf die JMX-Konsole gehen (http://localhost:8080/jmx-console) und in der Kategorie "jboss" die MBean "database=localDB,service=Hypersonic" auswählen.
Hypersonic Database Manager (1)
Ab zur Funktion "startDatabaseManager" und auf "Invoke" klicken.
Hypersonic Database Manager (2)
Es erscheint nur eine Seite mit einer schnöden Erfolgsmeldung, aber nach kurzer Zeit sollte sich eine Java-Anwendung, der "HSQL Database Manager" öffnen. Hier sehen wir auf der linken Seite die Tabelle unserer Bean. Im Fenster rechts oben können wir SQL-Statements abfeuern, im Beispiel ist das ein Select auf die "KuchenSimpleBean"-Tabelle
Hypersonic Database Manager (3)


Ausführen des Clients

Im Menü "Run" den Punkt "Open Run Dialog..." wählen. In der Auswahl "Configurations" unter "Java Application" eine neue Konfiguration erstellen. Die Einstellungen sollten so aussehen:
Anwendungsclient ausführen
Zu beachten ist, dass vor der Anwendung der EJB-Server gestartet sein muss.

Ohne Annotations

Die Deklaration ohne Annotations erfordert eine ganze Reihe Arbeit.

ejb-jar.xml
<?xml version="1.0" encoding="UTF-8"?>
<ejb-jar version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
                           http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd">
	<display-name>KuchenSimpleEJB</display-name>
	<enterprise-beans>
		<session>
			<description>
				<![CDATA[Stateless Session Bean für den Zugriff auf die Entitiy Bean
 * "Kuchen". Enthält Methoden zum Speichern und Löschen eines einzelnen
 * Kuchens sowie zum Holen einer Liste aller Kuchen.]]>
			</description>
			<display-name>KuchenWorkerBean</display-name>
			<ejb-name>KuchenWorkerBean</ejb-name>
			<remote>de.fhw.swtvertiefung.knauf.kuchen.KuchenWorker</remote>
			<ejb-class>de.fhw.swtvertiefung.knauf.kuchen.KuchenWorkerBean</ejb-class>
			<session-type>Stateless</session-type>
			<!--EntityManager-Injection -->
			<persistence-context-ref>
				<persistence-context-ref-name>KuchenPersistenceUnitRef</persistence-context-ref-name>
				<persistence-unit-name>kuchenPersistenceUnit</persistence-unit-name>
				<injection-target>
					<injection-target-class>
						de.fhw.swtvertiefung.knauf.kuchen.KuchenWorkerBean
					</injection-target-class>
					<injection-target-name>entityManager</injection-target-name>
				</injection-target>
			</persistence-context-ref>
		</session>
		<entity>
			<description>
				<![CDATA[Entity Bean für einen einzelnen Kuchen.]]>
			</description>
			<display-name>KuchenSimpleBean</display-name>
			<ejb-name>KuchenSimpleBean</ejb-name>
			<ejb-class>de.fhw.swtvertiefung.knauf.kuchen.KuchenSimpleBean</ejb-class>
			<persistence-type>Container</persistence-type>
			<prim-key-class>java.lang.Integer</prim-key-class>
			<reentrant>false</reentrant>
		</entity>
	</enterprise-beans>
</ejb-jar> 
Neu in diesem Beispiel:

Neu in diesem Beispiel ist ein weiterer Deployment-Deskriptor: "orm.xml". Hier wird das gesamte Mapping von Bean auf Datenbank erledigt.
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_1_0.xsd"
	version="1.0">
	<named-query name="findAllKuchen">
		<query>select o from KuchenSimpleBean o</query>
	</named-query>

	<entity class="de.fhw.swtvertiefung.knauf.kuchen.KuchenSimpleBean" access="PROPERTY"
	 metadata-complete="true">
		<table name="KUCHENSIMPLEBEAN"></table>
		<attributes>
			<id name="id">
				<column name="ID" /> 
				<generated-value/>
			</id>
			<basic name="name">
				<column name="NAME" />
			</basic>
		</attributes>
	</entity>

</entity-mappings> 
Das Element "entity" hat keine in der Schema-Definition angegebenen Pflicht-Unterelemente, trotzdem sind einige nötig damit unser Beispiel funktioniert.
Das Attribut "access" gibt an ob die Fehler per set-Property vom Container befüllt werden sollen, oder ob er sie direkt in die (privaten) Membervariablen schreiben soll.
"metadata-complete" ist sehr wichtig denn wenn wir es auf "true" setzen wird es beim Deploy seltsame Fehlermeldungen geben (siehe Troubleshooting).
Wir geben hier den Tabellennamen an ("KUCHENSIMPLEBEAN"), außerdem die Attribute und die zugehörigen Datenbankspalten. Für die ID-Spalte geben wir an dass der Wert automatisch generiert werden soll.
Es klingt verführerisch hier auch ein Unterelement <id-class class="java.lang.Integer" /> anzugeben, das ist allerdings ein dicker Fehler, denn eine "id-class" darf es nur geben wenn der Primary Key eine eigene Klasse (z.B. in Form eines Composite Primary Key) ist.

Die modifizierte Version des Projekts gibt es hier:
KuchenSimpleNoAnnotation.ear.
ACHTUNG: Dieses Projekt kann nicht neben dem obigen KuchenSimple-Beispiel existieren !


Manueller Primary Key


Jetzt wollen wir den Primary Key manuell generieren. Das Verfahren hier ist einfach aber seeehr inperformant: bei jedem Neuanlegen eines Datensatzes wird eine Query "select max(id) from KuchenSimpleBean" ausgeführt. Das Ergebnis dieser Query + 1 wird als ID für den neuen Datensatz verwendet.
Im Code ist folgendes zu ändern:

In KuchenSimpleBean.getId wird die Annotation "@GeneratedValue" entfernt:
  @Column()
  @Id()
  public Integer getId()
  {
    return this.intId;
  } 
KuchenSimpleBean erhält eine weitere @NamedQuery-Annotation (die wir jetzt in eine Annotation @NamedQueries verpacken müssen:
@NamedQueries( { 
  @NamedQuery(name = "findAllKuchen", query = "select o from KuchenSimpleBean o"),
  @NamedQuery(name = "getMaxKuchenId", query = "select max (o.id) from KuchenSimpleBean o")
  })
public class KuchenSimpleBean implements Serializable
{
  ...
KuchenWorkerBean.saveKuchen sieht so aus:
  public void saveKuchen (KuchenSimpleBean kuchen)
  {
    if (kuchen.getId() == null)
    {
      Query query = this.entityManager.createNamedQuery("getMaxKuchenId");
      Integer intMaxId = (Integer) query.getSingleResult();
      //Beim ersten Aufruf kommt hier NULL zurück weil keine ID da ist.
      if (intMaxId == null)
      {
        //Wir starten mit der ID "1"
        kuchen.setId(1);
      }
      else
      {
        //MaxID + 1 verwenden:
        kuchen.setId(intMaxId + 1);
      }
    }
    this.entityManager.merge(kuchen);
  } 
Neu ist der fette Code: wenn der übergebene Kuchen keine ID enthält, dann nehmen wir an dass er neu ist. In diesem Fall führen wir unsere Query aus. Sie liefert beim ersten Aufruf NULL zurück, in diesem Fall setzen wir die ID auf "1". Finden wir eine ID erhöhen wir diese um 1.

Kleiner Kurs in Java: Vor Java5 hätten die Aufrufe zum ID-Setzen so aussehen müssen:
	kuchen.setId( new Integer (intMaxId.intValue() + 1));
Neu in Java 5 kam "Auto-Boxing" hinzu, dass es erlaubt einen Basisdatentyp bei Bedarf in die entsprechende Klasse zu konvertieren und zurück (hier: int und java.lang.Integer).

Die modifizierte Version des Projekts gibt es hier:
KuchenSimpleManuellerPK.ear.
ACHTUNG: Dieses Projekt kann nicht neben dem obigen KuchenSimple-Beispiel existieren !



Troubleshooting

Hier werden ein paar Fehler beschrieben in die ich beim Programmieren gelaufen bin ;-)

Stand 08.11.2007
Historie:
28.10.2007: Erstellt aus Vorjahresbeispiel, Visual-Edit-Beschreibungen entfernt.
08.11.2007: SQL-Parameter-Logging.