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

@Transactional annotation doesn't work for scanned component, again [SPR-5082] #9755

Closed
spring-projects-issues opened this issue Aug 13, 2008 · 12 comments

Comments

@spring-projects-issues
Copy link
Collaborator

@spring-projects-issues spring-projects-issues commented Aug 13, 2008

Yuwei Zhao opened SPR-5082 and commented

Please see the following example:

@Service
@Transactional
public class AccountService {

    @Autowired
    private AccountDao accountDao;

    public void updateLoginInfo(Account account) {
        accountDao.updateLoginInfo(account);
        someOtherStatementsMightThrowException();
    }
}

And everything supports the annotation is configurated:

<context:annotation-config />
<context:component-scan base-package="com.xyz" />
<tx:annotation-driven transaction-manager="transactionManager" />

We use @Service annotation on service bean, that is, it could be autowired to any bean that depends on it.

But the @Transactional annotation doesn't work, until I remove @Service annotation and explicitly define the bean in context configuration xml:

<bean id="accountService" class="com.xyz.service.AccountService" />

I'm wondering if there is any step I missed, or it supposes to work like that? Thanks.


Affects: 2.5 final

Attachments:

Issue Links:

  • #10953 Autowired can not work with @Transactional

10 votes, 16 watchers

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Aug 13, 2008

Yuwei Zhao commented

I run to similar problem. I list everything I did in detail here.

  1. create database on MySQL 5.

create database sptestDB
character set utf8;

grant select, insert, update, delete on sptestDB.* to sp@localhost identified by 'sp';

use sptestDB;

create table data
(id int unsigned not null auto_increment,
name varchar(255) not null,
primary key(id)) engine = innodb;

  1. web.xml

<?xml version="1.0" encoding="UTF-8"?>

<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" >
<display-name>Transactional</display-name>
<description>Transactional</description>

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>dispatchServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatchServlet</servlet-name>
<url-pattern>/servlet/</url-pattern>
</servlet-mapping>
<jsp-config>
<jsp-property-group>
<url-pattern>
.jsp</url-pattern>
<el-ignored>false</el-ignored>
<scripting-invalid>false</scripting-invalid>
</jsp-property-group>
</jsp-config>
<session-config>
<session-timeout>20</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
3. /WEB-INF/applicationContext.xml,

<?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: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/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/sptestDB"/>
<property name="username" value="sp"/>
<property name="password" value="sp"/>
</bean>

<bean id="jdbcTemplate"
class="org.springframework.jdbc.core.simple.SimpleJdbcTemplate">
<constructor-arg ref="dataSource" />
</bean>

<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>

context:annotation-config/

<context:component-scan base-package="com.xyz">
<context:include-filter type="regex" expression=".*DAOImpl" />
<context:include-filter type="regex" expression=".*ServiceImpl" />
</context:component-scan>

<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

  1. /WEB-INF/servlet.xml

<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">

<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
<property name="alwaysUseFullPath" value="true" />
</bean>
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="alwaysUseFullPath" value="true" />
</bean>
<bean id="xmlFileViewBaseResolver" class="org.springframework.web.servlet.view.XmlViewResolver">
<property name="location" value="/WEB-INF/views.xml" />
</bean>

<context:component-scan base-package="com.xyz">
<context:include-filter type="regex" expression=".*Controller"/>
</context:component-scan>
</beans>

  1. views.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

<bean id="jstlParentView" abstract="true"
class="org.springframework.web.servlet.view.JstlView" >
<property name="exposeContextBeansAsAttributes" value="true" />
</bean>

<bean name="default" parent="jstlParentView">
<property name="url" value="/WEB-INF/default.jsp" />
</bean>
</beans>

There are only 5 java files:

  1. ISomeDAO.java

package com.xyz;

public interface ISomeDAO {
int getCount();
int insertName(String name);
}

  1. SomeDaoImpl.java

package com.xyz;

import javax.annotation.Resource;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository("someDAO")
public class SomeDAOImpl implements ISomeDAO {
@Resource(name="jdbcTemplate")
private SimpleJdbcTemplate jdbcTemplate;

public int getCount() {
String sql = "select count(*) from data";
return jdbcTemplate.queryForInt(sql);
}

public int insertName(String name) {
String sql = "insert into data(id, name) values(default, ?)";
return jdbcTemplate.update(sql, name);
}
}

  1. ISomeService.java

package com.xyz;

public interface ISomeService {
int getCount();
int insertName(String name);
}

  1. SomeServiceImple.java

package com.xyz;

import javax.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.annotation.Isolation;

@Service("someService")
public class SomeServiceImpl implements ISomeService{
@Resource(name="someDAO")
private ISomeDAO someDAO;

@Transactional(readOnly = true)
public int getCount() {
return someDAO.getCount();
}

@Transactional(readOnly = false, isolation = Isolation.READ_COMMITTED)
public int insertName(String name) {
int count = someDAO.insertName(name);
if(count>0) throw new RuntimeException("Rollback should happen here!");
return count;
}
}

  1. SomeController.java

package com.xyz.web;

import java.util.Date;
import java.text.SimpleDateFormat;

import javax.annotation.Resource;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
import com.xyz.ISomeService;

