MariaDB4j/MariaDB4j

Spring boot - add autoconfiguration

tomaszglinski opened this issue · 15 comments

Hi there,
I am trying to somehow replace the H2 with Mariadb4j for tests in spring boot apps, but i'm having problems with that.
The H2 db startsup very nice out of the box if only available on classpath. It would be a great boost for popularity of this project, if it was prepared for spring boot. A lot of people are complaining about H2 incompatibility.

Hello @tomaszglinski thanks for your interest in this project.

I believe what you are asking for is already possible:

Have you seen the note on the README re. the MariaDB4jSpringService, which you can very easily use in a Spring Boot application, as illustrated in the MariaDB4jApplication or the MariaDB4jSpringServiceTestSpringConfiguration?

If you need an even tighter automated integration with an autoconfiguration, then you're more than welcome to propose something via a pull request to this open source project ... ;-)

If there are replies, I'll close this issue in 1 month from today.

Hi, true, I didn't found the examples before. Unfortunately it is still not working. In debug mode I see that, propertyPlaceholderConfigurer method is called during Spring context startup, but the mariaDB4jSpringService method not. Test run ends with

Caused by: java.net.ConnectException: Connection refused: connect at java.net.DualStackPlainSocketImpl.connect0(Native Method) at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:79) at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350) at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206) at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188) at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172) at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392) at java.net.Socket.connect(Socket.java:589) at com.mysql.jdbc.StandardSocketFactory.connect(StandardSocketFactory.java:211) at com.mysql.jdbc.MysqlIO.<init>(MysqlIO.java:300) ... 77 common frames omitted 16:08:14.862 [main] WARN org.hibernate.engine.jdbc.spi.SqlExceptionHelper - SQL Error: 0, SQLState: 08S01 16:08:14.862 [main] ERROR org.hibernate.engine.jdbc.spi.SqlExceptionHelper - Communications link failure

I suppose there is wrong order of initialization, mariaDb4j should start before entity manager starts its initialization (including connection pool init).

Just to mention, my app uses also flyway for db schema initialization/update, that is done also just at the startup.

No idea now how to fix that, must think over the weekend....

@tomaszglinski You may be able to force the correct order of initialization by making whatever you have that requires MariaDB4j have an Inject / AutoWired dependency on a DB instance, and writing a Provider thus making it explicit that you require it and that it thus must be started before? Beyond that, I'm afraid I can't offer you any further support with integration of MariaDB4j into your app; I'm sure you understand. Best of luck!

zaenk commented

@tomaszglinski to achieve the correct initialization order, use Spring's named beans and @DependsOn annotation. The DataSource initialization have to wait until MariaDB is ready to receive connections. This gist shows a minimal example.

Although I did not checked if this exact code is running correctly, but it's stripped out from our "integration test suite" we use daily.

@zaenk thank you for contributing this! FYI I've worked this into vorburger@562b1a7 so others can find your tip & gist more easily.. Do feel free to send Pull Requests to further clarify the README better, or even contribute a full running example!

@vorburger I also have this problem of the order of initialization, I fixed it by this code:

@Configuration
@Profile("test")
class TestDataSourceConfiguration {

    @Bean
    MariaDB4jSpringService mariaDB4jSpringService() {
        new MariaDB4jSpringService()
    }

    @Bean
    @DependsOn("mariaDB4jSpringService")
    DataSource dataSource(DataSourceProperties dataSourceProperties) {
        DataSourceBuilder.create()
                .username(dataSourceProperties.username)
                .password(dataSourceProperties.password)
                .url(dataSourceProperties.url)
                .driverClassName(dataSourceProperties.driverClassName)
                .build() as DataSource
    }
}

But I hold that it is not a good code, cause I don't want to overwrite the implement of the bean dataSource, the only thing I wanna do is make sure dataSource will be initialized after the initialization of mariaDB4jSpringService finished.

I found that Spring can auto config H2, HSQL and Derby when add them into classpath. So I am thinking about is that possible to use MariaDB4j like these embedded databases? Or it's possible the only thing I need to do is add mariaDB4jSpringService as a bean? Do you have any plan to implement one of them?

@kbyyd24 I don't have plans to implement this, but I welcome Pull Requests from you / anyone.

@vorburger I have tried to add the code in previous comment into the mariaDB4j-core module, and make this configuration to auto-configure when spring boot is in classpath.

But it broken the module mariaDB4j-app cause there is already a bean of MariaDB4jSpringSerivce. Then I commented the code of the bean, but I got failed, the message is:

java.lang.IllegalStateException: Failed to load ApplicationContext

	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:125)
	at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:108)
	at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:117)
	at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:83)
	at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:246)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:227)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:289)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:291)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:246)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
	at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
	at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'dataSource' defined in class path resource [ch/vorburger/mariadb4j/springframework/DataSourceAutoConfiguration.class]: Unsatisfied dependency expressed through method 'dataSource' parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.boot.autoconfigure.jdbc.DataSourceProperties' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
	at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:732)
	at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:474)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1247)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1096)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:535)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:495)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:759)
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:869)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550)
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:762)
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:398)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:330)
	at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:139)
	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99)
	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:117)
	... 24 more
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.boot.autoconfigure.jdbc.DataSourceProperties' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1506)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1101)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1062)
	at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:818)
	at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:724)
	... 42 more

You can see the commit in my fork of the repo.

I don't know how to fix the error, maybe I need another project to implement auto-configure mariaDB4j.

Actually, I am not really understand what is mariaDB4j-app doing. Could you explain some?

@kbyyd24 See the README, "Note the use of the special mariaDB4j-app*.jar for this use-case, its a fat/shaded/über-JAR, based on a Spring Boot launcher." - it's more of an example. Perhaps what you are trying to do here should not go into mariaDB4j-core but a new mariaDB4j-springboot - I'm not sure, try.

Unfortunately personally I cannot help you to debug your Spring Boot related problem - but I'm happy to code review any working PR. But any other MariaDB4j community users who have an interest in and want to jump on helping you with this are very vey welcome to comment here - that would be very cool.

"Perhaps what you are trying to do here should not go into mariaDB4j-core but a new mariaDB4j-springboot"

This is a good idea, and I implemented it in PR (https://github.com/vorburger/MariaDB4j/pull/153). By this way, I don't need to change module mariaDB4j-app.

Thanks for your help.

Closing this just to clean up, as we are near having this solved by #153 (reviewed earlier today).

That's an old topic, but just asking as I wanted to use it... has anyone used it actually? Because it doesn't work...

@jordanms As in #153, you would need to open a new PR with suggested changes, or a new issue describing a problem, or suggesting an enhancement. Comments on a 4 year old closed issue are ignored.