Spring-Boot: How to restrict the visibility of Beans - java

I have two custom PlatformTransactionManager beans injected into the Spring framework with specific names as follows:
#Bean(name = "ubldbTransactionManager")
protected PlatformTransactionManager transactionManager(
#Qualifier("ubldbEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
#Bean(name = "bpdbTransactionManager")
public PlatformTransactionManager bpdbTransactionManager(
#Qualifier("bpdbEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
A 3rd-party library has a #Autowired protected PlatformTransactionManager transactionManager; dependency. So, the 3rd party library is not supposed to use none of the two TransactionManagers. However, as you see there is no Qualifier for the dependency injection in the external library and I get an error as follows:
Field transactionManager in org.camunda.bpm.spring.boot.starter.configuration.impl.DefaultDatasourceConfiguration required a single bean, but 2 were found:
- bpdbTransactionManager: defined by method 'bpdbTransactionManager' in class path resource [eu/nimble/service/bp/config/BusinessProcessDBConfig.class]
- ubldbTransactionManager: defined by method 'transactionManager' in class path resource [eu/nimble/service/bp/config/UBLDBConfig.class]
So, how can I restrict the visibility of the two Beans so that they would not be accessible by the 3rd-party library?

DefaultDatasourceConfiguration is provided to use default Spring beans e.g. DataSource named dataSource and PlatformTransactionManager named transcationManager. It's there to glue Camunda into a Spring Boot application which be default has a single data source.
Since you have created your own PlatformTransactionManager beans this disabled Spring Boot's default transaction manager bean named transcationManager (as per TransactionAutoConfiguration Spring Boot auto-configuration logic).
You most likely need to define one more transactionManager (and potentially dataSource) for Camunda's process engine, which requires it's own schema. Make sure to use the right bean name as below:
#Bean
public PlatformTransactionManager transactionManager() {
...
}
Starting from Spring 4 the bean name is the default qualifier when auto-wiring so the new transaction manager will be wired into DefaultDatasourceConfiguration as it matches the field name in the class.
Alternatively don't use DefaultDatasourceConfiguration and roll out your own configuration if Spring Boot defaults are not working for you.

Use #Qualifier annotation
The #Qualifier annotation is used to resolve the autowiring conflict, when there are multiple beans of same type.
#Bean
#Qualifier("ubldbTransactionManager")
protected PlatformTransactionManager transactionManager
and
#Bean
#Qualifier("bpdbTransactionManager")
public PlatformTransactionManager bpdbTransactionManager

Related

How can I use different variable names for common Spring Boot Application Properties, when the app never directly calls these variables?

I've created a pretty standard Spring Boot 2.0 app with Services and Repositories to access a database.
I had to set the standard Spring Boot application properties to get it to work, such as:
spring.datasource.url, spring.jpa.database, etc.
However, in order to prevent my properties from overwriting other properties in similar apps hosted in the same place, I need to rename these properties, such as:
myApp.spring.datasource.url, myApp.spring.jpa.database, etc.
Some of these properties will be set by environmental variables instead of the application.properties file.
However, I can't see any way to override those variables in my app.
The standard approach is to use #Value to configure those variables. However, the Spring Boot 2.0 setup for services looks up all these properties "behind the scenes," so that doesn't appear to be an option here.
Is there any way to configure my app to read all those myApp.common.property.name properties and treat them as common.property.name?
Thank you.
Yes, the standard way is to use #Value. But thw work doesn't stop there, you need to create DataSource and EntityManager with these values.
Springboot will create DataSource, Entitymanager and some other components bey looking into default properties(spring.xxx) from the file(hence Spring boot is opinionated). But when you change these names to non default values, then you need to create these components / beans yourself.
Instead of using #Value you could also use #Configurationproperties. #Value also works but you might need to declare like 6 or 7 values with #Value. If you wish to use #ConfigurationProperties, make sure you have #EnableConfigurationProperties annotation added somewhere in your project.
Here is a code snippet. You need to tune it to your project
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "entityManagerFactory",
basePackages = { "com.xxx.yyy.repo" }
)
public class SomeDbConfig {
#Primary // Use this if you have multiple datasources or else no use
#Bean(name = "dataSource")
#ConfigurationProperties(prefix = "myApp.spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Primary
#Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean
entityManagerFactory(
EntityManagerFactoryBuilder builder,
#Qualifier("dataSource") DataSource dataSource
) {
return builder
.dataSource(dataSource)
.packages("com.xxx.yyy.domain")
.persistenceUnit("somename")
.build();
}
#Primary
#Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager(
#Qualifier("entityManagerFactory") EntityManagerFactory
entityManagerFactory
) {
return new JpaTransactionManager(entityManagerFactory);
}
}

