Commit von Daten aus Transaktionen erfolgt erst beim herunterfahren des Servers

Herr_M

Erfahrenes Mitglied
Hallo Zusammen,

auch wenn es wohl eher unwahrscheinlich ist, das jemand hier die Lösung für mein Problem kennt (ist in Relation zu den Themen die hier meistens angesprochen werden etwas exotisch) werd ichs trotzdem mal schildern. Eine blinde Henne findet ja schließlich auch mal ein Korn.

Die Kurzfassung: Die Updates/Inserts/Deletes in meiner Client Server Anwendung werden nicht nach (vermeintlichem) Ende der Transaktion in die Datenbank geschrieben, sondern erst beim herunterfahren/neustarten des Servers.



Langfassung:
Ich entwickle zusammen mit einem Kollegen an einer Client Server Application, wobei der Client eine auf Swing basierende GUI Anwendung ist.
Der Serverteil der Anwendung wird via RMI angesprochen und setzt in der Realisierung auf EJB 3.0.
Die Datenbankzugriffe und damit auch die Transaktionssteuerung finden alle durch den Serverseitigen Teil der Anwendung statt. Dafür setzen wir auf JPA und eben besagte EJB 3.0 Beans.
Das Problem dabei ist, startet man den Client kann dieser wie erwartet auf den Server zugreifen, man kann Änderungen an den Daten vornehmen etc. Meldet man sich ab, und später wieder an sind die Änderungen vorhanden, werden angezeigt etc.
Wirft man allerdings mit einem Datenbanktool einen Blick auf die Datebank, sieht man das die Änderungen nicht wie erwartet in die Datenbank übernommen worden sind.
Erst nach dem herunterfahren des Servers landen diese dort.

Woran könnte das liegen?

Eckdaten des Projets:
Client mit Swing
Zugriff auf den Server via RMI

Serverseite:
Server: Geronimo 2.1.1
Programm verwendet JPA und EJB 3.0

Hier eine Beispiel für den Einsatz eines der Beans:
Code:
@Stateless
@Remote(AccessRemote.class)
@Local(Access.class)
public class AccessSessionBean implements Access {

  @PersistenceContext(unitName = "FMDBPU")
  EntityManager manager;
 
  @TransactionAttribute(TransactionAttributeType.REQUIRED)
  public void updateUserPassword(UserValueObject uservo)
    throws NotFoundException, Exception {
    BenutzerBean bb = manager.find(BenutzerBean.class, uservo.getUserId());
    bb.setPass(uservo.getPass());
    manager.flush();
  }
}


Hier der Code des Beans

Code:
package ...

import javax.ejb.Stateless;
import javax.persistence.*;

/**
 * @author liangs $Revision: $ $Modtime: $
 * @since 3.0
 */
@Entity
@Table(name = "BENUTZER")
// @NamedQuery(name = "findAllBenutzer", query = "SELECT benutzer FROM
// BenutzerBean benutzer")
@NamedQueries( {
    @NamedQuery(name = "findAllBenutzer", query = "SELECT benutzer FROM BenutzerBean benutzer"),
    @NamedQuery(name = "findBenutzerById", query = "SELECT benutzer FROM BenutzerBean benutzer WHERE benutzer.userId =:userId") })
@SequenceGenerator(name = "USERID_SEQUENCE", sequenceName = "SEQ_BENUTZER_USERID")
// TODO Sequenz in DB erstellen und Name vergeben
public class BenutzerBean implements java.io.Serializable {

  private int userId;
  private String userName;
  private String pass;
  private int boacForm;
  private int boacMeng;
  private int boacVerw;
  private int boacXtra;
  private int gesperrt;
  private int errLogins;

