如何懒加载Spring Boot数据源?spring.data

6

我有一个使用 spring-boot 框架编写的应用程序,定义了一个 mysql 的 DataSource:

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

我将该工具作为命令行应用程序由cronjob触发运行。在所有运行的80%中,我可以完全退出该工具而无需联系数据库:

@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);
    }
}

问题:我希望完全阻止Spring初始化数据库。因此,如果例如数据库停机或主机不可访问(可以使用spring.datasource.url=localhostX进行测试),那么工具仍应该是可运行的。

是否可能延迟数据源初始化直到真正需要它?

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

那真的是你所有的配置吗?没有连接池吗?因为我认为实际上连接池的创建在启动时会失败,因为它将无法预先填充到最小请求的连接数。 - Gimby
实际上,Spring 默认使用 Hikari 连接池。我想继续使用它,但是我该如何延迟 Hikari 连接池的初始化呢? - membersound
也许可以排除自动配置。https://dev59.com/nqzla4cB1Zd3GeqPDdoD - user7294900
但是我如何在罕见的20%情况下包含数据源自动配置呢?因为那时我需要它,而且在启动cli应用程序之前我不知道条件。 - membersound
你可以尝试将Hikari的minimumIdle设置为0... 这样应该可以阻止它预初始化连接。文档:https://github.com/brettwooldridge/HikariCP#configuration-knobs-baby - Gimby
4个回答

3
如在Spring文档中所述(https://docs.spring.io/spring-boot/docs/2.1.13.RELEASE/reference/html/boot-features-sql.html):
Spring Data JPA repositories 支持三种不同的引导模式:默认模式、延迟模式和懒加载模式。为启用延迟或懒加载引导,请将 spring.data.jpa.repositories.bootstrap-mode 设置为 deferred 或 lazy。
因此,在应用程序属性(或yaml)文件中尝试以下配置:
spring.data.jpa.repositories.bootstrap-mode=lazy

请添加@SpringBootApplication(exclude = HibernateJpaAutoConfiguration.class) - Ahmad Nadeem

3
@SpringBootApplication(exclude = HibernateJpaAutoConfiguration.class)

在启动时,某种方式下Hikari Pool和Hibernate似乎会相互影响。如果数据源不存在,则启动将失败。

使用上面的排除方法,并结合@Lazy注释以按预期延迟初始化。

但这仅限于我不使用Hibernate的情况下才有效。


请添加spring.data.jpa.repositories.bootstrap-mode=lazy - Ahmad Nadeem

0

当使用Hibernate作为JPA实现时,即使在应用程序启动时初始化bean,通过设置这两个Hibernate特定属性,我也能够避免连接到数据库 -

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

以下是Hibernate文档中这些属性的意义:

hibernate.temp.use_jdbc_metadata_defaults (例如true(默认值)或false)

此设置用于控制我们是否应在数据库不可用时(主要在工具使用中)查询JDBC元数据以确定某些设置的默认值。

这里

hibernate.hbm2ddl.auto (例如none(默认值),create-only,drop,create,create-drop,validate和update)

设置为自动执行SchemaManagementTool操作作为SessionFactory生命周期的一部分。有效选项由Action枚举的externalHbm2ddlName值定义:

none 不执行任何操作。

所以,在设置属性之后,只有在执行 SQL 查询时才会建立 JDBC 连接。

0
从Springboot 2.2开始,这是可能的。
在application.properties中添加以下属性。
spring.main.lazy-initialization=true

点击这里以获取更多详细信息。


你确定吗?我尝试过了,但它没有跳过DataSource初始化(java.sql.SQLNonTransientConnectionException: Could not connect to address=(host=localhostx)(port=3306)(type=master) : localhostx)。 - membersound
2
这不会影响所有的Bean吗? - user7294900
是的,你说得对,这将使所有的bean在第一次调用时按需初始化。 - MyTwoCents

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接