Multiple transaction managers NoUniqueBeanDefinitionException - java

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();
}

Related

Create DataSource lazily and transaction manager

I have a DB access module that interacts with multiple types of data sources.
Some higher modules use the DB module without interacting with JDBC data source. Therefore, I would like the data source to be defined as lazy. However, I also define a transaction manager which I can't define as lazy since it is not a direct dependency in my code. Since the transaction manager depends on the data source, the latter is always created.
#Configuration
#EnableTransactionManagement
public class SpringConfig {
#Bean(destroyMethod = "close")
#Lazy
#Autowired
public DataSource dataSource() throws Exception {
//create data source
}
#Bean
#Lazy
#Autowired
public PlatformTransactionManager txManager() throws Exception {
DataSourceTransactionManager txManager = new DataSourceTransactionManager(dataSource());
return txManager;
}
}
#Lazy
#Repository("FundRepository")
public class MyRepository {
private JdbcTemplate jdbcTemplate;
#Autowired
public void setDataSource(DataSource dataSource) {
jdbcTemplate = new JdbcTemplate(dataSource);
}
}
Any idea how to make this work?

MapJobRepositoryFactoryBean in Spring Batch uses Jdbc-based Daos, not the Map-based

I am implementing a small REST-service, which runs batch job. I am not going to persist any metadata and my job is not parallel, so I've choosed MapJobRepositoryFactoryBean. And I also have H2 in classpath for other purposes. But when I trying to run the job, I am getting the following error:
org.springframework.dao.EmptyResultDataAccessException: Incorrect result size: expected 1, actual 0
I figured out, that JobRepository bean I getting from MapJobRepositoryFactoryBean uses JdbcJobExecutionDao instead of MapJobExecutionDao. And because of that it fails on request
SELECT VERSION FROM BATCH_JOB_EXECUTION WHERE JOB_EXECUTION_ID=?
Why this happens? How should I properly configure it to use Map-based Dao?
Currently my configuration is the following:
public class ServiceConfiguration {
#Bean
public PlatformTransactionManager transactionManager() {
return new ResourcelessTransactionManager();
}
#Bean
public MapJobRepositoryFactoryBean mapJobRepositoryFactoryBean(PlatformTransactionManager transactionManager) throws Exception {
MapJobRepositoryFactoryBean mapJobRepositoryFactoryBean = new MapJobRepositoryFactoryBean(transactionManager);
return mapJobRepositoryFactoryBean;
}
#Bean(name = "inMemoryJobRepository")
public JobRepository jobRepository(MapJobRepositoryFactoryBean mapJobRepositoryFactoryBean) throws Exception {
JobRepository mapJobRepository = mapJobRepositoryFactoryBean.getObject();
return mapJobRepository;
}
#Bean(name = "syncJobLauncher")
public JobLauncher jobLauncher(#Qualifier("inMemoryJobRepository") JobRepository jobRepository) throws Exception {
SimpleJobLauncher simpleJobLauncher = new SimpleJobLauncher();
simpleJobLauncher.setJobRepository(jobRepository);
return simpleJobLauncher;
}
}
UPDATE:
As Michael Minella mentioned, the bean for custom BatchConfigurer should be created.
#Bean
public BatchConfigurer batchConfigurer(#Qualifier("inMemoryJobRepository") JobRepository jobRepository,
#Qualifier("syncJobLauncher") JobLauncher jobLauncher,
#Qualifier("resourcelessTransactionManager") PlatformTransactionManager transactionManager,
#Qualifier("inMemoryJobExplorer") JobExplorer jobExplorer){
return new InMemoryBatchConfigurer(jobRepository, transactionManager, jobLauncher, jobExplorer);
}

Access Datasource inside Bean method injected at other Bean Method

