Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MethodInterceptor causes integration test to commit changes to database [SPR-9158] #13796

Closed
spring-issuemaster opened this issue Feb 23, 2012 · 8 comments

Comments

@spring-issuemaster
Copy link
Collaborator

@spring-issuemaster spring-issuemaster commented Feb 23, 2012

Guillermo De Luca opened SPR-9158 and commented

Overview

The following Spring AOP method interceptor (MethodLoggingInterceptor) causes a Hibernate-based DAO test case to commit changes to the database even though the transaction manged by the Spring TestContext framework is rolled back.


Code

Regarding the testCreateRemoveHunter() method in SampleDaoImplTest:

  • The test works if the method interceptor is not enabled.
  • The test works if only one operation is performed per test method.
  • The logs confirm that the Spring TestContext Framework properly rolls back the transaction after the test method.

public class MethodLoggingInterceptor implements MethodInterceptor {

    private final Logger logger = Logger.getLogger(this.getClass().toString());

    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {

        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        Object result = methodInvocation.proceed();

        stopWatch.stop();

        StringBuilder logMessage = new StringBuilder();
        // build logMessage ...
	logger.info(logMessage.toString());

        return result;
    }
}
<bean name="methodLoggingInterceptor" class="com.kohls.listing.das.interceptor.MethodLoggingInterceptor"/>

<bean name="proxyCreator" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
    <property name="beanNames">
        <list>
            <value>*</value>
        </list>
     </property>
     <property name="interceptorNames">
        <list>
            <value>methodLoggingInterceptor</value>
        </list>
     </property>
</bean>

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="${database.driver}"/>
        <property name="url" value="${database.url}"/>
        <property name="username" value="${database.username}"/>
        <property name="password" value="${database.password}"/>
        <property name="defaultCatalog" value="${hibernate.default_schema}"/>
</bean>

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

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.jdbc.batch_size">100</prop>
                <prop key="hibernate.show_sql">true</prop>
                <prop key="hibernate.connection.autocommit">false</prop>
                <prop key="hibernate.dialect">${database.dialect}</prop>
            </props>
        </property>
        <property name="configLocation" value="hibernate.cfg.xml"/>
</bean>
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath*:applicationContext.xml")
public class SampleDaoImplTest {

    @Autowired
    HunterDao hunterDao;

    @Test
    @Transactional
    public void testCreateRemoveHunter() {
        Hunter hunter = new Hunter();
        hunterDao.createHunter(hunter);
        hunterDao.deleteHunter(hunter);
    }

    @AfterTransaction
    public void afterTransaction() {
        // if you query the state of the database, the hunter table
        // contains the Hunter entity created in testCreateRemoveHunter(),
        // but it shouldn't since the transaction for the test was rolled
        // back by the Spring TestContext Framework.
    }
}

Analysis

The basic cause of the problem is that the MethodInterceptor is applied to every bean in the context by specifying "*" for the beanNames property of BeanNameAutoProxyCreator. This results in the interceptor being applied not only to application beans but also to the Hibernate SessionFactory.

If the beanNames property of BeanNameAutoProxyCreator is changed to something less greedy than "*" -- for example, "*Dao" -- the sessionFactory will not be advised by the method interceptor, and the code will work as expected.

As for why the SessionFactory fails to operate as expected when advised by a MethodInterceptor, that remains to be determined, but it is assumed that Spring's Hibernate support fails to properly manage the Session for the current thread if the SessionFactory is hidden behind a dynamic proxy, which is the case when a MethodInterceptor is applied to every bean in the application context.


Affects: 3.0.5, 3.1.2

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Feb 26, 2012

Chris Beams commented

Added Sam Brannen as a watcher

@Sam, it would be great if you could take a look at this issue. Please assign to yourself, triage and schedule appropriately if you have time.

@Guillermo, a reproduction project would really help move this along. Consider following the instructions at https://github.com/SpringSource/spring-framework-issues#readme.

Thanks!

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Feb 28, 2012

Guillermo De Luca commented

I've added an example app to github

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Feb 29, 2012

Chris Beams commented

@Guillermo - please past the URL to the pull request. I'm not seeing it.

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Feb 29, 2012

Guillermo De Luca commented

git@github.com:gdeluca/SPR-9158.git

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Feb 29, 2012

Chris Beams commented

Thanks, Guillermo; we'll take a look. In the future, consider submitting such projects as pull requests against https://github.com/SpringSource/spring-framework-issues. This streamlines things for us quite a bit. In any case, thanks for the project.

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Jul 30, 2012

Sam Brannen commented

Hi Guillermo,

I have determined the basic cause of the problem you're experiencing: you are applying the MethodInterceptor to every bean in the context by specifying "*" for the beanNames property of BeanNameAutoProxyCreator. This results in the interceptor also being applied to the Hibernate SessionFactory.

Thus, if you change your BeanNameAutoProxyCreator definition to the following -- note the "*Dao" pattern -- the sessionFactory will not be advised by the method interceptor, and your code will work as expected.

<bean name="proxyCreator" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
    <property name="beanNames">
        <list>
            <value>*Dao</value>
        </list>
     </property>
     <property name="interceptorNames">
        <list>
            <value>methodLoggingInterceptor</value>
        </list>
     </property>
</bean>

Why are you applying your MethodLoggingInterceptor to every bean in the context instead of just your application code?

Is this a requirement for your use case?

Thanks in advance for feedback!

Sam

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Jul 31, 2012

Guillermo De Luca commented

Thanks Sam, that's enough for my case. You can close this bug if's ok for you

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Jul 31, 2012

Sam Brannen commented

Resolving as "Works as Designed" as explained in the Analysis section:

Analysis

The basic cause of the problem is that the MethodInterceptor is applied to every bean in the context by specifying "*" for the beanNames property of BeanNameAutoProxyCreator. This results in the interceptor being applied not only to application beans but also to the Hibernate SessionFactory.

If the beanNames property of BeanNameAutoProxyCreator is changed to something less greedy than "*" -- for example, "*Dao" -- the sessionFactory will not be advised by the method interceptor, and the code will work as expected.

As for why the SessionFactory fails to operate as expected when advised by a MethodInterceptor, that remains to be determined, but it is assumed that Spring's Hibernate support fails to properly manage the Session for the current thread if the SessionFactory is hidden behind a dynamic proxy, which is the case when a MethodInterceptor is applied to every bean in the application context.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
2 participants
You can’t perform that action at this time.