  @Id
  @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "USERID_SEQUENCE")
  @Column(name="USERID")
  public int getUserId() {
    return this.userId;
  }

  public void setUserId(int userId) {
    this.userId = userId;
  }

  @Column(name="BOACFORM")
  public int getBoacForm() {
    return this.boacForm;
  }

  public void setBoacForm(int boacForm) {
    this.boacForm = boacForm;
  }

  @Column(name="BOACMENG")
  public int getBoacMeng() {
    return this.boacMeng;
  }

  public void setBoacMeng(int boacMeng) {
    this.boacMeng = boacMeng;
  }

  @Column(name="BOACVERW")
  public int getBoacVerw() {
    return this.boacVerw;
  }

  public void setBoacVerw(int boacVerw) {
    this.boacVerw = boacVerw;
  }

  @Column(name="BOACXTRA")
  public int getBoacXtra() {
    return this.boacXtra;
  }

  public void setBoacXtra(int boacXtra) {
    this.boacXtra = boacXtra;
  }

  @Column(name="ERRLOGINS")
  public int getErrLogins() {
    return this.errLogins;
  }

  public void setErrLogins(int errLogins) {
    this.errLogins = errLogins;
  }

  @Column(name="GESPERRT")
  public int getGesperrt() {
    return this.gesperrt;
  }

  public void setGesperrt(int gesperrt) {
    this.gesperrt = gesperrt;
  }

  @Column(name="PASS")
  public String getPass() {
    return this.pass;
  }

  public void setPass(String pass) {
    this.pass = pass;
  }

  @Column(name="USERNAME")
  public String getUserName() {
    return this.userName;
  }

  public void setUserName(String userName) {
    this.userName = userName;
  }

}
 
Code:
public void updateUserPassword(UserValueObject uservo)
    throws NotFoundException, Exception {
    EntityTransaction tx=manager.getTransaction();
    tx.begin(); 
    BenutzerBean bb = manager.find(BenutzerBean.class, uservo.getUserId());
    bb.setPass(uservo.getPass());
    manager.persist(bb);
    tx.commit();
    manager.close();
  }

Mal getestet?
 
Code:
public void updateUserPassword(UserValueObject uservo)
    throws NotFoundException, Exception {
    EntityTransaction tx=manager.getTransaction();
    tx.begin(); 
    BenutzerBean bb = manager.find(BenutzerBean.class, uservo.getUserId());
    bb.setPass(uservo.getPass());
    manager.persist(bb);
    tx.commit();
    manager.close();
  }

Mal getestet?

Ja hab ich, mit dem Ergebnis
"Exception: You are not allowed to execute getTransaction on a container managed Bean..."

Das was du vorschlägst geht ja nur mit EntityManaged Beans, wenn ich meinen Kollegen richtig verstanden hab. Bei der von mir gewählten Herangehensweise sollte das commit eigentlich automatisch ausgelöst werden. Im Moment ist aber noch nicht mal klar ob es das nicht sogar wird, und es an einer darauf folgenden Stelle hängt.
 
Hallo,


Das was du vorschlägst geht ja nur mit EntityManaged Beans, wenn ich meinen Kollegen richtig verstanden hab.
Das geht bei Application Managed EntityManagers nicht aber bei Container Managed EntityManagers (wie auch in der Fehlermeldung angegeben).

nur mal so nebenbei AFAIK ist der JPA EntityManager nicht Threadsafe, weshalb man diesen nicht als einen Instanzmember halten sollte... sonst kommts hier zu Problemen mit gepooledten Instanzen wenn mehrere Threads an diesen Methoden aufrufen.

Die EntityManagerFactory hiongegen ist Threadsafe. Deshalb bietet es sich hier an diese als Instanzmember zu halten und dann bei bedarf wieder per
EntityManager entityManager = entityManagerFactory.createEntityManager();
zu erzeugen. Nach dem man dann mit dem entityManager interagiert hat, kann man ihn dann via entityManager.close() wieder freigeben.

Deine Problemsymptomatik lässt mich irgendwie vermuten, dass du die ganze Zeit den selben EntityManager per DI injected bekommst und dieser erst beim Application Server shutdown ein close(...) erfahrt und damit implizit ein flush(...) auf die Datenbank macht.

Ansonsten: Hast du schon gechecked, ob die Methode wirklich innerhalb einer Transaktion abläuft? Ansonsten würde ich mir hier deine TX Konfiguration nochmal durchsehen.

Stell das doch einfach mal so wie oben beschrieben um.

Gruß Tom
 
Ok dann werd ich das mal versuchen.
Der Manager wird bei mir im Moment tatsächlich über eine Annotation injected.
Das mit dem "fehlenden" close könnte des Rätsels Lösung sein.

Muss ich sonst wo noch was beachten ändern? Persistence.xml z.B.?

Meine Persistence.xml sieht aktuell so aus:
Code:
<?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" version="1.0"
		xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
	<persistence-unit name="FMDBPU" transaction-type="JTA">
		<description>FMDB</description>
    <provider>org.apache.openjpa.persistence.PersistenceProviderImpl</provider>
		<jta-data-source>FMDBPool</jta-data-source>
	<class>com.de.otto.fmdb.ejb.BenutzerBean</class>
	<class>com.de.otto.fmdb.ejb.FormulareBean</class>
	<class>com.de.otto.fmdb.ejb.SystemBean</class>
	<class>com.de.otto.fmdb.ejb.AuflagenBean</class>
	</persistence-unit>
