Hallo,
Ich beschäftige mich mit EJBs, setze als App.Server JBoss 4.2.2GA (Hibernate ist da ja schon dabei) ein und habe als Datenbank MySQL 5.0.
Eine Applikation braucht pessimistische Locks.
Ein Aufruf sieht ähnlich wie folgt aus:
Client --> ServiceBean -->* ModifierBean --> DB
wobei:
ServiceBean = einfaches Stateless Session Bean mit ein wenig Logik
ModifierBean= Stateless Session Bean mit Datenbankzugriff (und Locking)
Für meinen Testfall habe ich mal die Logikabläufe aus dem ServiceBean weggelassen und nur 2x modify() aufgerufen (siehe Quelltext).
EJB(A) spricht mehrere ModifierBean an. Die Lock-Abschnitte sollen ja möglichst kurz sein und so wollte ich alles, was nicht zwingend gelockt werden muss auch nicht in diesem Scope abhandeln.
Nun ist das vergeben der Locks an sich kein Problem.
Nur werden sie beim Verlassen von ModifierBean nicht wieder aufgehoben, sondern ersz wenn ServiceBean verlassen wird.
Was ich bisher schon versucht habe
- in ServiceBean wird ModifierBean wird per @EJB vom Container injected
in EJB(B) wird der Entitymanager per @PersistenceContext eingebunden
so wird das Lock aber erst beim Verlassen von ServiceBean aufgegeben. - wie oben, nur gibt es kein globales statisches ModifierBean, sondern es wird in jeder Servicemethode per ctx.lookup geholt. Dabei ist es ja in einem festen Scope zwischen den { } und somit ist die Lebenszeit begrenzt ... dachte ich -> weit gefehlt.
- wieder wie 1. diesmal aber mit einem Extended PersistenceContext und manuellem flush. Leider interessiert sich der EM nicht für das em.flush. Die Info landet wieder erst nach verlassen von ServiceBean in der DB.
- dann habe ich mal eine Session per @PersistenceContext injecten lassen ... wieder nix
- zum Schluss habe ich die HibernateFactory per JNDI geholt um so an eine Session zu kommen. Nur von dieser ging es dann nicht eine Transaktion zu eröffnen.
- ich habe auch mal ein @Transaction(REQUIRED) and ie Methode geschrieben, da das laut Hibernate den Context (Session/Transaktion) beim Verlassen der Methode beendet. Aber auch das schlug fehl.
So langsam gehen mir die Ideen aus, bzw. kommt die richtige nicht.
Was ich möchte ist,
dass das Lock beim Verlassen der ModifierBean-Methode wieder entfernt wird und nicht erst beim Verlassen von ServiceBean, sodass nur ModifierBean-Methoden einen gegenseitigen Ausschluss gewähren, ServiceBean-Methoden aber quasi-parallel ablaufen können.
Oder liegt es gar an JBoss, der evtl. den Context zu spät beendet?
Wenn jemand einen Hinweis hat, der mich weiterbringt, würde ich mich sehr freuen.
So, und nun der Quellcode von dem Beispiel
Start.java
Code:
package pz.test.writeDB;
import javax.naming.Context;
import javax.naming.InitialContext;
import pz.test.writeDB.service.IService;
public class Start extends Thread
{
private static IService sb;
private static Integer id;
private String value;
public Start(String value)
{
this.value = value;
}
public void run()
{
sb.modify(id, value);
System.out.println("finished");
}
public static void main(String[] args)
{
//lookup service bean
try
{
Context context = new InitialContext();
sb = (IService) context.lookup("CServiceBean/remote");
}
catch (Exception e)
{
e.printStackTrace();
}
//create working object
id = sb.create();
//start 2 concurrent threads
new Start("eins").start();
new Start("zwei").start();
}
}
CServiceBean.javaCode:
package pz.test.writeDB.service;
import javax.ejb.EJB;
import javax.ejb.Local;
import javax.ejb.Remote;
import javax.ejb.Stateless;
import javax.naming.InitialContext;
import pz.test.writeDB.IModifier;
@Stateless
@Remote(value=IService.class)
@Local(value=IService.class)
public class CServiceBean implements IService
{
// @EJB
// private static IModifier modifier;
public void modify(Integer id, String value)
{
try
{
InitialContext initial = new InitialContext();
IModifier modifier = (IModifier) initial.lookup("CModifierBean/remote");
modifier.modify(id, value); //breakpoint
System.out.println("");
modifier.flushEM();
}
catch(Exception e)
{
e.printStackTrace();
}
//warum existiert der Persistence Context hier immer noch?
//das Lock ist noch nicht aufgehoben
try
{
InitialContext initial = new InitialContext();
IModifier modifier = (IModifier) initial.lookup("CModifierBean/remote");
modifier.modify(id, value+"+"); //breakpoint
System.out.println("");
modifier.flushEM();
}
catch(Exception e)
{
e.printStackTrace();
}
//warum existiert der Persistence Context hier immer noch?
//das Lock ist noch nicht aufgehoben
System.out.println("modified"); //breakpoint
//erst wenn die folgende Klammer verlassen wird
//(repektive wenn wieder im Start-Objekt)
//wird das Lock aufgegeben
}
/**
* Testobjekt anlegen
*/
public Integer create()
{
try
{
InitialContext initial = new InitialContext();
IModifier modifier = (IModifier) initial.lookup("CModifierBean/remote");
Integer id = modifier.create();
return id;
}
catch(Exception e)
{
e.printStackTrace();
}
return null;
}
}
CModifierBean.javaCode:
package pz.test.writeDB;
import javax.ejb.Local;
import javax.ejb.Remote;
import javax.ejb.Remove;
import javax.ejb.Stateful;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import javax.persistence.PersistenceProperty;
import org.hibernate.LockMode;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.ejb.HibernateEntityManager;
import org.jboss.ejb3.entity.ExtendedEntityManager;
import pz.test.writeDB.model.TestObject;
@Stateless
@Remote(value=IModifier.class)
@Local(value=IModifier.class)
public class CModifierBean implements IModifier
{
//das ist der Standard, aber so geht nicht, was ich will
@PersistenceContext
private EntityManager em;
//noch ein paar gescheiterte Versuche mit statischen Members
// @PersistenceContext
// (
// type=PersistenceContextType.EXTENDED,
// properties = @PersistenceProperty(name="org.hibernate.flushMode", value="MANUAL")
// )
// private Session session;
// @PersistenceContext (
// type = PersistenceContextType.EXTENDED,
// properties = @PersistenceProperty (
// name="org.hibernate.flushMode",
// value="MANUAL")
// )
// private EntityManager em;
// private Session getSession()
// {
// try
// {
// Context ctx = new InitialContext();
// SessionFactory factory = (SessionFactory) ctx.lookup("java:/hibernate/HibernateFactory");
// return factory.openSession();
// }
// catch(Exception e)
// {
// e.printStackTrace();
// }
// return null;
// }
public Integer create()
{
TestObject to = new TestObject();
to.setValue("init");
// Session session = getSession();
// Transaction tx = session.beginTransaction();
to = em.merge(to); //muss gecastet werden, wenn session verw. wird
// tx.commit();
// session.close();
return to.getId();
}
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void modify(Integer id, String value)
{
// Session session = getSession();
// Transaction tx = session.beginTransaction();
Session session = ((HibernateEntityManager) em).getSession();
TestObject to = (TestObject) session.get(TestObject.class, id, LockMode.UPGRADE);
to.setValue(value);
session.merge(to);
// tx.commit();
session.close();
System.out.println("merged");
}
/**
* lock entity
*/
public <T> T lock(T entity)
{
//normaler PC
// Session session = ((HibernateEntityManager) em).getSession();
//wenn extended PC
// Session session = ((ExtendedEntityManager) em).getHibernateSession();
//
//
//// T t = null;
// try
// {
//// T result = (T) session.get(entity.getClass(), 1, LockMode.UPGRADE);
//// session.refresh(entity, LockMode.UPGRADE);
//// t = result;
// }
// catch(ClassCastException e)
// {
// e.printStackTrace();
// }
//// return t;
return entity;
}
@Remove
public void flushEM()
{
// session.flush();
// session.close();
// em.flush();
// em.close();
}
}
persistence.xmlCode:
<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="dbtest">
<jta-data-source>
java:/dbtest
</jta-data-source>
<properties>
<property name="hibernate.connection.isolation" value= "8" />
<property name="hibernate.dialect" value= "org.hibernate.dialect.MySQLInnoDBDialect" />
<property name="hibernate.hbm2ddl.auto" value="update" />
<property name="hibernate.session_factory_name" value="java:/hibernate/HibernateFactory"/>
<!--
<property name="hibernate.jndi.class">org.jnp.interfaces.NamingContextFactory</property>
<property name="hibernate.jndi.url">t3://127.0.0.1:1099</property>
-->
</properties>
</persistence-unit>
</persistence>
jndi.propertiesCode:
java.naming.provider.url=jnp://127.0.0.1:1099
java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces
java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
jnp.socket.Factory=org.jnp.interfaces.TimedSocketFactory