Spring Boot-加载初始数据


179

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

例如,我有一个域模型“ User”,我可以通过转到/ 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"));
    }
}

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


4
将工作,或简单地添加data.sql和/或schema.sql以初始化数据..这一切都记录在案的参考指南(我建议阅读)英寸
M. Deinum '16

如果可以,请标记正确的答案。
重生

有人有这个工作吗?我仍然无法将它们放在一起,不确定我在这里缺少什么。git.io/v5SWx
srini

Answers:


291

您只需在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引导已经将Hibernate配置为基于您的实体为内存数据库创建架构。如果您确实要使用schema.sql,则必须通过将其添加到application.properties中来禁用此功能:

spring.jpa.hibernate.ddl-auto=none

可以在有关数据库初始化的文档中找到更多信息。


如果您使用的是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的配置中时,出现了错误。至此,该架构尚未构建。
本杰明·拉伯特

5
@nespapu但是,您错了,schema.sql/ data.sql文件将在spring.datasource.initializeis 时执行true(这是默认设置)。spring.jpa.hibernate.ddl-auto可用于根据您的实体配置而不是使用SQL文件来生成表。默认情况下在内存数据库中启用此功能。这就是为什么我在答案中添加注释的原因,并解释说如果您使用内存数据库并且要使用schema.sql,则需要禁用它,spring.jpa.hibernate.ddl-auto否则两者都会尝试创建表。
g00glen00b

7
如果要使用data-h2.sql文件名作为初始数据,则还应该spring.datasource.platform=h2在应用程序属性中进行设置。
杰森·埃文斯

1
每次启动spring-boot应用程序,都会执行data.sql文件。这意味着,如果您有插入语句,它们可能会导致org.h2.jdbc.JdbcSQLException-exception,因为数据库中已经存在数据。我正在使用嵌入式H2数据库,但问题仍然存在。
伊戈尔(Igor)

1
不幸的是,@ g00glen00b几乎很容易,因为例如H2数据库存在问题MERGE INTO。我发现,有一种方法可以使用import.sql文件而不是data.sql来规避此问题。它要求spring.jpa.hibernate.ddl-auto创建创建降。然后每当创建模式文件(和/或一个schema.sql文件被执行)时,import.sql被以及执行。仍然:感觉像是一种变通方法,而不是干净的创建初始化数据的实现。
伊戈尔(Igor)

81

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

32

作为建议,请尝试以下操作:

@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:使用架构和数据脚本初始化

先决条件:application.properties您必须提及此:

spring.jpa.hibernate.ddl-auto=none(否则脚本将被休眠忽略,它将扫描项目中的@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)

希望对别人有帮助


3
选项2很棒,因为它可以清楚地证明正在发生的事情。特别是对于多个数据源,可能有必要禁用Spring的DataSourceAutoConfiguration.class,在这种情况下,此处提供的所有其他data.sql和schema.sql解决方案都将停止工作。
kaicarno

1
如果您想加载初始数据但仍然希望Hibernate创建DDL,但是您有多个数据源并手动设置它们,那么在这种情况下,更好的选择是按照stackoverflow.com/a/23036217/3092830声明Spring的DataSourceInitializer bean。因为它将为您解决@PostConstruct问题。
kaicarno

32

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

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

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

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


5
万一你需要一个外部文件别忘了file:代替classpath:
Aleksander Lech

文件(accounts.sql,...)应位于何处?
dpelisek

1
@dpelisek src / main / resources应该可以工作。答案已更新。
robjwilkins

14

您可以使用如下形式:

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

11

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

不过,如果您想使用更多详细信息来管理数据库版本等,Spring Boot可以与Flyway很好地集成。

也可以看看:


6
暗示这里的春季批处理似乎有些过分。
尼克

@ Nick,OP没有提及数据量。无论如何,答案并不完全是关于春季批处理的。
Xtreme Biker,

我认为,Flyway或Liquibase是正确的选择。不确定Nick的评论以及有关/ src / main / resources投票的更多信息。是的,后者将适用于小型项目。Xtreme Biker的答案通过很小的努力就提供了更多的功能。
亚历山德罗斯

9

在Spring Boot 2中,data.sql无法像Spring Boot 1.5中那样与我一起使用

import.sql

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

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

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

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


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

6

这是我的方法:

@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/


如果您正在使用服务,并且服务在自动装配库中,则此方法不起作用
–silentsudo


4

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

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

1

如果有人在努力使它即使按照已接受的答案也可以正常工作,对我来说,只能添加我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:

1

您可以注册并使用事件监听器来实现以下目标:

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

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


1

如果您只想插入几行,并且您有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

     }

1
我用了很久才回来,没能回忆起。就是这个。谢谢。
自由职业者

0

这也将起作用。

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

0

最紧凑的(用于动态数据)将@ 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()));
  }
}

0

您快到了!

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

0

您可以使用以下代码。在以下代码中,在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);
        }
    }

}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.