spring-projects / spring-framework

Spring Framework
https://spring.io/projects/spring-framework
Apache License 2.0
56.64k stars 38.14k forks source link

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

Closed spring-projects-issues closed 10 years ago

spring-projects-issues commented 16 years ago

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:

10 votes, 16 watchers

spring-projects-issues commented 16 years ago

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" > \Transactional\ \Transactional\

\ \contextConfigLocation\ \/WEB-INF/applicationContext.xml\ \ \ \org.springframework.web.context.ContextLoaderListener\ \ \ \dispatchServlet\ \org.springframework.web.servlet.DispatcherServlet\ \ \contextConfigLocation\ \/WEB-INF/servlet.xml\ \ \1\ \ \ \dispatchServlet\ \/servlet/\ \ \ \ \.jsp\ \false\ \false\ \ \ \ \20\ \ \ \index.jsp\ \ \

  1. /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="jdbcTemplate" class="org.springframework.jdbc.core.simple.SimpleJdbcTemplate"> \ \

\<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> \ \

\ 4. /WEB-INF/servlet.xml \ \ \ \ \ \ \ \ \ \ \ \ 5. views.xml \ \ \ \ \ \ \ \ \ There are only 5 java files: 1. ISomeDAO.java package com.xyz; public interface ISomeDAO { int getCount(); int insertName(String name); } 2. 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); } } 3. ISomeService.java package com.xyz; public interface ISomeService { int getCount(); int insertName(String name); } 4. 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; } } 5. 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 \, 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.
spring-projects-issues commented 16 years ago

Yuwei Zhao commented

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

spring-projects-issues commented 14 years ago

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.

spring-projects-issues commented 14 years ago

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.

spring-projects-issues commented 14 years ago

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.

spring-projects-issues commented 13 years ago

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 in the applicationContext.xml file 3.use 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.

spring-projects-issues commented 13 years ago

Stéphane Nicoll commented

another solution is to also enable in the child context so that the transactional proxies get created.

spring-projects-issues commented 13 years ago

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:

In servlet.xml: 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.
spring-projects-issues commented 12 years ago

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

spring-projects-issues commented 12 years ago

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 \ is put into applicationContext.xml, it works fine.

spring-projects-issues commented 12 years ago

Gleb Schukin commented

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

spring-projects-issues commented 11 years ago

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.