Beispiel: @Version-Annotation


Inhalt:

EJB-Projekt
Web-Projekt
Ohne Annotations

Beispiel für eine Entity Bean die ein "optimistic locking" mittels @Version-Annotation implementiert. Hier gibt es das Projekt zum Download (dies ist ein EAR-Export, die Importanleitung findet man im Stateless-Beispiel): KuchenVersion.ear

Aufbau des Beispieles

a) Entity Bean-Klasse KuchenVersionBean
b) Session Bean KuchenVersionBeanWorker sowie Local Interface.
d) Web Client


EJB-Projekt

In der Entity Bean de.fhw.komponentenarchitekturen.knauf.version.KuchenVersionBean wird ein neues Feld zugefügt:
  private Integer intVersion;
  
  @Version()
  public Integer getVersion()
  {
    return this.intVersion;
  }

  public void setVersion(Integer int_Version)
  {
    this.intVersion = int_Version;
  }
In der Session-Bean ergeben sich durch das Versions-Feld keine Änderungen.

Im Projekt sollten allerdings auf SessionBean-Ebene die Exceptions, die sich durch eine zwischenzeitliche Änderung ergeben, abgefangen und in eine eigene Exceptionklasse verpackt werden.


Web-Projekt

Im Web-Projekt gibt es eine Änderung: wenn eine Entity Bean in einem Formular bearbeitet wird, dann muss man nicht nur die ID mitführen, sondern auch den beim Öffnen des Formulars gültigen Wert des Version-Felds.
Grund: Das Version-Feld kann nur anschlagen, wenn beim Speichern einer Bean ein Wert im Version-Feld steht, der nicht mehr dem aktuellen Datenbankwert entspricht. Würde man nur die ID ins Formular geben und beim Submit die Bean laden, dann könnte man auch eine zwischenzeitlich geänderte Entity-Bean speichern, denn durch das Laden würde man ja schon die aktuellste Version erwischen.

Codeausschnitt aus "kuchenEdit.jsp" (Aufbau und Auswerten des Bearbeiten-Formulars):
  String strSubmitType = request.getParameter("submit");
  ...
  if ("Bearbeiten".equals(strSubmitType) == true)
  {
    //Kuchen bearbeiten: Den Kuchen holen (ID steckt im Request !) und den Namen im Textfeld vorgeben.
    String strID = request.getParameter("kuchenid");
    KuchenVersionBean kuchenBearbeiten = kuchenVersionWorker.findKuchenById( Integer.valueOf(strID) );
  %>
  <FORM method="post">
  Kuchen-Bezeichnung: <input type="text" name="name" value="<%=kuchenBearbeiten.getName()%>"> <br>

  <INPUT TYPE="SUBMIT" NAME="submit" VALUE="BearbeitenOK"> <br>
  <!--Die Kuchen-ID als Hidden Field mitführen -->
  <input type="hidden" name="kuchenid" value="<%=kuchenBearbeiten.getId()%>">
  <!--Auch die Version muss mitgeführt werden ! -->
  <input type="hidden" name="version" value="<%=kuchenBearbeiten.getVersion()%>">
  </FORM>
  <%
  }
  else if ("BearbeitenOK".equals(strSubmitType) == true)
  {
    //Im Bearbeiten-Formular auf "OK" geklickt: Kuchen updaten.
    //Den Kuchen holen (ID steckt im Request !) und den Namen im Textfeld vorgeben.
    String strID =  request.getParameter("kuchenid");

    KuchenVersionBean kuchenBearbeiten = kuchenVersionWorker.findKuchenById( Integer.valueOf(strID) );

    kuchenBearbeiten.setName( request.getParameter("name") );

    //Auch die Version wird hier mitgeführt !
    Integer intVersion = Integer.valueOf(request.getParameter ("version") );
    kuchenBearbeiten.setVersion (intVersion);
  
    kuchenVersionWorker.saveKuchen(kuchenBearbeiten);
    //Request weiterleiten an Übersichtsseite:
    request.getRequestDispatcher ("index.jsp").forward(request, response);
  }

