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:
- Two Transaction Managers
mainDBTransactionManager
(marked as@Primary
)-
jpaTransactionManager
(no@Primary
) -
Repositories
-
A JPA
CouponRepository
extendingJpaRepository
(in akr.co.example.jpa
package) -
Configuration
@EnableJpaRepositories(basePackages = "kr.co.example.jpa")
- Without
transactionManagerRef = "jpaTransactionManager"
- Without
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