Spring Boot - 加载初始数据

300

我想知道在应用程序启动之前加载初始数据库数据的最佳方法是什么? 我正在寻找的是一种可以填充我的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"));
    }
}

但我非常怀疑这是否是最好的做法。难道不是吗?


9
可以这样做,或者简单地将 data.sql 和/或 schema.sql 添加到初始化数据中。所有这些都在参考指南中有记录,建议您阅读该指南。 - M. Deinum
如果这有帮助到您,请标记正确的答案。 - Malakai
22个回答

462
你可以在你的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

更多信息可以在关于数据库初始化的文档中找到。
如果您正在使用Spring Boot 2,数据库初始化仅适用于嵌入式数据库(如H2、HSQLDB等)。如果您还想将其用于其他数据库,您需要更改初始化模式属性。
spring.sql.init.mode=always # Spring Boot >=v2.5.0
spring.datasource.initialization-mode=always # Spring Boot <v2.5.0

如果您正在使用多个数据库供应商,您可以根据您想要使用的数据库平台将文件命名为 data-h2.sqldata-mysql.sql
为了使其正常工作,您需要配置数据源平台属性。
spring.sql.init.platform=h2 # Spring Boot >=v2.5.0
spring.datasource.platform=h2 # Spring Boot <v2.5.0

9
@nespapu,您的理解有误。当spring.datasource.initializetrue(默认值)时,将执行schema.sql/data.sql文件。spring.jpa.hibernate.ddl-auto可用于基于实体配置生成表,而不是使用SQL文件。这在内存数据库中默认启用。这就是我在答案中添加注释的原因,解释如果您使用内存数据库并希望使用schema.sql,则需要禁用spring.jpa.hibernate.ddl-auto,否则两者都会尝试创建您的表格。 - g00glen00b
9
如果您想使用“data-h2.sql”文件名作为初始数据,您还应该在应用程序属性中设置“spring.datasource.platform=h2”。 - Jason Evans
2
每次启动spring-boot应用程序时都会执行data.sql文件。这意味着,如果您有插入语句,它们可能会导致org.h2.jdbc.JdbcSQLException异常,因为数据已经存在于数据库中。我正在使用嵌入式H2数据库,但问题仍然存在。 - Igor
2
@g00glen00b 很遗憾,这并不容易,因为例如 H2 数据库在 MERGE INTO 方面存在问题。我发现,可以使用 import.sql 文件代替 data.sql 来绕过这个问题。它需要使用 spring.jpa.hibernate.ddl-auto 来进行 createcreate-drop。然后每当模式文件被创建(和/或执行了 schema.sql),import.sql 也会被执行。尽管如此:它感觉像是一个变通方法,而不是创建初始数据的干净实现。 - Igor
1
在我看来,这确实应该是被接受的答案。 - Andrew Landsverk
显示剩余16条评论

127

如果我只是想插入简单的测试数据,我通常会实现一个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"));
    }
}

97

您可以在application.properties中添加spring.datasource.data属性,列出您想要运行的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插入语句运行,以保持整洁。

如果您将文件放在类路径中,例如在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集成良好,提供了一种快速、一致且版本受控的初始化模式和静态数据的方式。


10
如果您需要一个外部文件,请记得使用 file: 而不是 classpath: - Aleksander Lech
这些文件(accounts.sql,...)应该放在哪里? - dpelisek
1
@dpelisek src/main/resources 应该能够正常工作。答案已更新。 - robjwilkins
2
还有spring.datasource.schema用于DDL脚本。 - Helder Pereira
3
如果存在结构资源/sql/file1.sql和file2.sql,则使用:spring.datasource.data=classpath:sql/file1.sql,classpath:sql/file2.sql。 - Marek Bernád
5
这些属性现在已过时,您应该改用 spring.sql.init.data-locationsspring.sql.init.schema-locations - maechler

52

有多种方法可以实现这一点。 我更喜欢使用以下其中一种选项:

选项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,Hibernate将忽略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)

希望对某些人有所帮助

更新04-2021:这两个选项都很适合与Spring Profiles结合使用,这将帮助您避免创建额外的配置文件,使开发者的生活更轻松。


4
选项2非常好,因为它提供了明确的证据表明正在发生什么。特别是在存在多个数据源的情况下,可能需要禁用Spring的DataSourceAutoConfiguration.class,否则这里提供的所有其他data.sql和schema.sql解决方案都将停止工作。 - kaicarno
1
如果您想加载初始数据,但仍希望Hibernate创建DDL,但您有多个数据源并手动设置它们,则在这种情况下更好的选择是根据https://dev59.com/n37aa4cB1Zd3GeqPs6bN#23036217声明Spring的DataSourceInitializer bean,因为它将为您处理@PostConstruct问题。 - kaicarno
spring.jpa.hibernate.ddl-auto=none 修复了我的问题,因为在设置了该属性之前,JPA 忽略了我的 data.sql 文件。非常感谢。 - Tomas Antos
你也可以使用 "ApplicationListener<ContextRefreshedEvent>"。 - undefined

17

你可以使用类似以下的方式:

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

15

在Spring Boot 2中,data.sql对我来说无法正常工作,而在spring boot 1.5中可以。

import.sql

此外,如果Hibernate从头开始创建模式(即,ddl-auto属性设置为create或create-drop),则会在启动时执行位于类路径根目录下的名为import.sql的文件。

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

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

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


在Spring 2中,数据库初始化仅适用于嵌入式数据库。如果您想将其用于其他数据库,则需要指定spring.datasource.initialization-mode=always。 - Edu Costa

12

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

但如果您想使用更复杂的东西来管理DB版本等内容,Spring Boot与Flyway集成得很好。

另请参阅:


9
建议使用Spring Batch可能有些过于复杂。 - Nick
@Nick,OP没有提到数据量的大小。无论如何,答案并不全是关于Spring Batch的。 - Aritz
在我看来,Flyway或Liquibase是正确的选择。我不确定Nick的评论以及/src/main/resources的赞成意见。是的,对于小型项目,后者可以起作用。Xtreme Biker的答案通过非常少的努力提供了更多的功能。 - Alexandros

10

您可以在 src/main/resources 中创建一个 import.sql 文件,Hibernate 将在创建模式时执行它。


10

如果你来到这里发现似乎没有任何作用,那么可能是由于一些在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

为了实现以下目的,我还使用了 <--- 标记出当前主题的相关属性。

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

提示:不要忘记在 src/main/resources 下添加包含初始数据的文件 data.sql

参考资料:Spring Boot 2.5 发布说明


不要让ORM创建模式/数据。数据/模式的生命周期比orm库更长,因此在ORM之外控制数据/模式。 - Espresso
@Espresso 我同意你的观点,但是对于用于测试目的的数据库来说,这是最好的选择。问题还提到了 h2 数据库,通常被用作测试数据库。 - Panagiotis Bougioukos
1
延迟数据源初始化:true 这就是我在寻找的那一行,谢谢! 使用H2数据库创建本地Spring配置文件加上这一行是个好主意。 但我同意其他人的观点,你应该总是在生产数据库上创建/修改(例如使用Flyway)。 - YoloAcc

8
这是我得到它的方法:
@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/

这篇文章介绍了如何在Spring Boot启动时自动运行代码。

如果您正在使用服务,并且服务在自动装配存储库中,则此方法无效。 - silentsudo

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