简体   繁体   中英

second DB action freezes execution in transaction

we have been fighting with this on and off now for weeks.

first the 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:aop="http://www.springframework.org/schema/aop"
    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-3.0.xsd
     http://www.springframework.org/schema/tx
     http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
     http://www.springframework.org/schema/aop 
     http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
     http://www.springframework.org/schema/context
     http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <context:annotation-config />
    <context:component-scan base-package="com.nmsc" />
    <tx:annotation-driven proxy-target-class="true"/>

    <bean id="sessionFactory"
        class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
        <property name="configLocation">
            <value>hibernate.cfg.xml</value>
        </property>
        <property name="configurationClass">
            <value>org.hibernate.cfg.AnnotationConfiguration</value>
        </property>
    </bean>

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

</beans>

my dao class

@SuppressWarnings("unused")
@Repository("com.nmsc.hibernateDAO.SSOUserDAO")
@Transaction
public class SSOUserDAO implements SSOUserDAOSPI{
    private static Logger logger = Logger.getLogger(SSOUserDAO.class);

    @Autowired(required=true)
    private SessionFactory sessionFactory;

    @Transactional
    public Integer saveUpdate(SSOUser user) {
        sessionFactory.getCurrentSession().saveOrUpdate(user);

        return user.getIdUser();
    }

ok, now if in my junit i do this

@Test
    @Transaction
    public void testStoreRetrieve() {
        SSOUserDAOSPI userDAO = ServiceProviderContext.find(SSOUserDAOSPI.class);

        System.out.println("test");
        userDAO.deleteAllInUserTable();
        SSOUser user = buildTestUser();

        Integer id = userDAO.saveUpdate(user);

        user.setIdUser(id);

        // retrieve the user from the database
        SSOUser retrievedUser = userDAO.getByID(id);

        checkResults(user, retrievedUser);
    }

delete users works just find, but when it does the saveupdate, execution just freezes. no errors, no nothing, just freezes until you kill the process.

similarly, if i do

Integer id = userDAO.saveUpdate(user);

it will work perfectly and insert the user, but if i do

Integer id = userDAO.saveUpdate(user);
Integer id2 = userDAO.saveUpdate(user2);

it will insert the first user and freeze on the second. it did something similar before we tried to implement spring transactions (which we had to do for other parts of the system) only it would insert user, but not user2, but the hibernate log file would indicate that it did in fact insert the second user.

i can't tell if this is something with hibernate or postgres. i doubt its anything spring is doing because it did similar before we even brought spring into the picture.

EDIT TRY TWO

ok, here is my dao class

@SuppressWarnings("unused")
@Repository
public class SSOUserDAO implements SSOUserDAOSPI{
    private static Logger logger = Logger.getLogger(SSOUserDAO.class);

    @Autowired(required=true)
    private SessionFactory sessionFactory;

    public Integer saveUpdate(SSOUser user) {
        sessionFactory.getCurrentSession().saveOrUpdate(user);
        return user.getIdUser();
    }
public void deleteAllInUserTable() {
        // delete everything in the table to run the test
        logger.debug("beginning delete");
        if (sessionFactory == null){
            logger.debug("session factory null");
        } else
            logger.debug("sessionfactory not null");

        Query q = sessionFactory.getCurrentSession().createQuery("delete from SSOUser");
        q.executeUpdate();
    }

and i changed my test to this

@Test
    @Transactional(propagation=Propagation.REQUIRED)
    public void testStoreRetrieve() {

        SSOUserDAOSPI userDAO = ServiceProviderContext.find(SSOUserDAOSPI.class);

        System.out.println("test");
        userDAO.deleteAllInUserTable();
        SSOUser user = buildTestUser();

        Integer id = userDAO.saveUpdate(user);

        user.setIdUser(id);

        // retrieve the user from the database
        SSOUser retrievedUser = userDAO.getByID(id);

        checkResults(user, retrievedUser);
    }

which if im understanding you correctly is exactly what you are doing (minus the fact that its junit running it) but i get the same exception. we will probably switch to spring tests in the future but for the time being i need to get the existing tests to run, all 300 or so of them.

org.hibernate.HibernateException: No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here
    at org.springframework.orm.hibernate3.SpringSessionContext.currentSession(SpringSessionContext.java:63)
    at org.hibernate.impl.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:687)
    at com.nmsc.hibernateDAO.SSOUserDAO.deleteAllInUserTable(SSOUserDAO.java:121)
    at com.nmsc.hibernateDAO.SSOUserDAO_Test.testStoreRetrieve(SSOUserDAO_Test.java:55)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
    at org.junit.runners.BlockJUnit4ClassRunner.runNotIgnored(BlockJUnit4ClassRunner.java:79)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:71)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

the part thats driving me nuts is my initialization log shows this

 Returning cached instance of singleton bean 'org.springframework.transaction.config.internalTransactionAdvisor'
1651 [main] DEBUG org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor  - Autowiring by type from bean name 'SSOUserDAO' to bean named 'sessionFactory'
1651 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory  - Returning cached instance of singleton bean 'org.springframework.transaction.config.internalTransactionAdvisor'
1653 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory  - Finished creating instance of bean 'SSOUserDAO'

that tells me that spring is fully aware that the bean is managed by the transaction manager. so when i get an instance of the bean the transaction manager should know this, but it appears that it doesn't.

EDIT TRY THREE

ok, i changed my test to this

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations =
{
    "classpath:transaction-service.xml"
})
public class SSOUserDAO_Test extends JunitHelperClass {
    private static Logger logger = Logger.getLogger(SSOUserDAO_Test.class);

    @Resource
    SSOUserDAOSPI userDAO;

    @Test
    @Transactional(propagation=Propagation.REQUIRED)
    public void testStoreRetrieve() {

        //SSOUserDAOSPI userDAO = ServiceProviderContext.find(SSOUserDAOSPI.class);

        System.out.println("test");
        userDAO.deleteAllInUserTable();
        SSOUser user = buildTestUser();

        Integer id = userDAO.saveUpdate(user);

        user.setIdUser(id);

        // retrieve the user from the database
        SSOUser retrievedUser = userDAO.getByID(id);

        checkResults(user, retrievedUser);
    }

and i am back to the same lockup when it tries to insert the user. delete works fine, user flops.

I'm not sure what @Transaction is, but between that an the @Transactional you've scattered all over, it wouldn't be too surprising if you're having transaction deadlocks. Unless specifically designed otherwise, a particular chain of method calls corresponding to a user's request should typically happen within exactly one transaction, and the transaction shouldn't occur at the DAO level, but at some level above it. A transaction should generally span from when a user requests some action across all the loading from the database, business logic, and saving to the database required to fulfill that action, and only end when a particular user's request for some action or other has been satisfied. Straighten out your transaction management, and your problem will likely be resolved.

Edit: As an example, somewhere you should have a method that should go something like this:

@Transactional
public void modifyUser(int userId, User newUser) {
    validateInput(newUser);
    User existingUser = userDao.load(userId);
    copyUpdateableProperties(newUser, existingUser);
}

Note that

  • This isn't a DAO. It uses a DAO.
  • The transaction is established here and not on the DAO.
  • There's no explicit "save" call, as when an object is loaded and modified within the scope of a transaction, the modifications are saved upon the transaction's being committed.

Further and unrelated, though potentially contributing to the problem, the line userDAO.deleteAllInUserTable(); in your test is a distinct code smell. First, something like this belongs in a setup method, as it's preparing the environment for your test. Second, a much cleaner approach is to run each test within a transaction that's rolled back at the end of the test--functionality you get for free when using the Spring test framework .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM