0

Let's say I have a simple controller

RequestMapping(value = "api/v1")
@RestController
public class FooController {

    private final FooService fooService;

    FooController(FooService fooService) {
        this.fooService = fooService;
    }

    @PostMapping("foos")
    @Transactional
    FooDTO createFoo( @RequestBody FooCreationDTO newFoo ) {
        Foo foo = fooService.createFoo( ... );

        return asDTO(foo);
    }

    @PostMapping("foos/op/delete-bulk")
    @Transactional
    void deleteBulk(@RequestBody List<String> codes) {
        fooService.deleteFoosByCodes(codes);
    }
}

That I'm connecting to with some retrofit2 client, where

public void FooService.deleteFoosByCodes(List<String> codes) {
  if (codes.isEmpty()) {
      return;
  }
  fooRepository.deleteMatching(codes);
}

and

@Repository
public interface FooRepository extends JpaRepository<Foo, Long> {

    @Modifying
    @Query("DELETE FROM Foo f WHERE f.code IN :fooCodes")
    void deleteMatching(@Param("fooCodes") List<String> fooCodes);
}

createFoo receives its transaction as required,

deleteBulk throws an exception:

Exception Description: No transaction is currently active; nested exception is javax.persistence.TransactionRequiredException: 
Exception Description: No transaction is currently active
    at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:402)
    at org.springframework.orm.jpa.DefaultJpaDialect.translateExceptionIfPossible(DefaultJpaDialect.java:127)
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:527)
    at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
    at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:153)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:138)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:93)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:61)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
    at com.sun.proxy.$Proxy201.deleteMatching(Unknown Source)
    at ch.cypherk.myapp.server.domain.FooService.deleteFoosByIDs(FooService.java:198)
    at ch.cypherk.myapp.server.api.v1.FooController.deleteBulk(FooController.java:63)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:198)
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:752)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
  at ...
Caused by: javax.persistence.TransactionRequiredException: 
Exception Description: No transaction is currently active
    at org.eclipse.persistence.internal.jpa.transaction.EntityTransactionWrapper.throwCheckTransactionFailedException(EntityTransactionWrapper.java:89)
    at org.eclipse.persistence.internal.jpa.transaction.EntityTransactionWrapper.checkForTransaction(EntityTransactionWrapper.java:52)
    at org.eclipse.persistence.internal.jpa.EntityManagerImpl.checkForTransaction(EntityManagerImpl.java:2191)
    at org.eclipse.persistence.internal.jpa.QueryImpl.executeUpdate(QueryImpl.java:295)
    at org.springframework.data.jpa.repository.query.JpaQueryExecution$ModifyingExecution.doExecute(JpaQueryExecution.java:256)
    at org.springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.java:91)
    at org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:136)
    at org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:125)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:605)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.lambda$invoke$3(RepositoryFactorySupport.java:595)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:595)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at ...

WHY?!

The invocation works if I put an @Transactional on the service method, too, but it has no business being there.

Would appreciate some help in understanding what causes this error.

User1291
  • 7,664
  • 8
  • 51
  • 108
  • 5
    Controller should not be transactional, good explanation there: https://stackoverflow.com/questions/23118789/why-we-shouldnt-make-a-spring-mvc-controller-transactional – Anne Aug 14 '19 at 14:08
  • 3
    Use @Transactional on your service class or method not on controller. The controller responsibility is to get the parameter requests, and then call one or more service methods – IMParasharG Aug 14 '19 at 14:09
  • 1
    @anne still link state it can be done – Ori Marko Aug 14 '19 at 14:54
  • Also use `@Transactional` annotation only with public methods! – Kamil W Aug 14 '19 at 19:47
  • @Anne there's nothing in the post you link to saying the controller MUSTN'T be transactional. In fact, there's also an answer claiming you SHOULD make it transactional. – User1291 Aug 14 '19 at 20:12
  • @GovindParashar that's your opinion, thank you for sharing, but not really relevant to the question. – User1291 Aug 14 '19 at 20:13

0 Answers0