Hibernate, Spring und der TransactionSynchronizationManager - JUnit immer fehlerhaft

phi_2k

Erfahrenes Mitglied
Hallo Leute!! Ich weiß nicht was ich noch ausprobieren soll. Mein JUnit-Test wird mit Grün durchlaufen, auch die Daten stehen in der Datenbank, aber ich bekomme immer folgende Exception und dadurch wird der JUnit-Test im Cobertura nicht als erfolgreich anerkannt - EntwicklungsIDE ist Eclipse, genutzt wird Spring 2.0 und Hibernate 3 sowie Postgres 8.1. Ich hoffe jemand von euch kann mir weiterhelfen... :'(

2007-01-31 01:18:05,156 ERROR [org.springframework.transaction.support.TransactionSynchronizationUtils] (main) TransactionSynchronization.beforeCompletion threw exception
java.lang.IllegalStateException: No value for key [org.hibernate.impl.SessionFactoryImpl@19762f] bound to thread [main]
at org.springframework.transaction.support.TransactionSynchronizationManager.unbindResource(TransactionSynchronizationManager.java:179)
at org.springframework.orm.hibernate3.SpringSessionSynchronization.beforeCompletion(SpringSessionSynchronization.java:178)
at org.springframework.transaction.support.TransactionSynchronizationUtils.triggerBeforeCompletion(TransactionSynchronizationUtils.java:60)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.triggerBeforeCompletion(AbstractPlatformTransactionManager.java:720)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processRollback(AbstractPlatformTransactionManager.java:620)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.rollback(AbstractPlatformTransactionManager.java:608)
at org.springframework.test.AbstractTransactionalSpringContextTests.endTransaction(AbstractTransactionalSpringContextTests.java:274)
at org.springframework.test.AbstractTransactionalSpringContextTests.onTearDown(AbstractTransactionalSpringContextTests.java:222)
at org.springframework.test.AbstractSingleSpringContextTests.tearDown(AbstractSingleSpringContextTests.java:102)
at junit.framework.TestCase.runBare(TestCase.java:136)
at org.springframework.test.ConditionalTestCase.runBare(ConditionalTestCase.java:69)
at junit.framework.TestResult$1.protect(TestResult.java:110)
at junit.framework.TestResult.runProtected(TestResult.java:128)
at junit.framework.TestResult.run(TestResult.java:113)
at junit.framework.TestCase.run(TestCase.java:120)
at junit.framework.TestSuite.runTest(TestSuite.java:228)
at junit.framework.TestSuite.run(TestSuite.java:223)
at org.junit.internal.runners.OldTestClassRunner.run(OldTestClassRunner.java:35)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:38)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:460)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:673)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:386)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)





Bei der ganzen Geschichte handelt es sich um eine Hibernate-Anbindung an eine Postgres-DB und das ganze noch Spring-Managed. Hier zum Anhang die Sourcefiles:

core.beans.xml (Spring Konfiguration)
##############################################

