Multiple transaction managers - Selecting a one at runtime - Spring - java

I am using Spring to configure transactions in my application. I have two transaction managers defined for two RabbitMQ servers.
....
#Bean(name = "devtxManager")
public PlatformTransactionManager devtxManager() {
return new RabbitTransactionManager(devConnectionFactory());
}
#Bean(name = "qatxManager")
public PlatformTransactionManager qatxManager() {
return new RabbitTransactionManager(qaConnectionFactory());
}
#Bean
public ConnectionFactory devConnectionFactory() {
CachingConnectionFactory factory = new CachingConnectionFactory();
factory.setHost(propertyLoader.loadProperty("dev.rabbit.host"));
factory.setPort(Integer.parseInt(propertyLoader.loadProperty("dev.rabbit.port")));
factory.setVirtualHost("product");
factory.setUsername(propertyLoader.loadProperty("dev.sender.rabbit.user"));
factory.setPassword(propertyLoader.loadProperty("dev.sender.rabbit.password"));
return factory;
}
#Bean
public ConnectionFactory qaConnectionFactory() {
CachingConnectionFactory factory = new CachingConnectionFactory();
factory.setHost(propertyLoader.loadProperty("qa.rabbit.host"));
factory.setPort(Integer.parseInt(propertyLoader.loadProperty("qa.rabbit.port")));
factory.setVirtualHost("product");
factory.setUsername(propertyLoader.loadProperty("qa.sender.rabbit.user"));
factory.setPassword(propertyLoader.loadProperty("qa.sender.rabbit.password"));
return factory;
}
...
In my service class I need to pick the right transaction manager by the 'env' variable passed in. ( i.e if env=='qa' I need to choose 'qatxManager' else if 'env==dev' I need to choose 'devtxManager'.
....
#Transactional(value = "qatxManager")
public String requeue(String env, String sourceQueue, String destQueue) {
// read from queue
List<Message> messageList = sendReceiveImpl.receive(env, sourceQueue);
....
How can I get it done?

I think you need a Facade. Define an interface and create 2 classes implementing the same interface but with different #Transactional(value = "qatxManager")
Then define one Facade class which keeps 2 implementations (use #Qualifier to distinguish them) The Facade gets the env String and call method of proper bean

Related

How to create a rabbitmq Queue in Spring Boot but without using #Bean

In My Scenario I need to create a lots of queues dynamically at run time that is why I don't want to use #Bean instead want to write a function that create queue and I will call it whenever necessary.
Here When i use #bean annotation it creates queue on rabbitmq server.
#Bean
public Queue productQueue(final String queueName) {
return new Queue(queueName);
}
But with the same code without #Bean
public Queue productQueue(final String queueName) {
return new Queue(queueName);
}
when call this function doesn't create queue on rabbitmq server
Queue queue = <Object>.productQueue("product-queue");
To create rabbitmq queue Dynamically I used following approach and this is best approach if you also want to create exchanges and bind to queue.
#Autowired
private ConnectionFactory connectionFactory;
#Bean
public AmqpAdmin amqpAdmin() {
return new RabbitAdmin(connectionFactory);
}
Now you can define a class that creates queue, exchange and bind them
public class rabbitHelper {
#Autowired
private RabbitAdmin rabbitAdmin;
public Queue createQueue(String queueName) {
Queue q = new Queue(queueName);
rabbitAdmin.declareQueue(q);
return q;
}
public Exchange createExchange(String exchangeName) {
Exchange exchange = new DirectExchange(exchangeName);
rabbitAdmin.declareExchange(exchange);
return exchange;
}
public void createBinding(String queueName, String exchangeName, String routingKey) {
Binding binding = new Binding(queueName, Binding.DestinationType.QUEUE, queueName, routingKey, null);
rabbitAdmin().declareBinding(binding);
}
}
The Queue object must be a bean in the context and managed by Spring. To create queues dynamically at runtime, define the bean with scope prototype:
#Bean
#Scope("prototype")
public Queue productQueue(final String queueName) {
return new Queue(queueName);
}
and create queues at runtime using ObjectProvider:
#Autowired
private ObjectProvider<Queue> queueProvider;
Queue queue1 = queueProvider.getObject("queueName1");
Queue queue2 = queueProvider.getObject("queueName2");

Cant resolve symbol SimpleNativeJdbcExtractor [duplicate]

My Project is on spring-boot-starter-parent - "1.5.9.RELEASE" and I'm migrating it to spring-boot-starter-parent - "2.3.1.RELEASE".
This is multi-tenant env application, where one database will have multiple schemas, and based on the tenant-id, execution switches between schemas.
I had achieved this schema switching using SimpleNativeJdbcExtractor but in the latest Springboot version NativeJdbcExtractor is no longer available.
Code snippet for the existing implementation:
#Bean
#Scope(
value = ConfigurableBeanFactory.SCOPE_PROTOTYPE,
proxyMode = ScopedProxyMode.TARGET_CLASS)
public JdbcTemplate jdbcTemplate() {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
SimpleNativeJdbcExtractor simpleNativeJdbcExtractor = new SimpleNativeJdbcExtractor() {
#Override
public Connection getNativeConnection(Connection con) throws SQLException {
LOGGER.debug("Set schema for getNativeConnection "+Utilities.getTenantId());
con.setSchema(Utilities.getTenantId());
return super.getNativeConnection(con);
}
#Override
public Connection getNativeConnectionFromStatement(Statement stmt) throws SQLException {
LOGGER.debug("Set schema for getNativeConnectionFromStatement "+Utilities.getTenantId());
Connection nativeConnectionFromStatement = super.getNativeConnectionFromStatement(stmt);
nativeConnectionFromStatement.setSchema(Utilities.getTenantId());
return nativeConnectionFromStatement;
}
};
simpleNativeJdbcExtractor.setNativeConnectionNecessaryForNativeStatements(true);
simpleNativeJdbcExtractor.setNativeConnectionNecessaryForNativePreparedStatements(true);
jdbcTemplate.setNativeJdbcExtractor(simpleNativeJdbcExtractor);
return jdbcTemplate;
}
Here Utilities.getTenantId() ( Stored value in ThreadLocal) would give the schema name based on the REST request.
Questions:
What are the alternates to NativeJdbcExtractor so that schema can be dynamically changed for JdbcTemplate?
Is there any other way, where while creating the JdbcTemplate bean I can set the schema based on the request.
Any help, code snippet, or guidance to solve this issue is deeply appreciated.
Thanks.
When I was running the application in debug mode I saw Spring was selecting Hikari Datasource.
I had to intercept getConnection call and update schema.
So I did something like below,
Created a Custom class which extends HikariDataSource
public class CustomHikariDataSource extends HikariDataSource {
#Override
public Connection getConnection() throws SQLException {
Connection connection = super.getConnection();
connection.setSchema(Utilities.getTenantId());
return connection;
}
}
Then in the config class, I created bean for my CustomHikariDataSource class.
#Bean
public DataSource customDataSource(DataSourceProperties properties) {
final CustomHikariDataSource dataSource = (CustomHikariDataSource) properties
.initializeDataSourceBuilder().type(CustomHikariDataSource.class).build();
if (properties.getName() != null) {
dataSource.setPoolName(properties.getName());
}
return dataSource;
}
Which will be used by the JdbcTemplate bean.
#Bean
#Scope(
value = ConfigurableBeanFactory.SCOPE_PROTOTYPE,
proxyMode = ScopedProxyMode.TARGET_CLASS)
public JdbcTemplate jdbcTemplate() throws SQLException {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
return jdbcTemplate;
}
With this approach, I will have DataSource bean created only once and for every JdbcTemplate access, the proper schema will be updated during runtime.
There's no need to get rid of JdbcTemplate. NativeJdbcExtractor was removed in Spring Framework 5 as it isn't needed with JDBC 4.
You should replace your usage of NativeJdbcExtractor with calls to connection.unwrap(Class). The method is inherited by Connection from JDBC's Wrapper.
You may also want to consider using AbstractRoutingDataSource which is designed to route connection requests to different underlying data sources based on a lookup key.

spring transaction timeout configurable

I have a transactional method which has a fixed timeout. Is there a way to make a transaction timeout configurable through i.e. an application.yml?
#Transactional(propagation = Propagation.REQUIRED, timeout = TIMEOUT)
public String doStuff(String id) throws Exception {
service.doSomeStuff
}
As we cannot assign variable value to Java annotation attribute , to programmatically set #Transactional 's timeout , your best bet is to override AbstractPlatformTransactionManager#determineTimeout().
Suppose you are using JpaTransactionManager, the new manager looks like the code below. It allows to set timeout per transaction. We can use TransactionDefinition 's name to identify a transaction ,which in case of Spring declarative transaction ,the default name is in the format of FullyQualifiedClassName.MethodName.
public class FooTransactionManager extends JpaTransactionManager {
private Map<String, Integer> txTimeout = new HashMap<String, Integer>();
public <T> void configureTxTimeout(Class<T> clazz, String methodName, Integer timeoutSecond) {
txTimeout.put(clazz.getName() + "." + methodName, timeoutSecond);
}
//The timeout set by `configureTxTimeout` will have higher priority than the one set in #Transactional
#Override
protected int determineTimeout(TransactionDefinition definition) {;
if (txTimeout.containsKey(definition.getName())) {
return txTimeout.get(definition.getName());
} else {
return super.determineTimeout(definition);
}
}
}
Then configure the PlatformTransactionManager:
#Bean
public PlatformTransactionManager transactionManager(final EntityManagerFactory emf) {
final FooTransactionManager transactionManager = new FooTransactionManager();
transactionManager.setEntityManagerFactory(emf);
transactionManager.configureTxTimeout(Foo.class, "doStuff", 10);
transactionManager.configureTxTimeout(Bar.class, "doStuff", 20);
transactionManager.configureTxTimeout(Bar.class, "doMoreStuff", 30);
//blablabla
return transactionManager;
}
The codes above is just for demonstration purpose . In reality , you can use #Value to read the value from an external properties (e.g application.yml) during the configuration.
Update On 25-Jun-2020 :
It will be supported out of the box in the coming 5.3 (See this)

Create/bind and unbind/delete rabbitmq queue in Spring constantly

I am now working on a project with rabbitmq, Spring, and Hibernate, where once my program is running, the user can change a boolean field in the database.
If true, my program will create a new queue and bind an predetermined exchange to it.
If false, my program will unbind the queue and delete it.
However, the tutorials I have seen all seem to use annotation to create the queue and the binding when the program first runs:
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
#Profile({"tut3", "pub-sub", "publish-subscribe"})
#Configuration
public class Tut3Config {
#Bean
public FanoutExchange fanout() {
return new FanoutExchange("tut.fanout");
}
#Profile("receiver")
private static class ReceiverConfig {
#Bean
public Queue autoDeleteQueue1() {
return new AnonymousQueue();
}
#Bean
public Queue autoDeleteQueue2() {
return new AnonymousQueue();
}
#Bean
public Binding binding1(FanoutExchange fanout,
Queue autoDeleteQueue1) {
return BindingBuilder.bind(autoDeleteQueue1).to(fanout);
}
#Bean
public Binding binding2(FanoutExchange fanout,
Queue autoDeleteQueue2) {
return BindingBuilder.bind(autoDeleteQueue2).to(fanout);
}
#Bean
public Tut3Receiver receiver() {
return new Tut3Receiver();
}
}
#Profile("sender")
#Bean
public Tut3Sender sender() {
return new Tut3Sender();
}
}
In my case, rather than using annotations, should I implement interfaces such as AmqpAdmin and use methods such as declareQueue() and deleteQueue explicitly to be able to create and delete queue constantly?
If that's the case, does Spring have a specific place in projects to implment those methods?
Thanks.
In your example, the queues beans autoDeleteQueue1 and autoDeleteQueue1 are created with scope singleton and so they live within Spring container for a whole app lifecycle.
If you need more flexible way to handle queue beans, you can use the default implementation of AmqpAdmin, like it's described below.
if (condition) {
Queue queue = admin.declareQueue(new Queue("queueOne"));
admin.declareBinding(new Binding(queue, fanout))
} else {
admin.removeBindings(new Binding(new Queue("queueOne"), fanout))
}
You may want to put this code into some Service or whatever the class which handles this logic. Within that service you may wire the beans
#Bean
public ConnectionFactory connectionFactory() {
return new CachingConnectionFactory();
}
#Bean
public AmqpAdmin admin() {
return new RabbitAdmin(connectionFactory());
}
#Bean
public FanoutExchange fanout() {
return new FanoutExchange("tut.fanout");
}
More details can be found here
Here's also nice article with nice examples
If you look at the tutorial for RabbitMQ using Java, you will find #2 and #5 tutorial apply to your case.
For example, to create a queue you could use this:
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null);
To create binding, you can do this:
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
String queueName = channel.queueDeclare().getQueue();
if (argv.length < 1) {
System.err.println("Usage: ReceiveLogsTopic [binding_key]...");
System.exit(1);
}
for (String bindingKey : argv) {
channel.queueBind(queueName, EXCHANGE_NAME, bindingKey);
}
For your case, basically you want to explictly create the channel and create/bind explicitly rather than through beans.
Each of those operations can be its own method gathered in a service class.
Then you can simply utilize the service class for using each method. This way you can create/bind anytime you want.

