Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 895
g00glen00b Avatar asked Jan 07 '16 13:01

g00glen00b


1 Answers

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();
}
like image 167
František Hartman Avatar answered Sep 28 '22 07:09

František Hartman