Spring AOP und Hibernate: Transaktionen funktionieren nicht

DarthShader

Erfahrenes Mitglied
Hallo zusammen,

ich habe ein Problem mit Spring / AOP in Kombination mit Hibernate und Transaktionen. Anscheinend werden keine Transaktionen gestartet, und ich kann mir nicht erklären, warum das so ist.

Hier erstmal die "Vorgeschichte":

Dies ist eine Service Klasse, deren Methode soll mit einer Transaktion gesichert werden:

Java:
public class ServerRemoteService {
	public SysConfig getSystemConfiguration() {

		SysConfig sysConfig = sysConfigDao.getSysConfig( 1 );
		if ( sysConfig == null )
			return null;
			
	    // Tue etwas mit der sysConfig
	    sysConfig.getAddresses();   // Exception wegen Lazy Loading, da keine Session mehr offen ist!
	    
		return sysConfig;
}

So sieht das DAO aus:

Java:
public class HibernateSysConfigDaoImpl extends HibernateDaoSupport implements SysConfigDao {
	@Override
	public SysConfig getSysConfig( Serializable id ) {
		return (SysConfig)getHibernateTemplate().get( SysConfig.class, id );
	}
}

Und nun, wie ich den Spring context bezüglich der DB konfiguriere:

XML:
	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
		<property name="acquireRetryAttempts" value="1" />
		<property name="driverClass" value="com.mysql.jdbc.Driver" />
		<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/foo" />
		<property name="user" value="foo" />
		<property name="password" value="foo" />
	</bean>

	<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean" >
		<property name="dataSource" ref="dataSource" />
		<property name="hibernateProperties">
			<value>
				hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
				hibernate.show_sql = false
				hibernate.format_sql = true 
			</value>
		</property>
		<property name="packagesToScan">
			<list>
				<value>de.foo.domain.**.*</value>
    		</list>
		</property>
	</bean>

	<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
		<property name="sessionFactory" ref="sessionFactory" />
	</bean>


Und zu guter letzt die Definition von AOP, um das transaktionale Verhalten festzulegen:

XML:
	<tx:advice id="txAdvice" transaction-manager="transactionManager">
		<tx:attributes>
			<tx:method name="get*" read-only="true" propagation="REQUIRED" />
			<tx:method name="find*" read-only="true" propagation="REQUIRED" />
			<tx:method name="*" read-only="false" propagation="REQUIRED" />
		</tx:attributes>
	</tx:advice>

	<aop:config>
		<aop:pointcut id="serverRemoteServiceMethods" expression="execution(* de.foo.server.service.ServerRemoteService*.*(..))" />

		<aop:advisor advice-ref="txAdvice" pointcut-ref="serverRemoteServiceMethods" />
	</aop:config>


Wenn ich den Code aufrufe, dann erhalte ich ganz oben, wo ich es im Kommentar angegeben habe, eine Exception, die besagt, dass er "sysConfig.getAddresses();" nicht ausführen kann:

Code:
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role:
 de.foo.domain.sysconfig.SysConfig.addresses, no session or session was closed

Das ist auch logisch, denn es handelt sich ja um ein von Hibernate erstelltes Proxy-Objekt. Die Objekte werden aus der DB lazy geladen, d.h. "getAddresses()" führt zu einer weiteren Anfrage an die DB. In dem moment ist die Hibernate Session aber gar nicht mehr da, da ich im DAO ja das "getHibernateTemplate().get" verwende.

Aber: eigentlich sollte die Methode "ServerRemoteService.getSystemConfiguration()" doch in einer Transaktion ablaufen. Täte sie das, würde die Hibernate Session über den DAO-Aufruf hinaus doch offen bleiben, und der Aufruf von "sysConfig.getAddresses()" sollte doch klappen. Dennoch sagt er mir, in der Zeile sei keine Session mehr offen.

Deshalb vermute ich, dass meine AOP Konfig mit der Transaktion nicht greift. Die ganzen Package-Angaben und Pfade habe ich natürlich schon doppelt und dreifach geprüft


Hat irgendjemand eine Idee, wieso das nicht funktioniert?


Über Eure Hilfe würde ich mich sehr freuen


Vielen Dank!
 
Zuletzt bearbeitet von einem Moderator:
Hallo,

hast du ein interface für de.foo.server.service.ServerRemoteService ?

Die Spring AOP Facility funktionieren zum einen über dynamic Proxies (das ist IMHO default) (dafür brauchst du ein entsprechendes Interface) oder über Load-time Bytecode Manipulation. Hierzu wird jedoch eine entsprechende Bibliothek asm bzw. cglib benötigt.

Mein Vorschlag:

Definiere Interfaces für deine Services und verwende in den davon Abhängigen Komponenten NUR die Service-interfaces.

Das ermöglichst dann Spring APO den dynamic Proxy basierten Ansatz zu verwenden. (Deine Komponenten bekommen dann nicht direkt die Service Implementierung "in die Hand" sondern einen Proxy der die eigentlichen Aufrufe an den Server innerhalb einer Transaktion durchführt wenn nötig).

Verwende die Annotation basierte Transaktionssteuerung...

In der Service Implementierung annotierst du die Methoden, die innerhalb einer Transaktion ausgeführt werden sollen im Code mit @Transactional(...). Bei den Methoden die ohne Transaktion laufen sollen lässt du @Transactional einfach weg.

In der Spring Konfiguration sagst du dann

wie hier im Beispiel gezeigt:
http://www.tutorials.de/forum/1786653-post12.html

Gruß Tom
 
Hallo Thomas,

vielen Dank für Deine Antwort. Ich habe nicht beachtet, dass der AOP Mechanismus nur an Schnittstellen funktioniert, wenn man keine Bytecode Manipulation machen möchte. Ich habe Schnittstellen für alle meine Service Klassen.

Um mein Beispiel zu vereinfachen und Fehlerquellen zu minimieren, habe ich nun auf AOP verzichtet, und wie in Deinem Beispiel Annotation Based Transactions gewählt.

Meine Methode sieht nun so aus:

Java:
public class ServerRemoteService {