Adding Dynamic Number of Listeners(Spring JMS)

I have a requirement to add multiple listeners as mentioned in the application.properties file. Like Below,
InTopics=Sample.QUT4,Sample.T05,Sample.T01,Sample.JT7
NOTE: This number can be lot more or less.
I am thinking of getting them in an array,
#Value("${InTopics}")
private String[] inTopics;
But i don't know how to create multiple listeners from the array.
Currently, for one Topic i am doing as below,
#Configuration
#EnableJms
public class JmsConfiguration {
#Value("${BrokerURL}")
private String brokerURL;
#Value("${BrokerUserName}")
private String brokerUserName;
#Value("${BrokerPassword}")
private String brokerPassword;
#Bean
TopicConnectionFactory connectionFactory() throws JMSException {
TopicConnectionFactory connectionFactory = new TopicConnectionFactory(brokerURL, brokerUserName, brokerPassword);
return connectionFactory;
}
#Bean
JmsListenerContainerFactory<?> jmsContainerFactory(TopicConnectionFactory connectionFactory) throws JMSException {
SimpleJmsListenerContainerFactory factory = new SimpleJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setPubSubDomain(Boolean.TRUE);
return factory;
}
}
And My Listener,
#JmsListener(destination = "${SingleTopicName}", containerFactory = "jmsContainerFactory")
public void receiveMessage(Message msg) {
//Do Some Stuff
}
Is there any way i can achieve this?
You can't do it with annotated #JmsListeners but you can register each listener programmatically by extending JmsListenerConfigurer as described in the reference documentation.
EDIT
Since you are injecting the property as an array...
#Value("${InTopics}")
private String[] inTopics;
Spring will split up the list an create an array based on the number of queues in the list.
You can then iterate over the array in JmsListenerConfigurer.configureJmsListeners() and create an endpoint for each element in the array - you don't need to know ahead of time how big the array is.
for (String inTopic : inTopics) {
...
}
Here is the customized code to define number of listeners dynamically.
JmsConfiguration jmsConfiguration;
private List<String> queueList;
#Bean
public DefaultJmsListenerContainerFactory mqJmsListenerContainerFactory() throws JMSException {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(jmsConfiguration.jmsConnectionFactory());
factory.setDestinationResolver(new DynamicDestinationResolver());
factory.setSessionTransacted(true);
factory.setConcurrency("5");
return factory;
}
#Override
public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
queueList.forEach(queue -> {
SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint();
endpoint.setId(queue);
endpoint.setDestination(queue);
try {
endpoint.setMessageListener(message -> {
try {
logger.info("Receieved ID: {} Destination {}", message.getJMSMessageID(), message.getJMSDestination());
}
catch (JMSException e) {
logger.info("Exception while reading message - " + e);
}
});
registrar.setContainerFactory(mqJmsListenerContainerFactory());
}
catch (JMSException e) {
logger.info("Exception - " + e);
}
registrar.registerEndpoint(endpoint);
});
}
I did not know this was exist and I had to manually write all these code down. So, another way to do this is to implement BeanFactoryPostProcessor in your bean and manually add all the required components of a jms listener.
jndiTemplate
jndiQueueConnectionFactory (depends on jndiTemplate from step 1)
queueConnectionFactory (depends on jndiQueueConnectionFactory from step 2)
jndiDestinationResolver (uses jndiTemplate from stem 1)
messageListenerContiner (uses all above created items)
so, as you can see I do not only multiply the jms listener, I als dynamically generate the listener container multiple too. Ofc, this was my requirement. And may vary based on requirements.
One thing to keep in mind is that, there is no resource (like properties etc.. loaded) while you manipulate the BeanFactoryPostProcessor. You have to manually load the properties. I did via, afterPropertiesSet method coming from InitializingBean

Categories