Dimitri Mestdagh
Dimitri Mestdagh

Reputation: 44755

Multiple transaction managers NoUniqueBeanDefinitionException

I'm using Spring boot and I defined the spring.datasource.* properties to enable my datasource. If I only use this it works fine. However, I'm now trying to add JMS to my application as well, using the following config:

@Configuration
@EnableJms
public class TriggerQueueConfig {
    private Logger logger = LoggerFactory.getLogger(getClass());
    @Value("${jms.host:localhost}")
    private String host;
    @Value("${jms.port:1414}")
    private int port;
    @Value("${jms.concurrency.min:3}-${jms.concurrency.max:10}")
    private String concurrency;
    @Value("${jms.manager}")
    private String queueManager;
    @Value("${jms.cache:100}")
    private int cacheSize;

    @Bean
    public JmsListenerContainerFactory<?> jmsListenerContainerFactory() throws JMSException {
        logger.debug("Setting queue concurrency to {} (min-max)", concurrency);
        DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
        factory.setConnectionFactory(cachedConnectionFactory());
        factory.setMessageConverter(messageConverter());
        factory.setTransactionManager(transactionManager());
        factory.setSessionTransacted(true);
        factory.setConcurrency(concurrency);
        return factory;
    }

    @Bean(name = "jmsTransactionManager")
    public JmsTransactionManager transactionManager() throws JMSException {
        JmsTransactionManager transactionManager = new JmsTransactionManager();
        transactionManager.setConnectionFactory(cachedConnectionFactory());
        return transactionManager;
    }

    @Bean
    @Primary
    public ConnectionFactory cachedConnectionFactory() throws JMSException {
        CachingConnectionFactory connectionFactory = new CachingConnectionFactory(ibmConnectionFactory());
        connectionFactory.setSessionCacheSize(cacheSize);
        connectionFactory.setCacheConsumers(true);
        return connectionFactory;
    }

    @Bean
    public ConnectionFactory ibmConnectionFactory() throws JMSException {
        logger.debug("Connecting to queue on {}:{}", host, port);
        MQQueueConnectionFactory connectionFactory = new MQQueueConnectionFactory();
        connectionFactory.setHostName(host);
        connectionFactory.setPort(port);
        connectionFactory.setQueueManager(queueManager);
        connectionFactory.setTransportType(WMQConstants.WMQ_CM_CLIENT);
        return connectionFactory;
    }

    @Bean
    public MessageConverter messageConverter() {
        MarshallingMessageConverter converter = new MarshallingMessageConverter();
        converter.setMarshaller(marshaller());
        converter.setUnmarshaller(marshaller());
        return converter;
    }

    @Bean
    public Jaxb2Marshaller marshaller() {
        Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
        marshaller.setPackagesToScan("com.example");
        return marshaller;
    }
}

The JMS listener I created is working fine. However, when I'm trying to persist data using my repository (Spring Data JPA) in a @Transactional method, I'm getting the following exception:

org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [org.springframework.transaction.PlatformTransactionManager] is defined: expected single matching bean but found 2: transactionManager,jmsTransactionManager

This makes sense, because both transactionmanagers are PlatformTransactionManager's. Usually you would put @Primary on top of the bean that should be the default one. However, in this case I'm using Spring boot's autoconfiguration so I can't add the @Primary on it.

An alternative solution would be to provide the name of the transaction manager with each @Transactional annotation (for example @Transactional("transactionManager"), but this would be a lot of work, and it would make more sense to have a default transactionmanager because the JMS transactionmanager is an exceptional case.

Is there an easy way to define the automatically configured transactionmanager to be used by default?

Upvotes: 5

Views: 4906

Answers (1)

František Hartman
František Hartman

Reputation: 15086

The Spring boot 'magic' is really only this:

@Bean
@ConditionalOnMissingBean(PlatformTransactionManager.class)
public PlatformTransactionManager transactionManager() {
    return new JpaTransactionManager();
}

in org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration class.

Notice the @ConditionalOnMissingBean annotation - this will get configured only if a bean of type PlatformTransactionManager doesn't exist. So you can override this by creating your own bean with @Primary annotation:

@Bean
@Primary
public PlatformTransactionManager transactionManager() {
    return new JpaTransactionManager();
}

Upvotes: 6

Related Questions