【问题标题】:Spring Boot - 加载初始数据
【发布时间】:2016-10-28 16:15:25
【问题描述】:

我想知道在应用程序启动之前加载初始数据库数据的最佳方式是什么?我正在寻找的是可以用数据填充我的 H2 数据库的东西。

例如,我有一个域模型“用户”,我可以通过转到 /users 来访问用户,但最初数据库中不会有任何用户,所以我必须创建它们。是否有自动填充数据库的数据?

目前我有一个被容器实例化并为我创建用户的 Bean。

例子:

@Component
public class DataLoader {

    private UserRepository userRepository;

    @Autowired
    public DataLoader(UserRepository userRepository) {
        this.userRepository = userRepository;
        LoadUsers();
    }

    private void LoadUsers() {
        userRepository.save(new User("lala", "lala", "lala"));
    }
}

但我非常怀疑这是最好的方法。还是这样?

【问题讨论】:

  • 这行得通,或者只是将data.sql 和/或schema.sql 添加到初始化数据中。所有这些都是参考指南中的documented(我建议阅读)。
  • 如果对您有帮助,请标记正确答案。
  • 有人能解决这个问题吗?我仍然无法将这些放在一起,也不确定我在这里缺少什么。 git.io/v5SWx

标签: spring spring-boot spring-data


【解决方案1】:

您可以将spring.datasource.data 属性添加到application.properties,列出您要运行的sql 文件。像这样:

spring.datasource.data=classpath:accounts.sql, classpath:books.sql, classpath:reviews.sql

//or (depending on SB version)

spring.sql.init.data-locations=classpath:accounts.sql, classpath:books.sql, file:reviews.sql

然后将运行每个文件中的 sql insert 语句,让您保持整洁。

如果您将文件放在类路径中,例如在src/main/resources 中,它们将被应用。或将classpath: 替换为file: 并使用文件的绝对路径

如果要运行 DDL 类型的 SQL,请使用:

spring.datasource.schema=classpath:create_account_table.sql

// depending on spring version

spring.sql.init.schema-locations=classpath:create_account_table.sql 

编辑:这些解决方案非常适合让您快速启动和运行,但是对于更适合生产的解决方案,值得研究诸如 flywayliquibase 之类的框架。这些框架与 Spring 很好地集成,并提供了一种快速、一致、版本控制的初始化模式和常备数据的方法。

【讨论】:

  • 如果您想要一个外部文件,请不要忘记输入file: 而不是classpath:
  • @dpelisek src/main/resources 应该可以工作。答案已更新。
  • 还有 spring.datasource.schema 用于 DDL 脚本。
  • 或者如果结构resources/sql/file1.sql和file2.sql存在,那么使用:spring.datasource.data=classpath:sql/file1.sql, classpath:sql/file2.sql
  • 这些属性现在已弃用,您应该改用spring.sql.init.data-locationsspring.sql.init.schema-locations
【解决方案2】:

对于那些使用 MysqlDriver 的人,我尝试使用 @bean 注释的 Init 属性,它可以工作。

resources\Scripts路径下创建Schema和Data sql文件后

application.properties中添加一行

spring.jpa.hibernate.ddl-auto=none

编辑应用程序内容:

package com.spring_mvaen.demo;

import org.springframework.boot.CommandLineRunner;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.jdbc.datasource.init.DatabasePopulator;
import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;

@SpringBootApplication
public class DemoApplication implements CommandLineRunner {

  public static void main(String[] args) {
    SpringApplication.run(DemoApplication.class, args);
  }
  @Override
  public void run(String... arg0) throws Exception {
    System.out.println("Hello world from Command Line Runner");
  }

