Describe the issue

Hello Spring Team,

My name is kimsan. I've recently encountered a common configuration pitfall when using multiple transaction managers (for example, mainDBTransactionManager for MyBatis/JDBC and jpaTransactionManager for JPA).

Even though I annotated my service with @Transactional(transactionManager = "jpaTransactionManager"), there are scenarios where the internal qualifier becomes an empty string (""), causing Spring to look for a bean named "transactionManager". This leads to an exception like:

NoSuchBeanDefinitionException: No bean named 'transactionManager' available

After investigation, I found that the root cause was that I forgot to specify transactionManagerRef = "jpaTransactionManager" in @EnableJpaRepositories, combined with having a @Primary transaction manager for MyBatis.

This configuration issue often goes unnoticed until a write operation triggers a real transaction boundary.


Sample Project / Reproducer

Below is a minimal sample illustrating how this can happen:

  1. Two Transaction Managers
  2. mainDBTransactionManager (marked as @Primary)
  3. jpaTransactionManager (no @Primary)

  4. Repositories

  5. A JPA CouponRepository extending JpaRepository (in a kr.co.example.jpa package)

  6. Configuration

  7. @EnableJpaRepositories(basePackages = "kr.co.example.jpa")
    • Without transactionManagerRef = "jpaTransactionManager"
Gradle Project Structure (Click to expand)
└── src
    ├── main
    │   ├── java
    │   │   └── kr/co/example
    │   │       ├── MyBatisConfig.java
    │   │       ├── JpaDBConfig.java
    │   │       ├── CouponRepository.java
    │   │       └── CouponService.java
    │   └── resources
    └── test
        └── java
// MyBatisConfig.java
@Configuration
public class MyBatisConfig {

    @Bean
    @Primary
    public PlatformTransactionManager mainDBTransactionManager(@Qualifier("mainDataSource") DataSource ds) {
        return new DataSourceTransactionManager(ds);
    }

}
// JpaDBConfig.java
@Configuration
@EnableJpaRepositories(
   basePackages = "kr.co.example.jpa",
   // transactionManagerRef = "jpaTransactionManager" // <-- This is missing!!
)
public class JpaDBConfig {

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(/* ... */) {
        // ...
    }

    @Bean(name = "jpaTransactionManager")
    public JpaTransactionManager jpaTransactionManager(EntityManagerFactory emf) {
        return new JpaTransactionManager(emf);
    }
}
// CouponRepository.java
@Repository
public interface CouponRepository extends JpaRepository<Coupon, Long> {
    // ...
}
// CouponService.java
@Service
@Transactional(transactionManager = "jpaTransactionManager")
public class CouponService {

    @Autowired
    private CouponRepository couponRepository;

    public void saveCoupon(Coupon coupon) {
        // On 'save', occasionally fails because it tries to use the "" manager
        couponRepository.save(coupon);
    }

    public Coupon findCoupon(Long id) {
        // Read operations often *appear* to work or skip transaction
        return couponRepository.findById(id).orElse(null);
    }
}

Steps to Reproduce

Clone or create a project with two different transaction managers: one @Primary, one for JPA.

Omit transactionManagerRef = "jpaTransactionManager" in @EnableJpaRepositories.

Run saveCoupon() in CouponService which is annotated with @Transactional(transactionManager="jpaTransactionManager").

Observe that under certain conditions (especially if there's a prior transaction opened by the primary manager or if the service boundary triggers a new transaction incorrectly), Spring attempts to look up "transactionManager" instead of "jpaTransactionManager".

Get an exception like:

org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'transactionManager' available Expected behavior

Ideally, if a JPA repository is in use and we specify @Transactional(transactionManager="jpaTransactionManager"), I'd expect Spring to either: Always respect that if jpaTransactionManager bean is available, or Provide a clear error or warning if transactionManagerRef is missing in @EnableJpaRepositories. Possible Solutions or Feature Requests

Option A: Automatic detection/fallback

If TransactionAspectSupport detects that the target class is a JPA-based repository (e.g., implements JpaRepositoryImplementation) and no explicit manager is matched, fallback to "jpaTransactionManager" if present. Pro: Helps new users avoid a common misconfiguration. Con: Potentially confusing in multi-JPA setups. Option B: Warning message or exception at startup

If @EnableJpaRepositories finds multiple PlatformTransactionManager beans but no transactionManagerRef, log a WARN-level message indicating possible misconfiguration. Pro: Does not override user config; helps them fix the setup. Con: Doesn’t “auto-fix.” Option C: Clearer documentation

Emphasize in the reference docs that “When multiple transaction managers exist, you must specify transactionManagerRef.” Pro: Straightforward approach. Con: People often skip docs. Additional Test Case

Here’s a simplified JUnit test that demonstrates how a save() call may fail if the manager is misapplied:

@SpringBootTest
class CouponServiceTest {

    @Autowired
    CouponService couponService;

    @Test
    void testSaveCoupon_ThrowsNoSuchBeanDefinitionException() {
        Coupon coupon = new Coupon("TestCoupon");
        assertThrows(NoSuchBeanDefinitionException.class, () -> {
            couponService.saveCoupon(coupon);
        });
    }
}

In a properly configured environment (with transactionManagerRef = "jpaTransactionManager"), this test should pass and save the entity. In the faulty config, it fails because Spring tries to find "transactionManager". Why This Matters

Many users combine MyBatis + JPA and run into this exact scenario. The error messages can be cryptic for newcomers, leading to extra debugging time. A built-in fallback or clear warning could greatly improve developer experience.

Is the Spring Team open to adding an auto-detection fallback? Or would you prefer a warning approach? Is there any historical context for why transactionManagerRef must be explicitly set in multi-manager scenarios without an automatic fallback? I’d be happy to contribute a PR with any of these approaches if the community finds them valuable.

Thank you for your time!

Best regards, kimsan