In an spring boot project, is there a way to use injected object inside a #Bean method. In my example following, isdatasourceUse() method able to acccess injected Datasource (either from dev or war profile)
#EnableScheduling
#Configuration
#EnableAspectJAutoProxy
#Profile({ "dev", "war" })
public class AppConfig {
Logger logger = LoggerFactory.getLogger(AppConfig.class);
#Autowired
DBPropertyBean dbPropertyBean;
#Bean(destroyMethod = "")
#Profile("war")
public DataSource jndiDataSource() throws IllegalArgumentException, NamingException {
JndiObjectFactoryBean bean = new JndiObjectFactoryBean();
bean.setJndiName(dbPropertyBean.getJndiName());
bean.setProxyInterface(DataSource.class);
bean.setLookupOnStartup(false);
bean.afterPropertiesSet();
return (DataSource) bean.getObject();
}
#Bean(destroyMethod = "close")
#Profile("dev")
public DataSource getDataSource() throws Exception {
com.mchange.v2.c3p0.ComboPooledDataSource ds = new com.mchange.v2.c3p0.ComboPooledDataSource();
ds.setUser(dbPropertyBean.getDsUsername());
ds.setPassword(dbPropertyBean.getDsPassword());
ds.setJdbcUrl(dbPropertyBean.getDsJdbcUrl());
ds.setDriverClass(dbPropertyBean.getDsDriverClass());
ds.setMaxPoolSize(dbPropertyBean.getDsMaxPoolSize());
ds.setMinPoolSize(dbPropertyBean.getDsMinPoolSize());
ds.setInitialPoolSize(dbPropertyBean.getDsInitPoolSize());
ds.setAcquireIncrement(dbPropertyBean.getDsAcquireInc());
ds.setAcquireRetryAttempts(dbPropertyBean.getDsAcquireRetryAtt());
ds.setPreferredTestQuery(dbPropertyBean.getPreferredTestQuery());
ds.setIdleConnectionTestPeriod(dbPropertyBean.getIdleConnectionTestPeriod());
return ds;
}
#Bean
public void datasourceUse() {
//How to user datasource here
}
}
Use it like below:
#Autowired
public void datasourceUse(DataSource dataSource) {
System.out.println(dataSource);
}

Camunda Spring Transaction Integration not working

I'm using Camunda 7.3, Spring 4.2.4 and Hibernate 4.3.8 and I'm trying to use them with the same transaction as explained in Camunda Documentation. The transaction works ok with Hibernate operations but not with Camunda operations, if a transaction rollback occurs just the hibernate operations are reverted.
#Configuration
public class CamundaConfiguration {
// Variables with connection Data
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean bean = new LocalContainerEntityManagerFactoryBean();
bean.setPersistenceUnitName("PostgreSQL");
bean.setDataSource(dataSource());
bean.getJpaPropertyMap().put("hibernate.dialect", "org.hibernate.dialect.PostgreSQL82Dialect");
bean.getJpaPropertyMap().put("hibernate.ejb.naming_strategy", NamingStrategyLowerCase.class.getCanonicalName());
bean.getJpaPropertyMap().put("hibernate.jdbc.batch_size", 0);
bean.getJpaPropertyMap().put("hibernate.cache.use_second_level_cache", true);
bean.getJpaPropertyMap().put("hibernate.cache.use_query_cache", true);
bean.getJpaPropertyMap().put("javax.persistence.sharedCache.mode", SharedCacheMode.ALL);
bean.getJpaPropertyMap().put("hibernate.cache.default_cache_concurrency_strategy", "read-write");
bean.getJpaPropertyMap().put("javax.persistence.validation.factory", validator);
bean.getJpaPropertyMap().put("hibernate.cache.region.factory_class", SingletonEhCacheRegionFactory.class.getCanonicalName());
bean.setPersistenceProviderClass(org.hibernate.jpa.HibernatePersistenceProvider.class);
bean.setPackagesToScan("br.com.model");
return bean;
}
#Bean
public JpaTransactionManager transactionManager() {
JpaTransactionManager bean = new JpaTransactionManager(entityManagerFactory());
bean.getJpaPropertyMap().put("org.hibernate.flushMode", FlushMode.AUTO);
bean.setDataSource(dataSource);
bean.setPersistenceUnitName("PostgreSQL");
return bean;
}
#Bean
public DataSource dataSource() {
HikariConfig config = new HikariConfig();
config.setDriverClassName(driverClass);
config.setJdbcUrl(jdbcUrl);
config.setUsername(username);
config.setPassword(password);
config.setMaximumPoolSize(50);
config.setConnectionTestQuery("select 1");
HikariDataSource bean = new HikariDataSource(config);
return new LazyConnectionDataSourceProxy(bean);
}
#Bean
public ManagedProcessEngineFactoryBean processEngine() {
ManagedProcessEngineFactoryBean processEngineFactoryBean = new ManagedProcessEngineFactoryBean();
processEngineFactoryBean.setProcessEngineConfiguration(processEngineConfiguration());
return processEngineFactoryBean;
}
#Bean
public SpringProcessEngineConfiguration processEngineConfiguration() {
SpringProcessEngineConfiguration processEngineConfiguration = new SpringProcessEngineConfiguration();
processEngineConfiguration.setDataSource(dataSource());
processEngineConfiguration.setTransactionManager(transactionManager());
processEngineConfiguration.setJobExecutorActivate(true);
processEngineConfiguration.setDatabaseSchemaUpdate(ProcessEngineConfigurationImpl.DB_SCHEMA_UPDATE_TRUE);
return processEngineConfiguration;
}
#Bean
public TaskService taskService() throws Exception {
return processEngine().getObject().getTaskService();
}
}
The dataSource and transactionManager is the same used by Spring and Hibernate.
#Service
public class TaskManager {
#Inject
private TaskService taskService;
#Transactional
public void completeTask(String taskId, final Map<String, Object> variables) {
org.camunda.bpm.engine.task.Task camundaTask = taskService.createTaskQuery().taskId(taskId).singleResult();
taskService.complete(camundaTask.getId(), variables);
// Hibernate Operations
throw new RuntimeException("Exception test");
}
}
When executed the code above a rollback will occur and the 'Hibernate Operations' are rollbacked but the operations executed in taskService.complete are not.
I already debugged the Camunda code and everything seems ok, I found a SpringTransactionInterceptor and the commands are executed inside a TransactionTemplate.execute() and at this point the transaction is active.
After studying about transactions, Jpa and Spring, I found out the problem was jpaDialect is not configured, it's responsible to synchronize JDBC and JTA transactions.
The dialect object can be used to retrieve the underlying JDBC
connection and thus allows for exposing JPA transactions as JDBC
transactions.
I included the following code into configuration and now it's working:
#Configuration
public class CamundaConfiguration {
....
#Bean
public JpaDialect jpaDialect() {
return new org.springframework.orm.jpa.vendor.HibernateJpaDialect();
}
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean bean = new LocalContainerEntityManagerFactoryBean();
bean.setJpaDialect(jpaDialect());
bean.setJpaVendorAdapter(new org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter());
...
return bean;
}
#Bean
public JpaTransactionManager transactionManager() {
JpaTransactionManager bean = new JpaTransactionManager(entityManagerFactory());
bean.setJpaDialect(jpaDialect());
...
return bean;
}
...
}