Code:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	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.0.xsd
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
       ">

	<!--======================= Transaction setup ==========================-->
	<bean id="TxManager"
		class="org.springframework.orm.hibernate3.HibernateTransactionManager">
		<property name="sessionFactory" ref="MySessionFactory" />
	</bean>

	<!--bean id="TxManager"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="MyDataSource" />
	</bean-->
	
	<!--bean id="TxManager" 
		class="org.springframework.transaction.jta.JtaTransactionManager"/-->
	

	<tx:annotation-driven transaction-manager="TxManager" />
	
	<!--======================= datasource/hibernate setup ==========================-->
	<bean id="MyDataSource" class="org.apache.commons.dbcp.BasicDataSource"
		destroy-method="close">
		<property name="driverClassName" value="org.postgresql.Driver" />
		<property name="url" value="jdbc:postgresql:vma" />
		<property name="username" value="vma" />
		<property name="password" value="vma" />
	</bean>
	
	<bean id="MySessionFactory"
		class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
		<property name="dataSource" ref="MyDataSource" />
		<property name="configLocation" value="classpath:/hibernate.cfg.xml" />
	</bean>
	


	<!--======================= business/dao setup ==========================-->

	<bean id="UserDao" class="at.fhjoanneum.ima.sdi.iae2.vma.system.dal.hibernate.user.UserHibernateDao">
		<property name="sessionFactory" ref="MySessionFactory" />
	</bean>

	<bean id="UserService" class="at.fhjoanneum.ima.sdi.iae2.vma.system.service.pojo.user.UserServicePojo">
		<property name="dao" ref="UserDao" />
	</bean>

	<bean id="AdminService" class="at.fhjoanneum.ima.sdi.iae2.vma.system.service.pojo.admin.AdministrationServicePojo">
		<property name="dao" ref="AdminDao" />
	</bean>

	<bean id="AdminDao" class="at.fhjoanneum.ima.sdi.iae2.vma.system.dal.hibernate.admin.AdministrationHibernateDao">
		<property name="sessionFactory" ref="MySessionFactory" />
	</bean>

	<bean id="SystemService" class="at.fhjoanneum.ima.sdi.iae2.vma.system.service.pojo.system.SystemServicePojo">
		<property name="dao" ref="SystemDao" />
	</bean>
	
	<bean id="SystemDao" class="at.fhjoanneum.ima.sdi.iae2.vma.system.dal.hibernate.system.SystemHibernateDao">
		<property name="sessionFactory" ref="MySessionFactory" />
	</bean>
	
</beans>

hibernate.cfg.xml
#############################################
Code:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
		"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
		"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
				
<hibernate-configuration>
    <session-factory>
        <!-- property name="hibernate.connection.driver_class">org.postgresql.Driver</property>
        <property name="hibernate.connection.url">jdbc:postgresql:account</property>
        <property name="hibernate.connection.username">sdi</property>
        <property name="hibernate.connection.password">account</property-->

        <property name="hibernate.hbm2ddl.auto">create</property>
        <property name="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</property>
        <property name="hibernate.current_session_context_class">thread</property>
        <property name="show_sql">false</property>
        
        <mapping resource="at/fhjoanneum/ima/sdi/iae2/vma/system/dal/hibernate/Account.hbm.xml" />
        <mapping resource="at/fhjoanneum/ima/sdi/iae2/vma/system/dal/hibernate/Booking.hbm.xml" />
        <mapping resource="at/fhjoanneum/ima/sdi/iae2/vma/system/dal/hibernate/Category.hbm.xml" />
        <mapping resource="at/fhjoanneum/ima/sdi/iae2/vma/system/dal/hibernate/Expense.hbm.xml" />
        <mapping resource="at/fhjoanneum/ima/sdi/iae2/vma/system/dal/hibernate/ExpensePosition.hbm.xml" />
        <mapping resource="at/fhjoanneum/ima/sdi/iae2/vma/system/dal/hibernate/User.hbm.xml" />
        
    </session-factory>
    
    
