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?
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();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With