多事务管理器场景下提示"No bean named 'transactionManager' available"问题咨询
这个问题我之前在做多数据源事务配置时也碰到过,明明给@Transactional指定了slaveTransactionManager,但系统还是一个劲找默认的transactionManager,大概率是以下几个环节没配置对,咱们一步步排查:
1. 确认事务管理器的Bean名称完全匹配
首先要确保你定义的从库事务管理器Bean名称,和@Transactional里指定的完全一致(大小写敏感)。
比如你的Bean定义如果是这样的:
@Bean(name = "slaveTransactionManager") public PlatformTransactionManager slaveTransactionManager() { JpaTransactionManager tm = new JpaTransactionManager(); tm.setEntityManagerFactory(slaveEntityManagerFactory().getObject()); return tm; }
那@Transactional里必须写transactionManager = "slaveTransactionManager",不能写成SlaveTransactionManager或者其他拼写错误。如果你的Bean没指定name属性,那默认名称是方法名,所以方法名必须是slaveTransactionManager,不能随便修改。
2. 检查是否存在内部方法调用导致AOP不生效
Spring的@Transactional是基于AOP实现的,同一个类内部的方法调用不会触发AOP代理——也就是说如果你的从库方法被同一个类里的主库方法直接调用,即使你给从库方法加了@Transactional(transactionManager = "slaveTransactionManager"),这个注解也不会生效,还是会沿用外层主库的事务管理器。
举个反例:
@Service public class UserService { @Autowired private UserRepository repo; // 用默认主库事务管理器 @Transactional public void updateUser() { // 内部调用,AOP不生效,从库注解白加了 getUsersFromSlave(); } @Transactional(transactionManager = "slaveTransactionManager") public List<User> getUsersFromSlave() { return repo.findAll(); } }
解决办法有三种:
- 把从库方法抽成单独的Service Bean,通过Autowired调用;
- 注入自身的代理对象来调用:
@Service public class UserService { @Autowired private UserService selfProxy; @Transactional public void updateUser() { // 用代理对象调用,触发AOP selfProxy.getUsersFromSlave(); } @Transactional(transactionManager = "slaveTransactionManager") public List<User> getUsersFromSlave() { return repo.findAll(); } } - 开启代理暴露,用
AopContext获取代理:
先在配置类里加:
然后调用时:@Configuration @EnableTransactionManagement(exposeProxy = true) public class TransactionConfig { // ... 事务管理器配置 }((UserService) AopContext.currentProxy()).getUsersFromSlave();
3. 检查事务传播行为是否覆盖了你的指定
如果外层方法已经开启了事务(用默认主库管理器),而你的从库方法用的是默认的Propagation.REQUIRED传播行为,那么从库方法会直接加入到已有的主库事务中,不会切换到从库的事务管理器。
这种情况下,你需要给从库方法设置Propagation.REQUIRES_NEW,强制新建一个使用从库管理器的事务:
@Transactional( transactionManager = "slaveTransactionManager", propagation = Propagation.REQUIRES_NEW ) public List<User> getUsersFromSlave() { return repo.findAll(); }
4. 确认事务管理器和EntityManagerFactory的绑定是否正确
从库的事务管理器必须和从库的EntityManagerFactory绑定,不然它不知道该操作哪个数据源。检查你的配置:
@Bean(name = "slaveEntityManagerFactory") public LocalContainerEntityManagerFactoryBean slaveEntityManagerFactory() { LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); em.setDataSource(slaveDataSource()); // 绑定从库数据源 em.setPackagesToScan("com.example.entity"); em.setJpaVendorAdapter(new HibernateJpaVendorAdapter()); return em; } @Bean(name = "slaveTransactionManager") public PlatformTransactionManager slaveTransactionManager() { JpaTransactionManager tm = new JpaTransactionManager(); // 必须绑定从库的EntityManagerFactory tm.setEntityManagerFactory(slaveEntityManagerFactory().getObject()); return tm; }
如果这里错绑成主库的EntityManagerFactory,那即使指定了从库事务管理器,实际操作的还是主库,而且可能导致事务管理器不生效。
5. 检查@EnableTransactionManagement的配置
确保你的配置类上只加了一次@EnableTransactionManagement,不要重复添加。另外,不需要在这个注解里指定defaultTransactionManager,除非你想把主库设为默认,但即使设了,@Transactional里指定的transactionManager参数也会覆盖默认值。
内容的提问来源于stack exchange,提问作者NameNotFoundException