</persistence>
 
Hallo,

zunächst einmal micht ich mich noch korrigieren.
Eine Stateless Session Bean ist eine vom Container verwaltete Komponente die an sich Thread-safe ist. Deshalb kann man hier sehr wohl direkt mit vom Container bereitgestellten EntityManager gefahrlos arbeiten.


Weiterhin hab ich hier mal ein kleines Beispiel zu deinem Problem gebaut:
Als Application Server verwende ich hier JBoss 5.0RC3 und als JPA Persistence Provider Hibernate.
Java:
/**
 * 
 */
package de.tutorials.services;

import java.util.Set;

import javax.ejb.Remote;

import de.tutorials.domain.User;

/**
 * @author Tom
 *
 */
@Remote
public interface IUserAdministrationService {
    void registerUser(User user);
    void unregisterUser(User user);
    Set<User> list();
}

Java:
/**
 * 
 */
package de.tutorials.domain;

import java.io.Serializable;

import javax.persistence.Basic;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;

/**
 * @author Tom
 * 
 */
@Entity
@Table(uniqueConstraints = @UniqueConstraint(columnNames = { "email" }))
public class User implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    long id;

    @Basic
    String email;
    @Basic
    String name;

    public User() {
    }

    public User(String name,String email) {
        this.name = name;
        this.email = email;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((email == null) ? 0 : email.hashCode());
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        User other = (User) obj;
        if (email == null) {
            if (other.email != null)
                return false;
        } else if (!email.equals(other.email))
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }

    @Override
    public String toString() {
        return String.format("User id:%s name:%s email: %s", id, name, email);
    }

}

Java:
/**
 * 
 */
package de.tutorials.services.internal;

import java.util.HashSet;
import java.util.Set;

import javax.ejb.Stateless;
import javax.persistence.EntityExistsException;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.NonUniqueResultException;
import javax.persistence.PersistenceContext;

import de.tutorials.domain.User;
import de.tutorials.services.IUserAdministrationService;

/**
 * @author Tom
 * 
 */
@Stateless
public class UserAdministrationService implements IUserAdministrationService {

	@PersistenceContext(unitName = "de.tutorials.PU")
	EntityManager entityManager;

	@Override
	public Set<User> list() {
		Set<User> users = new HashSet<User>();
		users.addAll(entityManager.createQuery("Select u from User u")
				.getResultList());
		return users;
	}

	@Override
	public void registerUser(User user) {
		try {
			entityManager.persist(user);
		} catch (EntityExistsException entityExistsException) {
			System.out.println(String.format(
					"A user with email: %s already exists", user.getEmail()));
		}
	}

	@Override
	public void unregisterUser(User user) {
		try {
			User userToUnregister = (User) entityManager.createQuery(
					"Select u from User u where u.email=:email").setParameter(
					"email", user.getEmail()).getSingleResult();
			if (null != userToUnregister) {
				entityManager.remove(userToUnregister);
			}
		} catch (NoResultException noResultException) {
			System.out.println(String.format(
					"A user with email: %s does not exist", user.getEmail()));
		} catch(NonUniqueResultException nonUniqueResultException){
			System.out.println(String.format(
					"Several users with email: %s do exist, this should never happen due to unique constraints on email", user.getEmail()));
		}
	}
}

Persistence.xml
XML:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0"
    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">
    <persistence-unit name="de.tutorials.PU" transaction-type="JTA">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <jta-data-source>java:/mysqlDS</jta-data-source>
        <class>de.tutorials.domain.User</class>
        <properties>
            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" />
            <property name="hibernate.hbm2ddl.auto" value="create-drop" />
            <property name="hibernate.show_sql" value="true" />
        </properties>
    </persistence-unit>
</persistence>

Die mysql-tutorials-ds.xml:
XML:
<?xml version="1.0" encoding="UTF-8"?>
<datasources>
  <local-tx-datasource>
    <jndi-name>mysqlDS</jndi-name>
    <connection-url>jdbc:mysql://localhost:3306/tutorials</connection-url>
    <driver-class>com.mysql.jdbc.Driver</driver-class>
    <user-name>root</user-name>
    <password>tutorials</password>
    <exception-sorter-class-name>org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter</exception-sorter-class-name>
    <metadata>
       <type-mapping>mySQL</type-mapping>
    </metadata>
  </local-tx-datasource>
