JDBC not registering metrics on spring boot 2 when #RefreshScope is on DataSource Bean - spring-boot

I have an application that uses spring boot 2 and logs metrics from micrometer. I want to log jdbc (mysql) min, max, and active connections periodically. I also want to use #RefreshScope on my datasource bean to prevent hikari binding exceptions when injecting configs on the fly from spring admin. I find that when I use #RefreshScope on config class/datasource bean JDBC does not register itself with the MeterRegistry.
Is it possible to have JDBC register itself with the MeterRegistry with #RefreshScope?
Is there a way to progammatically register JDBC with the MeterRegistry in my bean definition?
#Configuration
#EnableAutoConfiguration
#EnableTransactionManagement
#RefreshScope
public class DbConfig {
#Primary
#Bean(name = "dataSource")
#RefreshScope
#ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
Removing #RefreshScope allows JDBC to automatically register with the MeterRegistry but causes the below exception on config change:
org.springframework.boot.context.properties.ConfigurationPropertiesBindException: Error creating bean with name 'dataSource': Could not bind properties to 'HikariDataSource' : prefix=spring.datasource, ignoreInvalidFields=false, ignoreUnknownFields=true; nested exception is org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'spring.datasource' to javax.sql.DataSource

Add javax.sql.DataSource as an extra refreshable. application.yml file example:
spring:
cloud:
refresh.extra-refreshable:
- javax.sql.DataSource
and remove #RefreshScope from your class.
Other solution would be cast the DataSource to HikariDataSource.
I use the first solution because of DataSource creation is done by external library in my application.
Reference: https://github.com/spring-cloud/spring-cloud-commons/issues/318

Related

Transactions, Spring Boot Starter JDBC & R2DBC

I am trying to migrate a Spring Boot project, version 2.3.0.M3, that have used JDBC template to R2DBC. The project also uses Liquibase so I cannot get rid of JDBC altogether.
I have both the spring-boot-starter-data-r2dbc and the spring-boot-starter-jdbc dependencies in the project with which I get the following exception when trying to run one of my tests:
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'org.springframework.transaction.TransactionManager' available: expected single matching bean but found 2: transactionManager,connectionFactoryTransactionManager
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1180)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveBean(DefaultListableBeanFactory.java:416)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:349)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342)
at org.springframework.transaction.interceptor.TransactionAspectSupport.determineTransactionManager(TransactionAspectSupport.java:480)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:335)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:99)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:747)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:95)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:747)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689)
...
The bean connectionFactoryTransaction manager is defined like this in the Spring class R2dbcTransactionManagerAutoConfiguration:
#Bean
#ConditionalOnMissingBean(ReactiveTransactionManager.class)
public R2dbcTransactionManager connectionFactoryTransactionManager(ConnectionFactory connectionFactory) {
return new R2dbcTransactionManager(connectionFactory);
}
The bean transactionManager is defined like this in the Spring class DataSourceTransactionManagerAutoConfiguration:
#Bean
#ConditionalOnMissingBean(PlatformTransactionManager.class)
DataSourceTransactionManager transactionManager(DataSource dataSource,
ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource);
transactionManagerCustomizers.ifAvailable((customizers) -> customizers.customize(transactionManager));
return transactionManager;
}
As can be seen, the #ConditionalOnMissingBean annotation contains different types which will cause an instance of both beans to be created.
However, in the Spring class TransactionAspectSupport there is this line of code in the determineTransactionManager method:
defaultTransactionManager = this.beanFactory.getBean(TransactionManager.class);
Since both of the transaction manager types, DataSourceTransactionManager and R2dbcTransactionManager, implement the TransactionManager interface, both the transaction manager beans as above will be matched and the error will occur.
I am now reaching out to hear if there is anyone who has managed to solve or work around this issue?
Thanks in advance!
With inspiration from M. Deinums answer (thanks!), I applied the following steps to my project and the test that failed earlier now runs successfully:
Remove the spring-boot-starter-jdbc dependency.
Add a dependency to spring-jdbc.
Add a dependency to HikariCP (com.zaxxer).
Add spring.liquibase user and password properties (I already had the url and change-log properties).
Remove all spring.datasource properties (I had url and drive-class-name).
I had the spring.r2dbc properties username, password and url defined which I did not need to change.
Update:
In addition, I used Testcontainers in the tests and could not assign a static port. In order to be able to configure the database port on Liquibase, I overrode a bean name liquibase of the type SpringLiquibase and created a DataSource (not exposed as a bean) in the liquibase bean creation method and set it on the liquibase bean.
It's possible to have spring-boot-starter-jdbc and spring-boot-starter-data-r2dbc co-exist. There is a class org.springframework.transaction.annotation.TransactionManagementConfigurer that can be used to resolve the conflict.
Spring Boot 2.3.0 seems to disable automatic datasource config when r2dbc is present. It's possible to manually import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration class to make both co-exist.
#Bean
TransactionManagementConfigurer transactionManagementConfigurer(ReactiveTransactionManager reactiveTransactionManager) {
return new TransactionManagementConfigurer() {
#Override
public TransactionManager annotationDrivenTransactionManager() {
return reactiveTransactionManager;
}
};
}