@Controller("someController")
@RequestMapping("/servlet/controller.do")
public class SomeController {
@Resource(name="someService")
private ISomeService someService;

@RequestMapping(method = RequestMethod.GET)
public ModelAndView execute(@RequestParam(value = "name", required = false)
String name) {
if(!(name!=null && name.trim().length()>0)) {
Date d = new Date();
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
name = df.format(d);
}
someService.insertName(name);
ModelAndView mv = new ModelAndView("default");
mv.addObject("count", someService.getCount());
mv.addObject("name", name);
return mv;
}
}

In SomeServiceImpl.java, I explicitly throw a runtime exception, rollbak should happen here, but it doesn't.

If I remove @Service("someService") from SomeServiceImpl.java, and put one line at end of applicationContext.xml
<bean id="someService" class="com.xyz.SomeServiceImpl" />, it works.

I have following jars in /WEB-INF/lib:

  1. common-annotation.jar
  2. commons-logging.jar
  3. jstl.jar
  4. log4j-1.2.15.jar
  5. mysql-connector-java-5.0.6-bin.jar
  6. spring.jar
  7. spring-webmvc.jar
  8. standard.jar

I will put up all files for test.

Loading

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Aug 13, 2008

Yuwei Zhao commented

Run ant-task dist, it will build and package exploded war in dist.war directory.

Loading

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Jan 10, 2010

David Mas commented

I experiment the same issue: I have a generic crud controller, and the concrete class is annotated with @Controller. The abstract generic crud controlled has its methods annotated with @Transactional, and at runtime there are no calls to TransactionInterceptor#invoke.

Loading

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Feb 8, 2010

Lucas commented

Same problem here. I'm using spring roo, I just added a class in the scanned package, annotated it as Component and Transactional, added a method that persists an Object using JPA, it just doesn't work, no exception, the object is just not saved, the id is not assigned, if I flush the object, I get

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'systemInitializer': Invocation of init method failed; nested exception is org.springframework.dao.InvalidDataAccessApiUsageException: no transaction is in progress; nested exception is javax.persistence.TransactionRequiredException: no transaction is in progress

I tried replacing @Component with @Service and @Repository, same result.

If a I declare the bean in the xml config file, and delet the @Component annotation, it works.

Loading

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Aug 10, 2010

Marcin Muras commented

I had same problem but after added proxy-target-class="true" to <tx:annotation-driven .. everything works again. Please check this solution.

Loading

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Mar 25, 2011

Julien Dubois commented

Hi,
You just messed up your component-scanning: your "Service" bean is in fact declared both in your applicationContext.xml file, and your servlet.xml file.
So your controller uses the one from the servlet.xml file (it's in the same Web child application context to be precise). As your transactionnal aspect is only defined in your applicationContext.xml file (the parent application context), you don't have transactions in the bean instance you use.
When you switch to XML, you only declare your bean once, in the (correct) applicationContext.xml file -> so this time, the controller uses this instance, which is transactionnal.

To put it simple:
1.move your service beans to a com.xml.service package,
2.use <context:component-scan base-package="com.xyz.service"> in the applicationContext.xml file
3.use <context:component-scan base-package="com.xyz.web"> in the servlet.xml file
4.it will work :-)

You should have a look at the Spring debug logs, it will show you what happens.

Loading

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Mar 25, 2011

Stéphane Nicoll commented

another solution is to also enable tx:annotation-driven/ in the child context so that the transactional proxies get created.

Loading

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Mar 26, 2011

Yuwei Zhao commented

Reply to Julien Dubois:

You just messed up your component-scanning: your "Service" bean is in
fact declared both in your applicationContext.xml file, and your servlet.xml
file.

I have include-filter specified in my examples,

In applicationContext.xml:

<context:component-scan base-package="com.xyz">
<context:include-filter type="regex" expression=".*DAOImpl" />
<context:include-filter type="regex" expression=".*ServiceImpl" />
</context:component-scan>

In servlet.xml:

<context:component-scan base-package="com.xyz">
<context:include-filter type="regex" expression=".*Controller"/>
</context:component-scan>

You use different packages in your proposed solution and I use regular expression to select targeting beans. By principle there is no difference. The "Service" bean is not declared twice.

Loading

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Jul 17, 2012

Rohit Kanchan commented

@Yuwei Zhao.. r u able to resolve this? I am facing the same issue with Spring 3.1. I have tried different things explained here, nothing worked for me. I have removed @Service from service layer and added into the servlet xml file, still I am getting following exception. Please let me know if any one of u got solution of this. Thanks in advance.

javax.persistence.TransactionRequiredException: no transaction is in progress
at org.hibernate.ejb.AbstractEntityManagerImpl.flush(AbstractEntityManag
erImpl.java:978)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.
java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAcces
sorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEnti

Loading

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Jul 20, 2012

Yuwei Zhao commented

@Rohit Kanchan
By the book, service components shall be put into applicationContext.xml, not servlet.xml, unless you only have one IoC Container.

In my experience, if @Service annotation is removed, and <bean id="foo" class="...." /> is put into applicationContext.xml, it works fine.

Loading

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Jul 20, 2012

Gleb Schukin commented

I had the same issue. Adding mode="proxy" to tx:annotation-driven fixed it.

Loading

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Feb 23, 2013

Julien Dubois commented

Yuwei : read my comment again. This is not how include-filter work (have a look at use-default-filters="false").

This is definitely not a bug, just a configuration error.

But I agree this configuration is complex, I often get questions about this, and this is a huge source of mistakes.

My recommandation : use the simplest configuration possible, as I have in my comment, unless you really need something special.

Loading

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