Spring - conflicts with existing, non-compatible bean definition of same name and class

The specific exception is:
Failed to instantiate [org.springframework.context.annotation.AnnotationConfigApplicationContext]: Constructor threw exception;
nested exception is org.springframework.context.annotation.ConflictingBeanDefinitionException:
Annotation-specified bean name 'myService' for bean class [my.package.ejb.MyService] conflicts with existing, non-compatible bean definition of same name and class [my.package.other.ejb.MyService]
Those MyService interfaces are not even annotated, they represent EJB 2.0 stateless beans.
My annotation configuration is as follow.
#Configuration
#ComponentScan("my.package")
#MapperScan("my.package")
public class ApplicationConfiguration {
#Bean
public DataSource dataSource() {
return new JndiDataSourceLookup().getDataSource("...");
}
#Bean
public SqlSessionFactoryBean sqlSessionFactory(final DataSource dataSource) {
final SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
sqlSessionFactory.setDataSource(dataSource);
sqlSessionFactory.setConfigLocation(new ClassPathResource("..."));
return sqlSessionFactory;
}
#Bean
public DataSourceTransactionManager dataSourceTransactionManager(final DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
Might be an incompatibility with #MapperScan (from MyBatis) and #ComponentScan?
The exception come from the SpringBeanAutowiringInterceptor I'm using to Autowire EJB 3.0 fields.
The documentation for MapperScannerConfigurer says that:
This class supports filtering the mappers created by either specifying
a marker interface or an annotation. The annotationClass property
specifies an annotation to search for. The markerInterface property
specifies a parent interface to search for. If both properties are
specified, mappers are added for interfaces that match either
criteria. By default, these two properties are null, so all interfaces
in the given basePackage are added as mappers.
Basically I was mapping thousands of interfaces as beans. Not cool!
Guys, my fault.

Mark externally-declared bean as #Primary

I have a Spring Boot app that requires multiple datasources that will be used via JdbcTemplates to make sql statements to different DBs. These datasource beans are already declared in external dependencies that I am pulling into my app via an #Import statement the JdbcTemplate-bean-declaring java-based #Configuration classes.
After declaring two different JdbcTemplates initialized with two different DataSource beans form these external dependencies, I run into the following error on app startup:
Parameter 0 of constructor in org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration required a single bean, but 2 were found:
Given that I don't have access to the declarations of these DataSource beans in my app, how can I mark one as #Primary?
EDIT: Here's code for the construction of the JdbcTemplates (Note "dataSourceA" and "dataSourceB" are pulled from an external dependency)
#Configuration
#Import({DataSourceAApplicationConfig.class, DataSourceBApplicationConfig.class})
public class JdbcTemplateAppConfig {
#Autowired
DataSource dataSourceA;
#Autowired
DataSource dataSourceB;
#Bean
#Primary
public JdbcTemplate jdbcTemplateForDataSourceA(#Qualifier("dataSourceA") DataSource dataSource) {
JdbcTemplate template = new JdbcTemplate(dataSource);
return template;
}
#Bean
public JdbcTemplate jdbcTemplateForDataSourceB#Qualifier("dataSourceB") DataSource dataSource){
JdbcTemplate template = new JdbcTemplate(dataSource);
return template;
}
}

Spring Boot. Get bean as response to local bean method call

I work on #Configuration class and masterTransactionManager bean needs to be injected with masterDataSource bean. I found example https://www.codeday.top/2017/07/08/31074.html and it not works.
Here simplified example class
#Configuration
public class MasterDataSourceConfig {
#Bean
#Primary
public DataSource masterDataSource() {
DruidDataSource dataSource = new DruidDataSource();
...
return dataSource;
}
#Bean
#Primary
public DataSourceTransactionManager masterTransactionManager() {
/*
* Spring not injects bean here, instead
* it just get new instance
* of DataSource object
*/
return new DataSourceTransactionManager(masterDataSource());
}
}
I was able to fix this with passing bean reference as argument:
#Bean
#Primary
public DataSourceTransactionManager masterTransactionManager(
#Qualifier("schmodelAuditDataSource") DataSource dataSource) {
/*
* Now bean injected, and everything works as it should
*/
return new DataSourceTransactionManager(dataSource);
}
Now the question is: how could it work new DataSourceTransactionManager(masterDataSource())? I never saw before that Spring can return bean on method call like in first example. Is this proper solution to get the bean? If this proper call, then why it not works for me?
I was not able to find a lot about such bean call method, though in thread Spring boot bean into bean injection methodology mentioned it should work. While in my case it is not.
Update: what error I see
The error I get in first case is
Error querying database. Cause: org.springframework.jdbc.CannotGetJdbcConnectionException
also in debug mode I see the instance of bean on spring bean initiation call is different, from the returning when my class calls masterDataSource() method.
The application starts without errors. I get error when start using beans instantiated (write data to database). I think this is result of not proper bean (is it bean at all, not sure) returned when called masterDataSource()
Before answer the question I need to clear what is Autowiring?
Spring managed their bean and lifecycle inside application context which is a container. Application context is a container which contain bean.
When the application context bootstrapped the beans also instantiated by their defined scope (singleton, prototype, request, session, global-session).
Spring's default scope is singleton refers it instantiate at once and shared the object (usually a cached object) within application context.
Autowire happens inject a beans instance to another bean.It means where we inject and what we inject -- both should be beans and lived in Spring IoC container (Application Context).
Now come to you question. If you autowire a bean, in fact your instantiated object (instantiated by spring) is set here.If you call some method like masterDataSource() in your example:-
#Bean
#Primary
public DataSource masterDataSource() {
DruidDataSource dataSource = new DruidDataSource();
...
return dataSource;
}
#Bean
#Primary
public DataSourceTransactionManager masterTransactionManager() {
return new DataSourceTransactionManager(masterDataSource());
}
Then masterDataSource() also create a new object and set (Injected) to DataSourceTransactionManager constructor.
So bottom line is if you used #Autowired then you get spring managed (lifecycle) bean/object and if you called masterDataSource() then you just create a new object apart from spring managed object.

#EnableTransactionManagement annotation with 2 transaction managers

I am using #Configuration annotation for configuration of spring instead of xml file. I am configuring 2 datasources with different session factory and different transaction managers. I am stuck with a problem here for #EnableTransactionManagement annotation. I read in its documentation that,
#EnableTransactionManagement is more flexible; it will fall back to a
by-type lookup for any PlatformTransactionManager bean in the
container. Thus the name can be "txManager", "transactionManager", or
"tm": it simply does not matter.
This means whatever name I give to method, it will always search for the method which returns PlatformTransactionManager object while I have 2 transactionmanagers. Now the problem is, when I test this class, it gives me error:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [org.springframework.transaction.PlatformTransactionManager] is defined: expected single bean but found 2
I even tried to have 2 different Configuration classes but in vain. In xml configuration, this was not the case. I registered my both transaction managers with two <tx:annotation-driven transaction-manager="" /> tag and it worked fine. But not able to do same here with annotations.
What should I do if I want to configure 2 datasources with 2 different transaction managers in Spring annotated configuration class?
In your configuration class, use #EnableTransactionManagement annotation.
Define a transaction manager in this class as:
#Bean(name="txName")
public HibernateTransactionManager txName() throws IOException{
HibernateTransactionManager txName= new HibernateTransactionManager();
txName.setSessionFactory(...);
txName.setDataSource(...);
return txName;
}
There on, in your class/method that executes transactional job(s), annotate as follows:
#Transactional("txName")
or
#Transactional(value = "txName")
This is how you would tie a name qualified transaction manager to wherever you need it. You can now have as many transaction managers as you want and use it accordingly wherever you need.
Just in case anyone runs into this problem, I found a solution:
#Configuration
#EnableTransactionManagement
#DependsOn("myTxManager")
#ImportResource("classpath:applicationContext.xml")
public class AppConfig implements TransactionManagementConfigurer {
#Autowired
private PlatformTransactionManager myTxManager;
...
#Override
public PlatformTransactionManager annotationDrivenTransactionManager() {
return this.myTxManager;
}
In this way, you can use a specific txManager defined in an xml configuration.
In case you want to define the txManager used on service-level, you shall remove the #EnableTransactionManagement annotation from the #Configuration class and specify the txManager in the #Transactional annotations, e.g.
#Service
#Transactional(value="myTxManager", readOnly = true)
public class MyServiceImpl implements MyService { ... }
From the java doc
For those that wish to establish a more direct relationship between
#EnableTransactionManagement and the exact transaction manager bean to be used, the
TransactionManagementConfigurer callback interface may be implemented - notice the
implements clause and the #Override-annotated method below:
Your #Configuration class needs to implement TransactionManagementConfigurer interface - implement the annotationDrivenTransactionManager which will return the reference to the transactionManager that should be used.
I am not sure why you are using two TransactionManagers . You could consider using the same TransactionManager for multiple datasource via the AbstractRoutingDataSource . Please refer
http://blog.springsource.org/2007/01/23/dynamic-datasource-routing/
for a sample on its usage.
I have to use JPA and Reactive Mongo in one project. What works at last was:
create a #Configuraition class to explicitly create a JPA transaction manager, like here:
private Environment env;
#Bean
#Primary
public LocalContainerEntityManagerFactoryBean dbEntityManager() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dbDatasource());
em.setPackagesToScan(new String[]{"projectone.mysql"});
em.setPersistenceUnitName("dbEntityManager");
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
HashMap<String, Object> properties = new HashMap<>();
properties.put("hibernate.dialect",env.getProperty("hibernate.dialect"));
properties.put("hibernate.show-sql",env.getProperty("jdbc.show-sql"));
em.setJpaPropertyMap(properties);
return em;
}
#Primary
#Bean
public DataSource dbDatasource() {
DriverManagerDataSource dataSource
= new DriverManagerDataSource();
dataSource.setDriverClassName(
env.getProperty("spring.datasource.driverClassName"));
dataSource.setUrl(env.getProperty("spring.datasource.url"));
dataSource.setUsername(env.getProperty("spring.datasource.username"));
dataSource.setPassword(env.getProperty("spring.datasource.password"));
return dataSource;
}
#Primary
#Bean
public PlatformTransactionManager jpaTransactionManager() {
JpaTransactionManager transactionManager
= new JpaTransactionManager();
transactionManager.setEntityManagerFactory(
dbEntityManager().getObject());
return transactionManager;
}
}
Notice that the bean name jpaTransactionManager, which would be the txManager name used in JPA #Transactional.
create MongoConfiguration to explicitly create a Mongo transaction manager(a lot of beans to define)
in #Transactional, call them with name. The default one transactionManger will not work. You have to distinguish, like jpaTransactionManager and reactiveMongoTransactionManger.
#Transactional(value="jpaTransactionManager")
public void xxx() {
...
}
Note that JPA transaction methods cannot Reactor types as return value(Mono/Flux). Spring will force methods returning Mono/Flux to use ReactiveTransactionManager, it will cause confusion.
Some of the other answers imply that using two transaction managers is in some way wrong; however, Spring's XML configuration allows for using multiple transaction managers as stated in the online documentation (below). Unfortunately, there does not seem to be a way to make the #EnableTransactionManagement annotation work in a similar manner. As a result, I simply use an #ImportResource annotation to load an XML file that includes the <tx:annotation-driven/> line. This allows you to get a Java configuration for most things but still make use of #Transactional with an optional Transaction Manager qualifier.
http://docs.spring.io/spring/docs/3.1.x/spring-framework-reference/html/transaction.html
Most Spring applications only need a single transaction manager, but there may be situations where you want multiple independent transaction managers in a single application. The value attribute of the #Transactional annotation can be used to optionally specify the identity of the PlatformTransactionManager to be used. This can either be the bean name or the qualifier value of the transaction manager bean. For example, using the qualifier notation, the following Java code
Try to use chained TransactionalManager
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.transaction.ChainedTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
#Configuration
public class ChainedDBConfig {
#Bean("chainedTransactionManager")
public PlatformTransactionManager transactionManager(
#Qualifier("database1TransactionManager") final PlatformTransactionManager db1PlatformTransactionManager,
#Qualifier("database2TransactionManager") final PlatformTransactionManager db2PlatformTransactionManager) {
return new ChainedTransactionManager(db1PlatformTransactionManager, db2PlatformTransactionManager);
}
}
And place the following annotation on your service class:
#Transactional(transactionManager = "chainedTransactionManager")
public class AggregateMessagesJobIntegrationTest {
...
}
You can also use it inside the integration tests:
#RunWith(SpringRunner.class)
#Transactional(transactionManager = "chainedRawAndAggregatedTransactionManager")
#Rollback
public class ExampleIntegrationTest extends AbstractIntegrationTest {
....
}
and it will do a rollback for both DB transaction managers.

Categories