	@Transactional
	@Override
    public SysConfig getSystemConfiguration() {
 
        SysConfig sysConfig = sysConfigDao.getSysConfig( 1 );
        if ( sysConfig == null )
            return null;
            
            
        // org.hibernate.LazyInitializationException: failed to lazily 
        // initialize a collection of role: de.foo.domain.sysconfig.
        // SysConfig.addresses, no session or session was closed
        // ->
        System.out.println( sysConfig.getAddresses() );
        
        
        return sysConfig;
}

Aus der Kontextkonfiguration habe ich jegliche AOP-Sachen entfernt. Die Datenbank/Datasource/Session relevanten Konfigurationen siehen nun wie folgt aus:

XML:
<beans
	xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:util="http://www.springframework.org/schema/util"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="
		http://www.springframework.org/schema/beans
		http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
		http://www.springframework.org/schema/util
		http://www.springframework.org/schema/util/spring-util-2.5.xsd
		http://www.springframework.org/schema/tx
		http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
		http://www.springframework.org/schema/context
		http://www.springframework.org/schema/context/spring-context-2.5.xsd"
	default-init-method="beanInit"
	default-destroy-method="beanDestroy" >

	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
		<property name="acquireRetryAttempts" value="1" />
		<property name="driverClass" value="com.mysql.jdbc.Driver" />
		<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/foo" />
		<property name="user" value="foo" />
		<property name="password" value="foo" />
	</bean>

	<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean" >
		<property name="dataSource" ref="dataSource" />
		<property name="hibernateProperties">
			<value>
				hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
				hibernate.show_sql = false
				hibernate.format_sql = true 
			</value>
		</property>
		<property name="packagesToScan">
			<list>
				<value>de.foo.domain.**.*</value>
    		</list>
		</property>
	</bean>

	<bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
		<property name="sessionFactory" ref="sessionFactory" />
	</bean>
	
	<tx:annotation-driven transaction-manager="txManager" />
	
</beans>


Un dennoch, wenn er zur Zeile mit "System.out.println( sysConfig.getAddresses() );" kommt, wirft er die Exception, dass er die Sachen nicht mehr nachladen kann, weil da wohl keine Session mehr offen ist.

Ich komme an diesem Punkt einfach nicht mehr weiter - woran könnte es denn noch liegen?


Über Deine weitere Hilfe wäre ich wirklich sehr dankbar


Vielen Dank!
 
Zuletzt bearbeitet von einem Moderator:
Hallo Tom,

die späte Antwort tut mir leid, ich war einige Tage beruflich nicht in der Lage, zu antworten. Leider habe ich immernoch dasselbe Problem...

du musst für den Service natürlich auch eine Bean-Definition hinterlegen...

Das habe ich, das obige XML ist nur ein Auszug aus meiner Kontext-Konfiguration, die eigentlich viel umfangreicher ist. Ich habe eine Bean zu definiert:

XML:
<bean id="serverRemoteService" class="de.foo.server.service.ServerRemoteService" />

Dennoch bekomme ich dieses "No Session" Fehler, ich kann mir einfach nicht erklären, warum das so ist.

Noch irgendwelche Idee, was ich ausprobieren könnte?
 
Zuletzt bearbeitet von einem Moderator:
Hallo Thomas,

ich habe jetzt nochmal Dein Beispiel aus dem anderen Thread ausprobiert (mit Person, PersonDao, PersonService) und es hat funktioniert.

Dann habe ich versucht, alles mit meinem Code zu vergleichen, konnte aber einfach keinen Unterschied erkennen. Was ich jedoch beim Testen der Transaktion gemacht habe war, die Methode, die in einer Transaktion stecken soll, über die "beanInit" Methode (welche beim Erstellen der Bean von Spring aufgerufen wird) einfach manuell aufzurufen, und dann kommt der Fehler.

Rufe ich die transaktionale Methode nicht in der beanInit auf, sondern "später" über den Remote Service, so funktioniert es.

Ich habe das Gefühl, dass dieses Transaktion-Setup zu dem Zeitpunkt der beanInit-Methode noch gar nicht abgeschlossen ist. In meiner XML Konfig kommen alle DataSource, Hibernate und txManager Beans vor der Service-Bean, aber anscheinend reicht das nicht aus. Es ist auch nicht all zu wichtig, da es ja nun funktioniert, wie es soll - dennoch würde es mich sehr interessieren, warum die Transaktion nicht greift, wenn ich die Service Methode aus der beanInit Methode heraus manuell aufrufe.

Hast Du irgendeine Idee dazu?

update

Anscheinend liegt es nicht an der Reihenfolge oder einer Zeitverzögerung. Erstelle ich einen Thread der die Transaktionale Methode erst 10 Sek später aufruft, bekomme ich dennoch den No-Session-Fehler. Möglicherweise liegt es daran, dass ich die eigene Service-Methode aus der eigenen beanInit-Methode aufrufe, und Spring dann nicht kappiert, dass die Service-Methode transaktional ablaufen muss? Es ist wirklich verwirrend, was da intern abgeht...
 
Zuletzt bearbeitet:

Neue Beiträge

Zurück