How do I configure multiple JPA data sources using Spring #Configuration class?

I am trying to configure multiple JPA entity/transaction managers within the same application context using Spring's #Configuration class.
When the context loads, Spring is having difficulties auto-wiring the beans because they implement the same interfaces.
Unfortunately, I'm using legacy code so I can't auto-wire the beans directly and use the #Qualifier annotations, which is why I'm trying to do it using the configuration class.
Within a #Bean declaration, is there any way to qualify which bean should be injected? I thought that using a direct method call would be enough, but it typically results in errors such as
NoUniqueBeanDefinitionException: No qualifying bean of type
[javax.sql.DataSource] is defined: expected single matching bean but
found 4
Here's an example of what I'm trying to do below:
#Configuration
public class ApplicationConfig {
#Bean(name = "transactionManager1")
public PlatformTransactionManager transactionManager1() {
return new JpaTransactionManager(entityManagerFactory1());
}
#Bean(name = "entityManagerFactory1")
public EntityManagerFactory entityManagerFactory1() {
...
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setDataSource(dataSource1());
...
}
#Bean(destroyMethod = "")
#ConfigurationProperties(prefix = "datasource.test1")
public JndiObjectFactoryBean jndiObjectFactoryBean1() {
return new JndiObjectFactoryBean();
}
#Bean(name = "dataSource1")
public DataSource dataSource1() {
JndiDataSourceLookup lookup = new JndiDataSourceLookup();
return lookup.getDataSource(jndiObjectFactoryBean1().getJndiName());
}
#Bean(name = "transactionManager2")
public PlatformTransactionManager transactionManager2() {
return new JpaTransactionManager(entityManagerFactory2());
}
#Bean(name = "entityManagerFactory2")
public EntityManagerFactory entityManagerFactory2() {
...
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setDataSource(dataSource2());
...
}
#Bean(destroyMethod = "")
#ConfigurationProperties(prefix = "datasource.test2")
public JndiObjectFactoryBean jndiObjectFactoryBean2() {
return new JndiObjectFactoryBean();
}
#Bean(name = "dataSource2")
public DataSource dataSource2() {
JndiDataSourceLookup lookup = new JndiDataSourceLookup();
return lookup.getDataSource(jndiObjectFactoryBean2().getJndiName());
}
I suppose I could try to inject the beans directly via the Spring context's getBean() method, but is there a cleaner way of doing this?
I'm not too familiar with the #Primary annotation, but based on what I've read I don't know how spring would autowire the secondary data source in this case since it looks like it would always pick the beans with #Primary first.
If you cannot change the injection sites to add qualifiers, then you're going to have to create a delegating DataSource based on some logic (which you haven't detailed in the question).
Something like this.
#Primary #Bean
public DelegatingDataSource delegatingDataSource(List<DataSource> sources) {
return new DelegatingDataSource() {
#Override
public DataSource getTargetDataSource() {
// decide which dataSource to delegate to
return sources.get(0);
}
}
}
I've used DelegatingDataSource, but that may not be able to provide what you need. You may need to get more advanced with some kind of interceptor/aspect to get details of the caller on which to base the DataSource selection.
However it's implemented, you need to specify a #Primary bean and use it as a proxy.

Categories