</hibernate-configuration>
[


SystemServiceHibernateTest.java
###################################################
Code:
package at.fhjoanneum.ima.sdi.iae2.vma.system.service;

import junit.framework.JUnit4TestAdapter;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.impl.SessionFactoryImpl;
import org.junit.Before;
import org.junit.Test;
import org.springframework.orm.hibernate3.SessionFactoryUtils;
import org.springframework.orm.hibernate3.SessionHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import at.fhjoanneum.ima.sdi.iae2.vma.system.common.UserLevels;
import at.fhjoanneum.ima.sdi.iae2.vma.system.common.UserLoginStates;
import at.fhjoanneum.ima.sdi.iae2.vma.system.common.UserStates;
import at.fhjoanneum.ima.sdi.iae2.vma.system.common.exception.system.login.LoginException;
import at.fhjoanneum.ima.sdi.iae2.vma.system.common.exception.system.logoff.LogoffException;
import at.fhjoanneum.ima.sdi.iae2.vma.system.domain.User;
import at.fhjoanneum.ima.sdi.iae2.vma.system.test.AbstractTransactionalHibernateTest;

public class SystemServiceHibernateSpringTest extends
		AbstractTransactionalHibernateTest {

	@SuppressWarnings("unused")
	private SessionFactory sessionFactory;

	private UserService userService;

	private AdministrationService adminService;
	
	private SystemService systemService;

	public AdministrationService getAdminService() {
		return adminService;
	}

	public void setAdminService(AdministrationService adminService) {
		this.adminService = adminService;
	}

	public UserService getUserService() {
		return userService;
	}

	public void setUserService(UserService userService) {
		this.userService = userService;
	}
	

	public SystemService getSystemService() {
		return systemService;
	}

	public void setSystemService(SystemService systemService) {
		this.systemService = systemService;
	}

	public void setSessionFactory(SessionFactory sessionFactory) {
		this.sessionFactory = sessionFactory;
	}
	
	
	@Test 
	public void testLogin() {
		
		User newUser = new User();
		newUser.setUserFirstName("Kurt");
		newUser.setUserLastName("Gross");
		newUser.setUserLevel(UserLevels.USERLEVEL_USER);
		newUser.setUserLogin("kugo");
		newUser.setUserPassword("passwort");
		newUser.setUserIsLoggedIn(UserLoginStates.USER_LOGGED_OUT);
		newUser.setUserStatus(UserStates.USER_ACTIVE);
		this.adminService.createUser(newUser);

		//logoff without being logged in
		try {
			this.systemService.performLogoff("kugo");
		} catch (LogoffException e) {
			System.out.println("1:" + e.getMessage());			
		}
		
		//login - wrong username, password
		try {
			this.systemService.performLogin("user", "passwort");
		} catch (LoginException e) {
			System.out.println("2: " + e.getMessage());
		}
		
		//login - first successfull
		//login - multiple login, not successfull
		try {
			this.systemService.performLogin("kugo","passwort");
			this.systemService.performLogin("kugo","passwort");			
		} catch (LoginException e) {
			System.out.println("3:" + e.getMessage());
		}
		
		//logoff - successfull
		try {
			this.systemService.performLogoff("kugo");
		} catch (LogoffException e) {
			System.out.println("4:" + e.getMessage());
		}

		//logoff - multiple logoff, not successfull
		try {
			this.systemService.performLogoff("kugo");
		} catch (LogoffException e) {
		}

		
	}
	

	public static junit.framework.Test suite() {
		return new JUnit4TestAdapter(AbstractTransactionalHibernateTest.class);
	}

}


AbstractTransactionalHibernateTest.java
#####################################
Code:
public abstract class AbstractTransactionalHibernateTest extends AbstractHibernateTest {
	private Transaction currentTx;
	private boolean doCommit = false;
	//private static boolean firsttime = true;
	
	
	@Before
	@Override
	public final void internalInit() {
		try {
		// init hibernate only the first time, because we rollback every tx
		if (firsttime ) { 
			firsttime = false;			
			initHibernate();			
		}		
		currentTx = getSession().beginTransaction();
		} catch (Throwable ex) {
			ex.printStackTrace();
		}
	}
	
	@After
	public void internalAfter() {
		if (doCommit) {
			currentTx.commit();
		} else {
			// rollback as default to isolate tests
			currentTx.rollback();
		}
		doCommit = false;
		
//		currentTx.commit();
	}
	
//	@AfterClass
//	public static void internalCleanUp() {
//		try {
//			sessionFactory.close();
//		} catch (Throwable t) {
//			t.printStackTrace();
//		}
//	}
	
	/**
	 * Call this method in every test to left data in database (no rollback)
	 */
	protected void setCommit()	{
		this.doCommit = true;		
	}
}





AbstractHibernateTest.java
###########################################
Code:
public class AbstractHibernateTest extends
		AbstractTransactionalSpringContextTests {
	protected static Configuration config;

	protected static SessionFactory sessionFactory;

	protected static boolean firsttime = true;

	// @BeforeClass
	// public static void classInit() {
	// firsttime = true;
	// }
	
	@Override
	protected String[] getConfigLocations() {
		return new String[] { "at/fhjoanneum/ima/sdi/iae2/vma/system/service/core.beans.xml", };
	}

	protected void initHibernate() {
		String loc = configResourceLocation();
		if (loc != null && loc.length() > 0) {
			config = new Configuration().configure(loc);
		} else {
			config = new Configuration().configure();
		}
		/*
		 * // setup hsql in mem database
		 * config.setProperty(Environment.URL,"jdbc:hsqldb:mem:" +
		 * this.getClass().getSimpleName().toLowerCase())
		 * //.setProperty("hibernate.default_schema", "public")
		 * .setProperty(Environment.DRIVER, "org.hsqldb.jdbcDriver")
		 * .setProperty(Environment.HBM2DDL_AUTO,"create")
		 * .setProperty("hibernate.show_sql", "false")
		 * .setProperty(Environment.USER, "sa") .setProperty(Environment.PASS,
		 * "") .setProperty(Environment.DIALECT, HSQLDialect.class.getName())
		 * .setProperty(Environment.CURRENT_SESSION_CONTEXT_CLASS,
		 * ThreadLocalSessionContext.class.getName()) ;
		 */
		// call template method
		
		config.setProperty(Environment.DIALECT, PostgreSQLDialect.class.getName());		
		//config.setProperty(Environment.CURRENT_SESSION_CONTEXT_CLASS,ThreadLocalSessionContext.class.getName());
		
		config = configure(config);

		// create session factory
		sessionFactory = config.buildSessionFactory();
		
	}

	@Before
	public void internalInit() {
		try {
			if (firsttime) { // init every time
				// firsttime = false;
				initHibernate();
			}
		} catch (Throwable ex) {
			ex.printStackTrace();
		}
	}

	/**
	 * override for different resource location than /hibernate.cfg.xml
	 * 
	 * @return
	 */
	protected String configResourceLocation() {
		return "/hibernate.cfg.xml";
	}

	/**
	 * Get the current session for asserting tests
	 * 
	 * @return
	 */
	protected Session getSession() {
		return sessionFactory.getCurrentSession();
	}

	/**
	 * Template method for additional/new configuration
	 * 
	 * @param cfg
	 */
	protected Configuration configure(Configuration cfg) {
		return cfg;
	}

	/**
	 * Get session factory;
	 * 
	 * @return
	 */
	public SessionFactory getSessionFactory() {
		return sessionFactory;
	}

}
 
Warum codest du die TransactionalTest selber? Dafür gibt es in Spring die AbstractTransactionalSpringContextTests. Einfach davon ableiten und mit denen arbeiten. Hatte bisher keine Probleme damit...

@EDIT: Moment... ich seh grad, du arbeitest ja damit... warum diese Zwischenklasse? Da hebelst du die automatische Initialisierung von Hibernate aus, ebenso wie das transaktionale Verhalten. Dein eigentlicher Testcase sollte von ATSCT erben. Dann läuft jeglicher Test innerhalb der Transaktion. Der ATSCT ist eh ein Integrationstest (hat also schon Zugriff auf den ApplicationContext und initialisiert den dann natürlich auch am Anfang). Alles was du tun musst, ist mit setConfigLocation() den Pfad zu deiner applicationContext.xml angeben... der Rest läuft transaktional gegen deine Konfigurierte DataSource.

REINHAUN!
 
Zuletzt bearbeitet:
Ich erbe auch von ATST, habe aber oft Probleme wenn ich einen Datensatz geändert habe die Änderungen mit einen Query (speziell mit findById) nachzuvollziehen. Kann ich das irgendwie innerhald ber Testklasse handeln?
 
Nicht jeder OR Mapper persistiert alle Daten sofort in die Datenbank. Im Fall von Hibernate solltest du daher vor testenden Leseoperationen immer ein flush() rufen. Damit werden die Daten in die DB persistiert, und sollten auch von jeder Anfrage gefunden werden.

Gruß
Ollie
 
Zurück