0

I have two services. One loads data from DB, modify them and calls another service to persist the modified data:

@Service
@Transactional
public class CallerService {

    public void doSomething() {
        List dataList = loadData();
        for(data : dataList) {
            data.setValue("newValue");
            calledService.persistData(data);
        }
    }
}

@Service
@Transactional //##
public class CalledService {
    public void persistData(Data data){
        myDao.persistData(data);
    }
}

With such code, a new transaction is created each time CalledService.persistData is called. Howether, if I remove the line marked with "##", only one transaction is created for the whole process, allowing rollback in case any exception occurs.

  1. Is this the expected spring behavior ?
  2. Is there a way I could keep CalledService transactional and avoid new transactions to be created when going from CallerService to CalledService ?

Please note that I did try to change the propagation to "Required" and several other values on CalledService.

EDIT: Here is the stacktrace from persistData:

CalledServiceImpl.persistData(Reglement, int, Gestionnaire) line: 304   
NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]  
NativeMethodAccessorImpl.invoke(Object, Object[]) line: 57  
DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 43  
Method.invoke(Object, Object...) line: 606  
AopUtils.invokeJoinpointUsingReflection(Object, Method, Object[]) line: 317 
ReflectiveMethodInvocation.invokeJoinpoint() line: 190  
ReflectiveMethodInvocation.proceed() line: 157  
TransactionInterceptor$1.proceedWithInvocation() line: 99   
TransactionInterceptor(TransactionAspectSupport).invokeWithinTransaction(Method, Class<?>, InvocationCallback) line: 281    
TransactionInterceptor.invoke(MethodInvocation) line: 96    
ReflectiveMethodInvocation.proceed() line: 179  
JdkDynamicAopProxy.invoke(Object, Method, Object[]) line: 207   
$Proxy51.persistData(Data) line: not available  
CallerServiceImpl.doSomething(String) line: 277 
CallerServiceImpl.runBatch(String[]) line: 178  
CallerServiceImpl(BatchAbstractService).doBatch(String[]) line: 29  
NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]  
NativeMethodAccessorImpl.invoke(Object, Object[]) line: 57  
DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 43  
Method.invoke(Object, Object...) line: 606  
AopUtils.invokeJoinpointUsingReflection(Object, Method, Object[]) line: 317 
JdkDynamicAopProxy.invoke(Object, Method, Object[]) line: 201   
$Proxy95.doBatch(String[]) line: not available  
ClientBatch.process(String, String[]) line: 78  
ClientBatch.main(String[]) line: 53 

EDIT2: CallerService and CalledService are not in the same project. If I put them in the same project, the transaction is rolled back approriately when an exception occurs in the process.

Nicolas Seiller
  • 564
  • 1
  • 10
  • 20
  • you can check http://stackoverflow.com/questions/6222600/transactional-method-calling-another-method-without-transactional-anotation – Murli Nov 17 '15 at 17:17
  • Have you proved your transaction proxies are in place? i.e. your configuration is correct ? If you breakpoint into `CalledService#presistData(Data)` can you quote the stacktrace at that point? This will verify your problem is that transnational proxies are not being setup. Then you/we can debug from knowing this. – Darryl Miles Nov 17 '15 at 17:23
  • Ok we can see the Transaction stuff in the stacktrace, so.. are you sure you are leaving the outer scope of `@Transactional` at some point before you expect the data to have been committed? You say a new transaction is created each time CalledService is invoked, this is not true, there is one transaction and both Caller and Called join that one transaction. Hence why I talk about leaving the outer `@Transactional` boundary before checking committed data in DB. To have a new transaction see support for `@Transactional(propagation=Propagation.REQUIRES_NEW)` for the inner (CalledService). – Darryl Miles Nov 17 '15 at 17:55
  • Please, see my "EDIT2". – Nicolas Seiller Nov 18 '15 at 09:40
  • What does "not in the same project" mean ? What would be more important is not the IDE project (or JAR) they are in, but what context each layer is running at. For example CalledService maybe an EJB running inside an AS, but CallerService maybe in EJB client code, so there are 2 JVMs being used. Transaction management does not concern itself with your logical notation of a seperate project. – Darryl Miles Nov 18 '15 at 12:29
  • If this were a J2SE project with all code running in a single JVM with a single Spring application context and a single transaction manager. You would expect by default the inner `@Transactional` to join the outer one on CallerService. You would expect commit or rollback decision to be made when you return from the outer `@Transactional` code boundary. Anything else is kind of too complicated for a SO comment and you did not provide us with information to describe this more complex environment you may have. – Darryl Miles Nov 18 '15 at 12:33

1 Answers1

0

enforce Propagation.REQUIRED to support the current transaction instead of creating a new one. Removing the Transactional annotation is a bad practice, because the transactional behavior is demanded to the caller and the method itself is potentially not safe

Filippo Fratoni
  • 379
  • 2
  • 7
  • REQUIRED is the default: https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/transaction/annotation/Transactional.html#propagation-- – JB Nizet Nov 17 '15 at 17:18
  • If you mean by specifying `@Transactional(propagation = Propagation.REQUIRED)` on CalledService, as stated in my question, I already tried that. – Nicolas Seiller Nov 17 '15 at 17:19
  • I mean transactional annotation on methods rather than on classes like in your post – Filippo Fratoni Nov 17 '15 at 17:22
  • Like he says it is the default. His example shows he has `@Transational` already specified; and it default to REQUIRED already (as per docs) – Darryl Miles Nov 17 '15 at 17:34
  • We all understand that Propagation.REQUIRED is the default, but his code is not working so that I proposed a tentative. Enforce the @Transational annotation placing it on each method signature and not on classes declaration, like a see in the code fragment, to proof a possible change in the behavior. Keep saying that Propagation.REQUIRED is the default doesn't help much in solving the problem – Filippo Fratoni Nov 18 '15 at 07:53