</datasources>

und hier dann unser Client:
Java:
/**
 * 
 */
package de.tutorials;

import javax.naming.InitialContext;

import de.tutorials.domain.User;
import de.tutorials.services.IUserAdministrationService;

/**
 * @author Tom
 * 
 */
public class Client {

	/**
	 * @param args
	 */
	public static void main(String[] args) throws Exception {
		InitialContext context = new InitialContext();
		IUserAdministrationService userAdministrationService = 
			(IUserAdministrationService) context.lookup("UserAdministrationService/remote");

		User tom = new User("tom", "tom@foo.de");
		userAdministrationService.registerUser(tom);

		User anne = new User("anne", "anne@foo.de");
		userAdministrationService.registerUser(anne);

		for (User member : userAdministrationService.list()) {
			System.out.println(member);
		}

		userAdministrationService.registerUser(anne);

		for (User member : userAdministrationService.list()) {
			System.out.println(member);
		}

		userAdministrationService.unregisterUser(anne);

		for (User member : userAdministrationService.list()) {
			System.out.println(member);
		}

		context.close();
	}
}

Hierfür müssen wir noch die entsprechende jndi.properties im Classpath liegen haben:
Code:
java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces
java.naming.provider.url=jnp://localhost:1099

... und schon sollte es funktionieren. Ich vermute mal das irgendwas bei deiner Datasource Konfiguration falsch ist.

Ausgabe Server (Ausschnitt):
Code:
...
21:13:40,999 INFO  [EJBContainer] STARTED EJB: de.tutorials.services.internal.UserAdministrationService ejbName: UserAdministrationService
21:13:44,425 WARN  [InterceptorsFactory] EJBTHREE-1246: Do not use InterceptorsFactory with a ManagedObjectAdvisor, InterceptorRegistry should be used via the bean container
21:13:44,425 WARN  [InterceptorsFactory] EJBTHREE-1246: Do not use InterceptorsFactory with a ManagedObjectAdvisor, InterceptorRegistry should be used via the bean container
21:13:44,440 INFO  [STDOUT] Hibernate: insert into User (email, name) values (?, ?)
21:13:44,454 INFO  [STDOUT] Hibernate: insert into User (email, name) values (?, ?)
21:13:44,467 INFO  [STDOUT] Hibernate: select user0_.id as id6_, user0_.email as email6_, user0_.name as name6_ from User user0_
21:13:44,481 INFO  [STDOUT] Hibernate: insert into User (email, name) values (?, ?)
21:13:44,483 WARN  [JDBCExceptionReporter] SQL Error: 1062, SQLState: 23000
21:13:44,483 ERROR [JDBCExceptionReporter] Duplicate entry 'anne@foo.de' for key 'email'
21:13:44,483 INFO  [STDOUT] A user with email: anne@foo.de already exists
21:13:44,490 INFO  [STDOUT] Hibernate: select user0_.id as id6_, user0_.email as email6_, user0_.name as name6_ from User user0_
21:13:44,511 INFO  [STDOUT] Hibernate: select user0_.id as id6_, user0_.email as email6_, user0_.name as name6_ from User user0_ where user0_.email=?
21:13:44,513 INFO  [STDOUT] Hibernate: delete from User where id=?
21:13:44,520 INFO  [STDOUT] Hibernate: select user0_.id as id6_, user0_.email as email6_, user0_.name as name6_ from User user0_

Ausgabe Client:
Code:
User id:1 name:tom email: tom@foo.de
User id:2 name:anne email: anne@foo.de
User id:1 name:tom email: tom@foo.de
User id:2 name:anne email: anne@foo.de
User id:1 name:tom email: tom@foo.de

HTH

Gruß Tom
 

Anhänge

  • remoteClientSessionBeanJPAJboss.zip
    13 KB · Aufrufe: 35
Zuletzt bearbeitet von einem Moderator:
Hallo Tom,

erstmal vielen Dank für das Beispiel.

Nach dem jetzt doch beschlossen wurde es mal mit JBoss anstelle von Geronimo zu versuchen, wird dein Beispiel gleich doppelt von Nutzen sein.
Denn so haben wir gleich eine Anwendung zum testen bei der wir davon ausgehen können, dass sie funktioniert.

Nur auf der Website von JBoss konnte ich im Downloadbereich keinen "JBoss 5.0RC3" finden. Meinst du vielleicht die Beta 3 von Version 5.0.0 ?
 

Neue Beiträge

Zurück