How to lazy initialize spring-boot datasource?spring.data
Asked Answered
Y

5

8

I have a spring-boot application that defines a mysql DataSource:

spring.datasource.url=...
spring.datasource.username=...
spring.datasource.password=...

I run the tool as command line application triggered by a cronjob. And in 80% of all runs, I could exit the tool without having to contact the database at all:

@Controller
public class MainController {
    @Autowired
    private MyService myService;

    public void evaluate() {
           if (condition == true) myService.run();
           else System.exit(0);
    } 
}

@Service
@Lazy
public class MyService {
    @Autowired
    private JdbcTemplate jdbc;

    public void run() {
       jdbc.execute(sql);
    }
}

Problem: I want to prevent spring initializing the database at all. So, if for example the database is down or the host is unreachable (could be tested with using spring.datasource.url=localhostX), then the tool should still be runable.

Is that possible to delay the datasource initialization until it is really needed?

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is org.hibernate.service.spi.ServiceException: Unable to create requested service [org.hibernate.engine.jdbc.env.spi.JdbcEnvironment]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1778) ~[spring-beans-5.1.10.RELEASE.jar:5.1.10.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:593) ~[spring-beans-5.1.10.RELEASE.jar:5.1.10.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515) ~[spring-beans-5.1.10.RELEASE.jar:5.1.10.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320) ~[spring-beans-5.1.10.RELEASE.jar:5.1.10.RELEASE]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.1.10.RELEASE.jar:5.1.10.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318) ~[spring-beans-5.1.10.RELEASE.jar:5.1.10.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.1.10.RELEASE.jar:5.1.10.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1105) ~[spring-context-5.1.10.RELEASE.jar:5.1.10.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:867) ~[spring-context-5.1.10.RELEASE.jar:5.1.10.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:549) ~[spring-context-5.1.10.RELEASE.jar:5.1.10.RELEASE]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:744) ~[spring-boot-2.1.9.RELEASE.jar:2.1.9.RELEASE]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:391) ~[spring-boot-2.1.9.RELEASE.jar:2.1.9.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:312) ~[spring-boot-2.1.9.RELEASE.jar:2.1.9.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1215) ~[spring-boot-2.1.9.RELEASE.jar:2.1.9.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1204) ~[spring-boot-2.1.9.RELEASE.jar:2.1.9.RELEASE]
Caused by: org.hibernate.service.spi.ServiceException: Unable to create requested service [org.hibernate.engine.jdbc.env.spi.JdbcEnvironment]
    at org.hibernate.service.internal.AbstractServiceRegistryImpl.createService(AbstractServiceRegistryImpl.java:275) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final]
    at org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:237) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final]
    at org.hibernate.service.internal.AbstractServiceRegistryImpl.getService(AbstractServiceRegistryImpl.java:214) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final]
    at org.hibernate.id.factory.internal.DefaultIdentifierGeneratorFactory.injectServices(DefaultIdentifierGeneratorFactory.java:152) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final]
    at org.hibernate.service.internal.AbstractServiceRegistryImpl.injectDependencies(AbstractServiceRegistryImpl.java:286) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final]
    at org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:243) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final]
    at org.hibernate.service.internal.AbstractServiceRegistryImpl.getService(AbstractServiceRegistryImpl.java:214) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final]
    at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.<init>(InFlightMetadataCollectorImpl.java:179) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final]
    at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:119) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final]
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.metadata(EntityManagerFactoryBuilderImpl.java:904) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final]
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:935) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final]
    at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:58) ~[spring-orm-5.1.10.RELEASE.jar:5.1.10.RELEASE]
    at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:365) ~[spring-orm-5.1.10.RELEASE.jar:5.1.10.RELEASE]
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:391) ~[spring-orm-5.1.10.RELEASE.jar:5.1.10.RELEASE]
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:378) ~[spring-orm-5.1.10.RELEASE.jar:5.1.10.RELEASE]
    at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.afterPropertiesSet(LocalContainerEntityManagerFactoryBean.java:341) ~[spring-orm-5.1.10.RELEASE.jar:5.1.10.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1837) ~[spring-beans-5.1.10.RELEASE.jar:5.1.10.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1774) ~[spring-beans-5.1.10.RELEASE.jar:5.1.10.RELEASE]
    ... 15 common frames omitted