  @Bean(name = "dataSource")
  public DriverManagerDataSource dataSource() {
    DriverManagerDataSource dataSource = new DriverManagerDataSource();
    dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
    dataSource.setUrl("jdbc:mysql://localhost:3306/db_spring_rest?useUnicode=true&useLegacyDatetimeCode=fa    lse&serverTimezone=UTC&createDatabaseIfNotExist=true&allowPublicKeyRetrieval=true&useSSL=false");
    dataSource.setUsername("root");
    dataSource.setPassword("root");

    // schema init
    Resource initSchema = new ClassPathResource("scripts/schema.sql");
    Resource initData = new ClassPathResource("scripts/data.sql");
    DatabasePopulator databasePopulator = new ResourceDatabasePopulator(initSchema, initData);
    DatabasePopulatorUtils.execute(databasePopulator, dataSource);

    return dataSource;
  }


}

【讨论】:

    【解决方案3】:

    如果您来到这里后似乎没有任何效果,那么您可能会受到Spring Boot 2.5 及以后引入的一些更改的影响。

    这是我用于 postgresql 的全部属性集。

    spring:
      sql.init.mode: always   <-----------------
      datasource:
        url: jdbc:postgresql://localhost:5432/products
        username: 
        password: 
      jpa:
        defer-datasource-initialization: true  <------------------
        hibernate:
          ddl-auto: create-drop   <----------------
        database-platform: org.hibernate.dialect.PostgreSQLDialect
    

    我还用&lt;---标记了当前主题的相关属性,以实现以下目的。

    • ORM 供应商将根据 Java 实体模型为您创建数据库架构。
    • 创建数据库模式后,初始数据将从文件data.sql加载到数据库

    Ps:不要忘记在src/main/resources下添加带有初始数据的文件,data.sql

    也作为参考:Spring Boot 2.5 release notes

    【讨论】:

      【解决方案4】:

      有多种方法可以实现这一目标。我更喜欢使用以下选项之一:

      选项 1: 使用 CommandLineRunner bean 初始化:

      @Bean
      public CommandLineRunner loadData(CustomerRepository repository) {
          return (args) -> {
              // save a couple of customers
              repository.save(new Customer("Jack", "Bauer"));
              repository.save(new Customer("Chloe", "O'Brian"));
              repository.save(new Customer("Kim", "Bauer"));
              repository.save(new Customer("David", "Palmer"));
              repository.save(new Customer("Michelle", "Dessler"));
      
              // fetch all customers
              log.info("Customers found with findAll():");
              log.info("-------------------------------");
              for (Customer customer : repository.findAll()) {
                  log.info(customer.toString());
              }
              log.info("");
      
              // fetch an individual customer by ID
              Customer customer = repository.findOne(1L);
              log.info("Customer found with findOne(1L):");
              log.info("--------------------------------");
              log.info(customer.toString());
              log.info("");
      
              // fetch customers by last name
              log.info("Customer found with findByLastNameStartsWithIgnoreCase('Bauer'):");
              log.info("--------------------------------------------");
              for (Customer bauer : repository
                      .findByLastNameStartsWithIgnoreCase("Bauer")) {
                  log.info(bauer.toString());
              }
              log.info("");
          }
      }
      

      选项 2: 使用架构和数据 SQL 脚本进行初始化

      先决条件:

      application.properties

      spring.jpa.hibernate.ddl-auto=none
      

      解释:

      没有ddl-auto SQL 脚本将被忽略 休眠和触发默认行为 - 扫描项目 @Entity 和/或 @Table 带注释的类。

      然后,在您的 MyApplication 类中粘贴:

      @Bean(name = "dataSource")
      public DriverManagerDataSource dataSource() {
          DriverManagerDataSource dataSource = new DriverManagerDataSource();
          dataSource.setDriverClassName("org.h2.Driver");
          dataSource.setUrl("jdbc:h2:~/myDB;MV_STORE=false");
          dataSource.setUsername("sa");
          dataSource.setPassword("");
      
          // schema init
          Resource initSchema = new ClassPathResource("scripts/schema-h2.sql");
          Resource initData = new ClassPathResource("scripts/data-h2.sql");
          DatabasePopulator databasePopulator = new ResourceDatabasePopulator(initSchema, initData);
          DatabasePopulatorUtils.execute(databasePopulator, dataSource);
      
          return dataSource;
      }
      

      scripts 文件夹位于resources 文件夹下(IntelliJ Idea)

      希望对某人有所帮助

      2021 年 4 月更新:这两个选项都非常适合与 Spring Profiles 结合使用,因为这将帮助您避免创建额外的配置文件,让您作为开发人员的生活变得轻松。

      【讨论】:

      • 选项 2 很棒,因为它可以明确证明正在发生的事情。特别是对于多个数据源,可能需要禁用 Spring 的 DataSourceAutoConfiguration.class,在这种情况下,此处提供的所有其他 data.sql 和 schema.sql 解决方案都将停止工作。
      • 如果您想加载初始数据但仍希望 Hibernate 创建 DDL 但您有多个数据源并手动设置它们,那么在这种情况下更好的选择是按照 @ 声明 Spring 的 DataSourceInitializer bean 987654322@ 因为它将为您处理@PostConstruct 问题。
      • spring.jpa.hibernate.ddl-auto=none 解决了我的问题,因为在我设置属性之前,JPA 忽略了我的 data.sql 文件。非常感谢。
      【解决方案5】:

      您可以在 src/main/resources 文件夹中创建一个 data.sql 文件,它会在启动时自动执行。在这个文件中你可以添加一些插入语句,例如:

      INSERT INTO users (username, firstname, lastname) VALUES
        ('lala', 'lala', 'lala'),
        ('lolo', 'lolo', 'lolo');
      

      同样,您也可以创建一个 schema.sql 文件(或 schema-h2.sql)来创建您的架构:

      CREATE TABLE task (
        id          INTEGER PRIMARY KEY,
        description VARCHAR(64) NOT NULL,
        completed   BIT NOT NULL);
      

      虽然通常您不必这样做,因为 Spring boot 已经将 Hibernate 配置为根据您的实体为内存数据库创建架构。如果您真的想使用 schema.sql,则必须通过将其添加到 application.properties 来禁用此功能:

      spring.jpa.hibernate.ddl-auto=none
      

      更多信息可以在Database initialization的文档中找到。


      如果您使用的是 Spring boot 2,则数据库初始化仅适用于嵌入式数据库(H2、HSQLDB、...)。如果您也想将其用于其他数据库,则需要更改 spring.datasource.initialization-mode 属性:

      spring.datasource.initialization-mode=always
      

      如果您使用多个数据库供应商,您可以将文件命名为 data-h2.sqldata-mysql.sql,具体取决于您要使用的数据库平台使用。

      要使其工作,您必须配置 spring.datasource.platform 属性:

      spring.datasource.platform=h2
      

      【讨论】:

      • 感谢@g00glen00b 的清理:“它会在启动时自动执行”。当我使用 addScript(s) 选项将 data.sql 文件包含在我的 bean 配置中时,我遇到了错误。至此,架构尚未构建。
      • @nespapu 你错了,schema.sql/data.sql 文件将在spring.datasource.initializetrue 时执行(这是默认设置)。 spring.jpa.hibernate.ddl-auto 可用于根据您的实体配置生成表,而不是使用 SQL 文件。这在内存数据库上默认启用。这就是我在回答中添加注释的原因,解释说如果您使用内存数据库并且想要使用schema.sql,则需要禁用spring.jpa.hibernate.ddl-auto,否则两者都会尝试创建您的表。
      • 如果你想为你的初始数据使用data-h2.sql文件名,你也应该在你的应用程序属性中设置spring.datasource.platform=h2
      • data.sql 文件被执行每次 spring-boot 应用程序被启动。这意味着如果您有插入语句,它们可能会导致org.h2.jdbc.JdbcSQLException-异常,因为数据已经存在在数据库中。我使用的是嵌入式 H2 数据库,但问题仍然存在。
      • @g00glen00b 遗憾的是,这很容易,因为例如 H2 数据库在使用 MERGE INTO 时遇到了问题。我发现,有一种方法可以使用 import.sql 文件而不是 data.sql 文件来规避这种情况。它需要spring.jpa.hibernate.ddl-autocreatecreate-drop。然后,每当创建模式文件(和/或执行 schema.sql)时,也会执行 import.sql。仍然:感觉像是一种解决方法,而不是创建初始化数据的干净实现。
      【解决方案6】:

      我创建了一个有助于在 Spring Boot 应用程序中加载初始/演示数据的库。你可以在https://github.com/piotrpolak/spring-boot-data-fixtures找到它

      一旦数据夹具启动器在类路径上,它将在应用程序启动时自动尝试加载DICTIONARY数据(此行为可以由属性控制)-您需要做的就是注册一个实现DataFixture的bean .

      我发现通过代码加载初始数据优于使用 SQL 脚本加载:

      • fixture 的逻辑与您的应用程序逻辑/域模型非常接近,并且随着域的发展而进行重构
      • 您可以从增量演示数据更新中受益 - 想象一个 QA 环境,其中包含一些用户数据(在应用程序部署后不需要丢失),但同时您希望为您开发的新功能添加数据

      示例数据夹具:

      /**
       * You can have as many fixture classes as you want.
       * @Order annotation is respected for the fixtures belonging to the same set.
       * You can make your demo database to be incrementally updated with fresh data
       * each time the application is redeployed - all you need to do is to write
       * a good condition in `canBeLoaded()` method.
       */
      @Component
      public class InitialDataFixture implements DataFixture {
      
          private final LanguageRepository languageRepository;
      
          // ...
      
          @Override
          public DataFixtureSet getSet() {
            return DataFixtureSet.DICTIONARY;
          }
      
          /**
           * We want to make sure the fixture is applied once and once only.
           * A more sophisticated condition can be used to create incremental demo data
           * over time without the need to reset the QA database (for example).
           */
          @Override
          public boolean canBeLoaded() {
            return languageRepository.size() == 0;
          }
      
          /**
           * The actual application of the fixture.
           * Assuming that data fixtures are registered as beans, this method can call
           * other services and/or repositories.
           */
          @Override
          public void load() {
            languageRepository.saveAll(Arrays.asList(
                new Language("en-US"), new Language("pl-PL")));
          }
      }
      

      这个概念的灵感来自 Symfony Doctrine Data Fixtures bundle。

      【讨论】:

      • 我认为 - 随着时间的推移 - 你会发现你在 canBeLoaded() 方法中添加了越来越多的功能,然后你可能会选择 Liquibase 或 Flyway。 Liquibase 为您解决的问题之一是并发:如果您的应用程序是多节点的并且您的应用程序的多个实例同时启动会怎样?如果发生不可预见的事情,那么回滚呢?那么你的数据库处于什么状态?这些都是难以解决的问题,Liquibase 和 Flyway 都花费了数年时间来解决问题。
      【解决方案7】:

      一种可能性是使用了不正确的 JDBC URL。确保它是jdbc:h2:mem:testdb

      【讨论】:

        【解决方案8】:

        您可以使用以下代码。在以下代码中,在 Spring Boot 应用程序启动期间发生了数据库插入。

        @SpringBootApplication
        public class Application implements CommandLineRunner {
            
            @Autowired
            private IService<Car> service;
        
            public static void main(String[] args) {
                SpringApplication.run(Application.class, args);
            }
        
            @Override
            public void run(String... args) throws Exception {
                for(int i=1; i<=1000; i++) {
                    Car car = new Car();
                    car.setName("Car Name "+i);
                    book.setPrice(50 + i);
                    service.saveOrUpdate(car);
                }
            }
        
        }
        

        【讨论】:

        • 最佳答案
        【解决方案9】:

        你快到了!

        @Component
        public class DataLoader implements CommandLineRunner {
        
            private UserRepository userRepository;
        
            public DataLoader(UserRepository userRepository) {
                this.userRepository = userRepository;
            }
        
            @Override
            public void run(String... args) throws Exception {
                 LoadUsers()
            }
        
            private void LoadUsers() {
                userRepository.save(new User("lala", "lala", "lala"));
            }
        }
        

        【讨论】:

          【解决方案10】:

          如果您只想插入几行并且您有 JPA 设置。你可以在下面使用

              @SpringBootApplication
                  @Slf4j
                  public class HospitalManagementApplication {
          
                      public static void main(String[] args) {
                          SpringApplication.run(HospitalManagementApplication.class, args);
                      }            
          
                      @Bean
                      ApplicationRunner init(PatientRepository repository) {
                          return (ApplicationArguments args) ->  dataSetup(repository);
                      } 
          
                      public void dataSetup(PatientRepository repository){
                      //inserts
          
               }
          

          【讨论】:

          • 我在用这个长背,记不住。就是这个。谢谢。
          【解决方案11】:

          您可以注册和事件监听器来实现如下:

          @EventListener
          public void seed(ContextRefreshedEvent event) {
              userRepository.save(new User("lala", "lala", "lala"));
          }
          

          当 ContextRefreshEvent 被触发时,我们可以访问应用程序中的所有自动装配的 bean   -  包括模型和存储库。

          【讨论】:

            【解决方案12】:

            我用这种方式解决了类似的问题:

            @Component
            public class DataLoader {
            
                @Autowired
                private UserRepository userRepository;
            
                //method invoked during the startup
                @PostConstruct
                public void loadData() {
                    userRepository.save(new User("user"));
                }
            
                //method invoked during the shutdown
                @PreDestroy
                public void removeData() {
                    userRepository.deleteAll();
                }
            }
            

            【讨论】:

              【解决方案13】:

              在 Spring Boot 2 中,data.sql 不像在 Spring Boot 1.5 中那样与我一起工作

              import.sql

              此外,如果 Hibernate 从头开始​​创建模式(也就是说,如果 ddl-auto 属性设置为 create 或 create-drop),则在启动时会执行 classpath 根目录中名为 import.sql 的文件。

              注意非常重要,如果插入键不能重复,不要使用 ddl-auto 属性设置为更新,因为每次重启都会再次插入相同的数据

              有关更多信息,请访问 spring 网站

              https://docs.spring.io/spring-boot/docs/current/reference/html/howto-database-initialization.html

              【讨论】:

              • 在 Spring 2 中数据库初始化只适用于嵌入式数据库如果你想将它用于其他数据库你需要指定 spring.datasource.initialization-mode=always
              【解决方案14】:

              如果有人即使在关注accepted answer 后仍难以使其工作,对我而言,只需添加我的src/test/resources/application.yml H2 datasource 详细信息:

              spring:
                datasource:
                  platform: h2
                  url: jdbc:h2:mem:test;DB_CLOSE_DELAY=-1
                  driver-class-name: org.h2.Driver
                  username: sa
                  password:
              

              【讨论】:

                【解决方案15】:

                最紧凑的(用于动态数据)将@mathias-dpunkt 解决方案放入MainApp(使用Lombok @AllArgsConstructor):

                @SpringBootApplication
                @AllArgsConstructor
                public class RestaurantVotingApplication implements ApplicationRunner {
                  private final VoteRepository voteRepository;
                  private final UserRepository userRepository;
                
                  public static void main(String[] args) {
                    SpringApplication.run(RestaurantVotingApplication.class, args);
                  }
                
                  @Override
                  public void run(ApplicationArguments args) {
                    voteRepository.save(new Vote(userRepository.getOne(1), LocalDate.now(), LocalTime.now()));
                  }
                }
                

                【讨论】:

                  【解决方案16】:

                  这也可以。

                      @Bean
                      CommandLineRunner init (StudentRepo studentRepo){
                          return args -> {
                              // Adding two students objects
                              List<String> names = Arrays.asList("udara", "sampath");
                              names.forEach(name -> studentRepo.save(new Student(name)));
                          };
                      }
                  

                  【讨论】:

                    【解决方案17】:

                    这是我得到的方法:

                    @Component
                    public class ApplicationStartup implements ApplicationListener<ApplicationReadyEvent> {
                    
                        /**
                         * This event is executed as late as conceivably possible to indicate that
                         * the application is ready to service requests.
                         */
                    
                        @Autowired
                        private MovieRepositoryImpl movieRepository;
                    
                        @Override
                        public void onApplicationEvent(final ApplicationReadyEvent event) {
                            seedData();
                        }
                    
                        private void seedData() {
                            movieRepository.save(new Movie("Example"));
                    
                            // ... add more code
                        }
                    
                    }
                    

                    感谢本文作者:

                    http://blog.netgloo.com/2014/11/13/run-code-at-spring-boot-startup/

                    【讨论】:

                    • 如果您正在使用服务,并且如果服务在自动装配存储库中,这将不起作用
                    【解决方案18】:

                    你可以这样使用:

                    @SpringBootApplication  
                    public class Application {
                    
                    @Autowired
                    private UserRepository userRepository;
                    
                    public static void main(String[] args) {
                        SpringApplication.run(Application.class, args);
                    }
                    
                    @Bean
                    InitializingBean sendDatabase() {
                        return () -> {
                            userRepository.save(new User("John"));
                            userRepository.save(new User("Rambo"));
                          };
                       }
                    }
                    

                    【讨论】:

                      【解决方案19】:

                      如果我只想插入简单的测试数据,我通常会实现 ApplicationRunner。此接口的实现在应用程序启动时运行,并且可以使用例如一个自动装配的存储库,用于插入一些测试数据。

                      我认为这样的实现会比你的更明确一些,因为接口暗示你的实现包含你想要在你的应用程序准备好后直接做的事情。

                      你的实现看起来是这样的。像这样:

                      @Component
                      public class DataLoader implements ApplicationRunner {
                      
                          private UserRepository userRepository;
                      
                          @Autowired
                          public DataLoader(UserRepository userRepository) {
                              this.userRepository = userRepository;
                          }
                      
                          public void run(ApplicationArguments args) {
                              userRepository.save(new User("lala", "lala", "lala"));
                          }
                      }
                      

                      【讨论】:

                        【解决方案20】:

                        您可以简单地在src/main/resources 中创建一个import.sql 文件,Hibernate 将在创建架构时执行它。

                        【讨论】:

                          【解决方案21】:

                          Spring Boot 允许您使用简单的脚本来初始化数据库,使用 Spring Batch

                          不过,如果您想使用更精细的东西来管理数据库版本等,Spring Boot 与Flyway 集成得很好。

                          另请参阅:

                          【讨论】:

                          • 在这里建议春季批次似乎有点过头了。
                          • @Nick,OP 没有提到数据量。无论如何,答案并不全是关于春季批次的。
                          • 在我看来,Flyway 或 Liquibase 是正确的选择。不确定尼克的评论以及更多关于 /src/main/resources 的支持。是的,后者适用于小型项目。 Xtreme Biker 的答案通过非常小的努力提供了更多的功能。
                          猜你喜欢
                          • 2019-04-21
                          • 2018-02-14
                          • 2017-12-18
                          • 2018-05-20
                          • 2018-04-16
                          • 2018-03-22
                          • 1970-01-01
                          • 2016-12-16
                          • 1970-01-01
                          相关资源
                          最近更新 更多