Spring引导-加载初始数据

我想知道在应用程序启动之前加载初始数据库数据的最佳方法是什么?我要找的是一些东西,将填补我的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"));
}
}

但我非常怀疑这是不是最好的办法。真的是这样吗?

415332 次浏览

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

不过,如果你想使用一些更详细的东西来管理DB版本等等,Spring Boot与再经集成得很好。

参见:

你可以在你的src / main /资源文件夹中创建一个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来基于你的实体为内存数据库创建模式。如果你真的想使用模式。SQL,你必须禁用这个功能添加到你的application.properties:

spring.jpa.hibernate.ddl-auto=none

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


如果你使用弹簧靴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

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

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

有多种方法可以实现这一点。我倾向于使用以下选项之一:

使用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)

希望它能帮助到别人

更新04-2021:这两个选项都很适合与春天的概要文件结合使用,因为这将帮助您避免创建额外的配置文件,使您作为开发人员的生活更轻松。

你可以这样使用:

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

你可以在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 insert语句,以便保持整洁。

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

如果你想运行DDL类型的SQL,那么使用:

spring.datasource.schema=classpath:create_account_table.sql


// depending on spring version


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

编辑:这些解决方案可以让你快速启动和运行,但是对于更适合生产的解决方案,值得考虑诸如再经liquibase这样的框架。这些框架很好地与spring集成,并提供了一种快速、一致、版本控制的初始化模式和站立数据的方法。

下面是我得到答案的方法:

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

这也是可行的。

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

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

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

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

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

如果有人在努力使这个工作,即使是在接受的答案之后,对我来说,只在我的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:

在Spring Boot 2数据中。SQL不像spring boot 1.5那样适合我

import.sql

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

非常重要的一点是,如果你插入键不可复制,不要使用ddl-auto属性被设置为更新,因为每次重启都会再次插入相同的数据

欲了解更多信息,请访问spring网站

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

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

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

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


}

你快成功了!

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

您可以使用下面的代码。在下面的代码中,数据库插入发生在spring引导应用程序启动期间。

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


}

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

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

一旦数据fixture启动器位于类路径上,它将在应用程序启动时自动尝试加载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数据fixture包。

如果你来到这里并你好像什么都不管用,那么你可能会受到一些由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加载到数据库

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

也可以作为参考:Spring Boot 2.5发布说明

对于那些使用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;
}




}