Zum Test kann man zwei Browserfenster öffnen, und in jedem Fenster die gleiche Entity Bean zum Bearbeiten öffnen. Dann im ersten Fenster etwas ändern und Speichern, danach das gleiche im zweiten Browserfenster versuchen. Das führt zu dieser Meldung:
javax.ejb.EJBException: javax.persistence.OptimisticLockException
	org.jboss.ejb3.tx.Ejb3TxPolicy.handleExceptionInOurTx(Ejb3TxPolicy.java:77)
	org.jboss.aspects.tx.TxPolicy.invokeInOurTx(TxPolicy.java:83)
	org.jboss.aspects.tx.TxInterceptor$Required.invoke(TxInterceptor.java:190)
	org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:102)
	....
	org.jboss.ejb3.proxy.handler.session.SessionSpecProxyInvocationHandlerBase.invoke(SessionSpecProxyInvocationHandlerBase.java:125)
	$Proxy159.saveKuchen(Unknown Source)
	org.apache.jsp.kuchenEdit_jsp._jspService(kuchenEdit_jsp.java:137)
	org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70)
	javax.servlet.http.HttpServlet.service(HttpServlet.java:803)
	org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:369)
	org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:322)
	org.apache.jasper.servlet.JspServlet.service(JspServlet.java:249)
	javax.servlet.http.HttpServlet.service(HttpServlet.java:803)
	org.jboss.web.tomcat.filters.ReplyHeaderFilter.doFilter(ReplyHeaderFilter.java:96)

root cause:
javax.persistence.OptimisticLockException
	org.hibernate.ejb.AbstractEntityManagerImpl.wrapStaleStateException(AbstractEntityManagerImpl.java:627)
	org.hibernate.ejb.AbstractEntityManagerImpl.throwPersistenceException(AbstractEntityManagerImpl.java:588)
	org.hibernate.ejb.AbstractEntityManagerImpl.merge(AbstractEntityManagerImpl.java:244)
	org.jboss.ejb3.entity.TransactionScopedEntityManager.merge(TransactionScopedEntityManager.java:191)
	de.fhw.swtvertiefung.knauf.version.KuchenVersionWorkerBean.saveKuchen(KuchenVersionWorkerBean.java:49)
	sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
	sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	java.lang.reflect.Method.invoke(Unknown Source)
	....
	
root cause:
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [de.fhw.komponentenarchitekturen.knauf.version.KuchenVersionBean#1]
	org.hibernate.event.def.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:313)
	org.hibernate.event.def.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:167)
	org.hibernate.event.def.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:81)
	org.hibernate.impl.SessionImpl.fireMerge(SessionImpl.java:704)
	org.hibernate.impl.SessionImpl.merge(SessionImpl.java:688)
	org.hibernate.impl.SessionImpl.merge(SessionImpl.java:692)
	org.hibernate.ejb.AbstractEntityManagerImpl.merge(AbstractEntityManagerImpl.java:235)
	org.jboss.ejb3.entity.TransactionScopedEntityManager.merge(TransactionScopedEntityManager.java:191)
	de.fhw.swtvertiefung.knauf.version.KuchenVersionWorkerBean.saveKuchen(KuchenVersionWorkerBean.java:49)
	sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	....
Sauberer Code sollte also im Client bei jeder Speicherung die javax.persistence.OptimisticLockException fangen!


Ohne Annotations

"ejb-jar.xml" sieht so aus:
<?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">

	<enterprise-beans>
		<session>
			<description>
				<![CDATA[Stateless Session Bean für das Arbeiten mit Kuchen]]>
			</description>
			<display-name>KuchenVersionWorkerBean</display-name>
			<ejb-name>KuchenVersionWorkerBean</ejb-name>
			<business-local>de.fhw.komponentenarchitekturen.knauf.version.KuchenVersionWorkerLocal</business-local>
			<ejb-class>de.fhw.komponentenarchitekturen.knauf.version.KuchenVersionWorkerBean</ejb-class>
			<session-type>Stateless</session-type>
			<persistence-context-ref>
				<persistence-context-ref-name>KuchenVersionPersistenceUnitRef</persistence-context-ref-name>
				<persistence-unit-name>kuchenVersionPersistenceUnit</persistence-unit-name>
				<injection-target>
					<injection-target-class>
						de.fhw.komponentenarchitekturen.knauf.version.KuchenVersionWorkerBean
					</injection-target-class>
					<injection-target-name>entityManager</injection-target-name>
				</injection-target>
			</persistence-context-ref>
		</session>
		
	</enterprise-beans>
</ejb-jar> 
Es gibt keine Neuerungen im Vergleich zu den bisherigen Beispielen.

"orm.xml" enthält die Deklaration der Versions-Spalte:
<?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 KuchenVersionBean o</query>
	</named-query>
	<entity class="de.fhw.komponentenarchitekturen.knauf.version.KuchenVersionBean" access="PROPERTY"
		metadata-complete="true">
		<attributes>
			<id name="id">
				<generated-value />
			</id>
			<basic name="name">
			</basic>
			<version name="version"></version>
		</attributes>
	</entity>

</entity-mappings>

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



Stand 23.11.2008
Historie:
23.11.2008: Erstellt aus Vorjahresbeispiel, angepaßt an JBoss 5.