Search code examples
javaspringspring-data-jpaspring-transactionsspring-autoconfiguration

Spring @Transactional not working with @NoRepository hierarchy


I have a base interface to contract a query for custom update. This is a library used in other spring applications, and in some enviroments I have a secondary "transactionManager" and also a secondary "entityManager". If this secondary transactionManager exists in spring context, my repository must to use it.

So here is the code of this base repository interface:

@NoRepository
interface MyEntityRepository extends JpaRepository<MyEntity, Integer> {

   @Query("update ....")
   @Modifying
   void updateSomething();

}

I have then two other interfaces to be defined with conditionals:

@Repository("myEntityRepository")
@Transactional(transactionManager = "sharedTransactionManager")
@ConditionalOnBean(name = "sharedTransactionManager")
interface SharedMyEntityRepository extends MyEntityRepository {
}

@Repository("myEntityRepository")
@Transactional
@ConditionalOnMissingBean(name = "sharedTransactionManager")
interface DefaultMyEntityRepository extends MyEntityRepository {
}

But when I run the MyEntityRepository.updateSomething method, I'm getting this error:

org.springframework.dao.InvalidDataAccessApiUsageException: no transaction is in progress; nested exception is javax.persistence.TransactionRequiredException: no transaction is in progress

How can I solve this? Why spring isn't applying the transaction based on the two @Repository (SharedMyEntityRepository or DefaultMyEntityRepository) ?


Solution

  • After some tests, I got it working.

    I just had to contract the @Transactional (without any transactionManager) in the base interface method. After that, at the child repository I had to contract the new transactionManager at my final interface via @Transactional(transactionManager = "sharedTransactionManager").

    Here is the code:

    @NoRepository
    interface MyEntityRepository extends JpaRepository<MyEntity, Integer> {
    
       @Transactional // this contracts the base method as transactional
       @Query("update ....")
       @Modifying
       void updateSomething();
    
    }
    
    @Repository("myEntityRepository")
    @Transactional(transactionManager = "sharedTransactionManager") // override the transactionManager
    @ConditionalOnBean(name = "sharedTransactionManager")
    interface SharedMyEntityRepository extends MyEntityRepository {
    }
    
    @Repository("myEntityRepository")
    @ConditionalOnMissingBean(name = "sharedTransactionManager")
    interface DefaultMyEntityRepository extends MyEntityRepository {
    }
    
    

    So, when the SharedMyEntityRepository.updateSomething runs, it starts a transaction using the sharedTransactionManager contracted via interface level annotation.