Caused by: org.hibernate.HibernateException: Access to DialectResolutionInfo cannot be null when 'hibernate.dialect' not set
    at org.hibernate.engine.jdbc.dialect.internal.DialectFactoryImpl.determineDialect(DialectFactoryImpl.java:100) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final]
    at org.hibernate.engine.jdbc.dialect.internal.DialectFactoryImpl.buildDialect(DialectFactoryImpl.java:54) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final]
    at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.initiateService(JdbcEnvironmentInitiator.java:137) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final]
    at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.initiateService(JdbcEnvironmentInitiator.java:35) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final]
    at org.hibernate.boot.registry.internal.StandardServiceRegistryImpl.initiateService(StandardServiceRegistryImpl.java:94) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final]
    at org.hibernate.service.internal.AbstractServiceRegistryImpl.createService(AbstractServiceRegistryImpl.java:263) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final]
    ... 32 common frames omitted
Yaakov answered 4/11, 2019 at 14:54 Comment(5)
Is that really all the configuration you have? No connection pool? Because I would think that it is in fact the connection pool creation which fails on startup as it will fail to pre-fill it with the minimum requested amount of connections.Fertilizer
Indeed spring uses hikari connection pool by default. Which I would want to stick with. But how could I then delay the hikari cp initialization?Yaakov
Maybe exclude the auto-configuration #51312791Acreage
But how would I then include the datasource auto configuration in the rare 20% cases? Because then I need it, and I don't know the condition before starting the cli app.Yaakov
You could try setting the minimumIdle to 0 for Hikari... that should stop it from pre-initialising connections. Docs: github.com/brettwooldridge/HikariCP#configuration-knobs-babyFertilizer
Y
4
@SpringBootApplication(exclude = HibernateJpaAutoConfiguration.class)

Somehow the hikari pool and hibernate seem to interact on startup. And if the datasource is not present, startup fails.

Using the exclusion above with @Lazy annotation delays the initialization as expected.

But that only works because I don't make use of hibernate.

Yaakov answered 4/11, 2019 at 15:13 Comment(1)
also add spring.data.jpa.repositories.bootstrap-mode=lazyBolus
W
5

As said in spring doc(https://docs.spring.io/spring-boot/docs/2.1.13.RELEASE/reference/html/boot-features-sql.html):

Spring Data JPA repositories support three different modes of bootstrapping: default, deferred, and lazy. To enable deferred or lazy bootstrapping, set the spring.data.jpa.repositories.bootstrap-mode to deferred or lazy respectively.

So try the following config in your application.properties (or yaml):

spring.data.jpa.repositories.bootstrap-mode=lazy
Woodsum answered 23/1, 2022 at 9:31 Comment(1)
also add @SpringBootApplication(exclude = HibernateJpaAutoConfiguration.class)Bolus
Y
4
@SpringBootApplication(exclude = HibernateJpaAutoConfiguration.class)

Somehow the hikari pool and hibernate seem to interact on startup. And if the datasource is not present, startup fails.

Using the exclusion above with @Lazy annotation delays the initialization as expected.

But that only works because I don't make use of hibernate.

Yaakov answered 4/11, 2019 at 15:13 Comment(1)
also add spring.data.jpa.repositories.bootstrap-mode=lazyBolus
T
1

You can use LazyConnectionDataSourceProxy, reference api docs

Torino answered 16/8, 2024 at 4:33 Comment(0)
S
0

Starting with Springboot 2.2 it is possible.

Add the following property in application.properties

spring.main.lazy-initialization=true

Read more detail here.

Sora answered 4/11, 2019 at 14:59 Comment(3)
Are you sure? I tried that, but it's not skipping the DataSource initialization (java.sql.SQLNonTransientConnectionException: Could not connect to address=(host=localhostx)(port=3306)(type=master) : localhostx).Yaakov
Isn't it effecting all Beans?Acreage
Yes, you are right it will make all the beans to initialize on demand. On the first call.Sora
L
0

When using Hibernate as JPA implementation, I was able to avoid connecting to database during application startup by setting these 2 hibernate specific properties even though the beans are initialised at startup -

spring:
  jpa:
    properties:
      hibernate:
        temp:
          use_jdbc_metadata_defaults: false
        hbm2ddl:
            auto: none

Here are the significance of these properties from Hibernate Documentation

hibernate.temp.use_jdbc_metadata_defaults (e.g. true (default value) or false)

This setting is used to control whether we should consult the JDBC metadata to determine certain Settings default values when the database may not be available (mainly in tools usage).

and here

hibernate.hbm2ddl.auto (e.g. none (default value), create-only, drop, create, create-drop, validate, and update)

Setting to perform SchemaManagementTool actions automatically as part of the SessionFactory lifecycle. Valid options are defined by the externalHbm2ddlName value of the Action enum:

none No action will be performed.

So, after setting the properties, the jdbc connection is made only when executing sql queries.

Littman answered 19/4, 2022 at 8:22 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.