How to disable SpringBoot autoconfiguration for TomcatServletWebServerFactory in order for a custom spring-starter to provide it?

so I was writing my own SpringBootStarter which was supposed to enable the JNDI lookup in the embedded tomcat of a SpringBoot application.
My sample SpringBoot application has a dependency of my custom SpringBootStarter, which in turn has a dependency on the spring-boot-starter-web. If I create a Configuration class like the following inside the sample SpringBoot application everything works perfectly:
#Configuration
public class SampleSpringBootAppConfig {
#Bean
public TomcatServletWebServerFactory tomcatFactory() {
return new TomcatServletWebServerFactory() {
#Override
protected TomcatWebServer getTomcatWebServer(org.apache.catalina.startup.Tomcat tomcat) {
System.out.println("CONFIGURING CUSTOM TOMCAT WEB SERVER FACTORY ");
tomcat.enableNaming();
return super.getTomcatWebServer(tomcat);
}
#Override
protected void postProcessContext(Context context) {
ContextResource resource = new ContextResource();
resource.setName("myDataSource");
resource.setType(DataSource.class.getName());
resource.setProperty("driverClassName", "org.postgresql.Driver");
resource.setProperty("url", "jdbc:postgresql://localhost:5432/postgres");
resource.setProperty("username", "postgres");
resource.setProperty("password", "postgres");
context.getNamingResources()
.addResource(resource);
}
};
}
Because SpringBoot finds a custom Bean, there won't be an autoconfigured default one / it is overridden and the JNDI is successfully enabled.
However, as soon as I extract this Bean configuration into my auto-configure module of my custom SpringBoot Starter, the following exception is thrown while trying to start the sample SpringBoot application:
org.springframework.context.ApplicationContextException: Unable to start web server; nested exception is org.springframework.context.ApplicationContextException: Unable to start ServletWebServerApplicationContext due to multiple ServletWebServerFactory beans : tomcatServletWebServerFactory,tomcatFactory
I reckon this is due to SpringBoot not finding a customized Bean and therefore creating an autoconfigured default one which also won't be overridden. So now there will be two ServletWebServerFactory beans, the default one and the one from my auto- configure module.
What i tried so far (to no avail) :
annotating my custom Bean with #Primary
setting spring.main.allow-bean-definition-overriding to true
Is there any way to make SpringBoot not initialize a autoconfigured default bean, or any other possible solution to this?
try this
#AutoConfigureBefore(ServletWebServerFactoryAutoConfiguration.class)
I was able to solve this myself by excluding the responsible AutoConfiguration class:
#SpringBootApplication ( exclude = ServletWebServerFactoryAutoConfiguration.class)

Spring Boot: How to make single externalize JDBC datasource configuration work in differnt DAOImpl classes

I have a requirement to fetch DB username and password from Vault. So I have removed the default implementation (spring.datasource.url,spring.datasource.username,spring.datasource.password)
and added the following code in DAOImpl class.
Code
#Autowired
private JdbcTemplate jdbcTemplate;
#Bean
#Primary
public DataSource dataSource()
{
return DataSourceBuilder.create().username("someusername").password("somepassword")
.url("someurl")
.driverClassName("oracle.jdbc.driver.OracleDriver").build();
}
It was working perfectly. But when I added a new DAOImpl class I got the following Exception. Is it necessay to add the above code snippet in all the DAOImpl
classes. Is there a way to configure dataSource in single class and use it in all the DAOImpl classes
Exception
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'dataSource': Requested bean is currently in creation: Is there an unresolvable circular reference?

Spring Boot voodoo required instantiating JPA with DataNucleus and Hikari

Any help getting this config to work would be welcome.
I am trying to take over the automatic connection pool, datasource and JPA configuration from Spring Boot to allow me to bring DataNucleus into the mix instead of Hibernate.
My approach is to code up the pieces Boot says are missing on a trial and error basis. I had to remove the Hibernate dependencies to allow DataNucleus to run.
Maybe I've now coded up too much or maybe not I'm not far enough.
Spring falls over with the error:
Exception encountered during context initialization - cancelling refresh attempt:
[huge SNIP]
nested exception is org.springframework.beans.BeanInstantiationException:
Failed to instantiate [org.springframework.data.repository.support.Repositories]:
Factory method 'repositories' threw exception;
nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'symbolRepositoryImpl':
Unsatisfied dependency expressed through field 'entityManager';
nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException:
No qualifying bean of type 'javax.persistence.EntityManager' available:
expected single matching bean but found 2:
org.springframework.orm.jpa.SharedEntityManagerCreator#0,
org.springframework.orm.jpa.SharedEntityManagerCreator#1
[SNIP]
2017-06-01 09:43:09.675 ERROR 9108 --- [ restartedMain] o.s.b.d.LoggingFailureAnalysisReporter
***************************
APPLICATION FAILED TO START
***************************
Description:
Field entityManager in com.bp.gis.tardis.repository.SymbolRepositoryImpl
required a single bean, but 2 were found:
- org.springframework.orm.jpa.SharedEntityManagerCreator#0: defined by method 'createSharedEntityManager' in null
- org.springframework.orm.jpa.SharedEntityManagerCreator#1: defined by method 'createSharedEntityManager' in null
Action:
Consider marking one of the beans as #Primary, updating the consumer to accept multiple beans,
or using #Qualifier to identify the bean that should be consumed
I could spend hours debugging this further but the breakpoint comes in the initialisation of one of the repositories which should have an entityManager injected.
This is what I'm manually instantiating:
#Configuration
#EnableJpaRepositories(
basePackages = {"org.adam.repository"}
)
public class DataSourceConfig {
#Bean
#ConfigurationProperties(prefix = "adam.datasource")
public AdamDataSourceProperties getDataSourceProperties() {
return new AdamDataSourceProperties();
}
#Bean
public DataSource getDataSource() {
AdamDataSourceProperties props = getDataSourceProperties();
return new HikariDataSource(props.getHikariConfig());
}
#Bean
public LocalContainerEntityManagerFactoryBean getEmfBean() {
LocalContainerEntityManagerFactoryBean emfBean =
new LocalContainerEntityManagerFactoryBean();
emfBean.setDataSource(getDataSource());
emfBean.setPersistenceUnitName("adam");
return emfBean;
}
#Bean
public EntityManagerFactory getEmf() {
LocalContainerEntityManagerFactoryBean emfBean = getEmfBean();
return emfBean.getNativeEntityManagerFactory();
}
}
My AdamDatasourceProperties is initialised by Spring using the "adam.datasource" prefixed values in application.properties, and it can then create a HikariConfig object to use to instantiate the HikariDataSource. That bit is actually fine, it's the entity manager factory that is probably causing issues - or something else.
I've got no evidence that my last method getEmf() is actually helping.
Also, I'm dubious that the error
Required a single bean, but 2 were found
or the suggested action are helpful - I don't fancy going into the Spring source code in order to annotate one of those methods on Spring's SharedEntityManagerCreator as #Primary.
UPDATE
DataNucleus won't run if it finds other JPA API classes on the classpath - it insists on its own version of the persistence API - hence removing the Hibernate packages was necessary.
Caused by: org.datanucleus.exceptions.NucleusUserException:
Found Meta-Data for class org.adam.entity.TimeSeriesEntity
but this class is either not enhanced or you have multiple copies
of the persistence API jar in your CLASSPATH!!
Make sure all persistable classes are enhanced before running
DataNucleus and/or the CLASSPATH is correct.
at org.datanucleus.metadata.MetaDataManagerImpl
.initialiseClassMetaData(MetaDataManagerImpl.java:2814)
so I have excluded Hibernate from spring-boot-starter-data-jpa and the error disappears.
I changed the LocalContainerEntityManagerFactoryBean method name to entityManagerFactory:
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean emfBean =
new LocalContainerEntityManagerFactoryBean();
emfBean.setDataSource(getDataSource());
emfBean.setPersistenceUnitName("adam");
return emfBean;
}
and to enable testing, I have to copy this #Configuration class and change the EMF method to accept Spring's test database:
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
#Qualifier("dataSource") DataSource dataSource) {
LocalContainerEntityManagerFactoryBean emfBean =
new LocalContainerEntityManagerFactoryBean();
emfBean.setDataSource(dataSource);
emfBean.setPersistenceUnitName("adam");
return emfBean;
}
That #Qualifier is for the sake of Intellij whose Spring facet complains about 2 candidates for injection here.
I also discovered that with this configuration, the repository/DTO dependency injection for the EntityManager doesn't work with #Autowired. It has to be the native-JPA annotation:
#PersistenceContext
private EntityManager entityManager;
With my previous Hibernate and OpenJPA configurations, Spring was happy to inject its own self-instantiated EntityManager in the presence of #Autowire.
This adds more fuel to my beef with Spring. It just too often doesn't do what it says on the tin. The Spring tests should find the #Configuration classes in the package hierarchy, but doesn't - I need to use #Import. Spring should also find dependency injection candidates based on type (EntityManager, DataSource etc) but it doesn't - in some cases they have to be produced by methods named a particular name or with #Bean annotations declaring a name.
Still, it's done.

not able to set the value for autowired parameter

I have datasource autowired with setters. Trying to return datasource value with Bean declaration in Spring javaconfig file. For some reason, it is not identifying and showing the error:
Property 'dataSource' required
Any idea? Here is my Bean in the javaconfig file:
#Bean(name = "dataSource")
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.hsqldb.jdbcDriver");
dataSource.setUrl("xyz");
dataSource.setUsername("xyz");
dataSource.setPassword("xyz");
return dataSource;
}
and the log trace:
Error creating bean with name 'featureStoreSpringJDBC' defined
in URL [jar:file:/C:home/WEB-INF/lib/ff4j-store-springjdbc.jar!
/org/ff4j/store/FeatureStoreSpringJDBC.class]:
Initialization of bean failed; nested exception
is org.springframework.beans.factory.BeanInitializationException
Property 'dataSource' is required for bean 'featureStoreSpringJDBC'
Please note that the attribute dataSource is not annotated with the #Autowired annotation, as a consequence you must explicitely invoke setter and initialize the FeatureStore in javaconfig.
The reason is you should defined the whole FF4J as a bean in the java config.In version before 1.3 it was autowired but we got issues with spreading of javaConfig.

Resources