Spring Boot - ładowanie danych początkowych

180

Zastanawiam się, jak najlepiej załadować wstępne dane bazy danych przed uruchomieniem aplikacji? Szukam czegoś, co wypełni moją bazę danych H2 danymi.

Na przykład mam model domeny „Użytkownik”. Mam dostęp do użytkowników, przechodząc do / users, ale początkowo nie będzie żadnych użytkowników w bazie danych, więc muszę ich utworzyć. Czy istnieje możliwość automatycznego wypełnienia bazy danych danymi?

W tej chwili mam Bean, który jest uruchamiany przez kontener i tworzy dla mnie użytkowników.

Przykład:

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

Ale bardzo wątpię, czy to najlepszy sposób na zrobienie tego. Albo to jest?

Lithicas
źródło
4
To zadziała lub po prostu dodaj data.sqli / lub schema.sqlzainicjuj dane. Wszystko to jest udokumentowane w przewodniku (który proponuję przeczytać).
M. Deinum,
Zaznacz poprawną odpowiedź, jeśli ci to pomogło.
Reborn
Czy ktoś ma to do pracy? Nadal nie jestem w stanie tego poskładać i nie wiem, czego tu brakuje. git.io/v5SWx
srini

Odpowiedzi:

295

Możesz po prostu utworzyć plik data.sql w folderze src / main / resources i zostanie on automatycznie uruchomiony podczas uruchamiania. W tym pliku wystarczy dodać kilka instrukcji insert, np .:

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

Podobnie możesz utworzyć plik schema.sql (lub schema-h2.sql), aby utworzyć schemat:

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

Chociaż zwykle nie powinieneś tego robić, ponieważ rozruch sprężynowy już konfiguruje Hibernację, aby utworzyć schemat w oparciu o twoje encje dla bazy danych w pamięci. Jeśli naprawdę chcesz używać schema.sql, musisz wyłączyć tę funkcję, dodając to do pliku application.properties:

spring.jpa.hibernate.ddl-auto=none

Więcej informacji można znaleźć w dokumentacji dotyczącej inicjalizacji bazy danych .


Jeśli używasz Spring boot 2 , inicjalizacja bazy danych działa tylko dla osadzonych baz danych (H2, HSQLDB, ...). Jeśli chcesz go używać również dla innych baz danych, musisz zmienićspring.datasource.initialization-mode właściwość:

spring.datasource.initialization-mode=always

Jeśli korzystasz z wielu dostawców baz danych, możesz nazwać swój plik data-h2.sql lub data-mysql.sql zależności od platformy bazy danych, której chcesz używać.

Aby to zadziałało, musisz jednak skonfigurować spring.datasource.platformwłaściwość:

spring.datasource.platform=h2
g00glen00b
źródło
Dziękuję @ g00glen00b za wyczyszczenie: „i zostanie wykonane automatycznie przy starcie”. Otrzymywałem błędy, ponieważ umieściłem plik data.sql w konfiguracji mojego komponentu bean przy użyciu opcji addScript (s). W tym momencie schemat nie został jeszcze zbudowany.
Benjamin Slabbert
5
@nespapu Zrobiłeś to źle, pliki schema.sql/ data.sqlzostaną wykonane, gdy spring.datasource.initializejest true(co jest domyślne). spring.jpa.hibernate.ddl-automoże służyć do generowania tabel na podstawie konfiguracji jednostki, a nie przy użyciu pliku SQL. Jest to domyślnie włączone w bazach danych w pamięci. Dlatego dodałem uwagę w mojej odpowiedzi, wyjaśniając, że jeśli używasz bazy danych w pamięci i chcesz użyć schema.sql, musisz wyłączyć, w spring.jpa.hibernate.ddl-autoprzeciwnym razie oba spróbują utworzyć twoją tabelę.
g00glen00b
7
Jeśli chcesz użyć data-h2.sqlnazwy pliku dla swoich początkowych danych, powinieneś również ustawić ją spring.datasource.platform=h2we właściwościach aplikacji.
Jason Evans
1
Plik data.sql jest wykonywany za każdym razem, gdy uruchamiana jest aplikacja uruchamiana wiosną. Oznacza to, że jeśli masz instrukcje wstawiania, mogą one powodować org.h2.jdbc.JdbcSQLExceptionwyjątek, ponieważ dane są już obecne w bazie danych. Używam wbudowanej bazy danych H2, ale problem pozostaje ten sam.
Igor
1
@ g00glen00b niestety nie jest to łatwe, ponieważ na przykład baza danych H2 ma problemy z MERGE INTO. Odkryłem, że istnieje sposób na obejście tego za pomocą pliku import.sql zamiast data.sql . Wymaga ona spring.jpa.hibernate.ddl-auto, aby utworzyć lub tworzyć upuść . Następnie za każdym razem, gdy tworzony jest plik schematu (i / lub wykonywany jest schema.sql ), wykonywany jest również plik import.sql . Mimo to: wydaje się, że jest obejściem, a nie czystą implementacją tworzenia danych init.
Igor
82

Jeśli chcę wstawić proste dane testowe, często implementuję plik ApplicationRunner. Implementacje tego interfejsu są uruchamiane podczas uruchamiania aplikacji i mogą wykorzystywać np. Autowired repozytorium do wstawiania danych testowych.

Myślę, że taka implementacja byłaby nieco bardziej wyraźna niż twoja, ponieważ interfejs sugeruje, że twoja implementacja zawiera coś, co chciałbyś zrobić bezpośrednio po przygotowaniu aplikacji.

Twoja implementacja wyglądałaby na coś. lubię to:

@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"));
    }
}
Mathias Dpunkt
źródło
32

Jako sugestia spróbuj tego:

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

Opcja 2: Zainicjuj za pomocą schematu i skryptów danych

Wymagania wstępne: w application.properties musisz wspomnieć o tym:

spring.jpa.hibernate.ddl-auto=none(w przeciwnym razie skrypty zostaną zignorowane przez hibernacji i będzie to skanowanie za projekt @Entityi / lub @Tableadnotacjami klas)

Następnie w swojej MyApplicationklasie wklej to:

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

Gdzie scriptsfolder znajduje się w resourcesfolderze (IntelliJ Idea)

Mam nadzieję, że to komuś pomoże

Odrodzony
źródło
3
Opcja 2 jest świetna, ponieważ daje wyraźny dowód tego, co się dzieje. Szczególnie w przypadku wielu źródeł danych może być konieczne wyłączenie Spring's DataSourceAutoConfiguration.class, w którym to przypadku wszystkie inne dostarczone tutaj rozwiązania data.sql i schema.sql przestaną działać.
kaicarno
1
Jeśli chcesz załadować dane początkowe, ale nadal chcesz, aby Hibernacja utworzyła DDL, ale masz wiele źródeł danych i skonfigurujesz je ręcznie, lepszym rozwiązaniem w tym przypadku jest zadeklarowanie komponentu bean DataSourceInitializer Springa zgodnie ze stackoverflow.com/a/23036217/3092830 ponieważ zajmie się problemem @PostConstruct za Ciebie.
kaicarno
32

Możesz dodać spring.datasource.datawłaściwość do application.propertieslisty plików sql, które chcesz uruchomić. Lubię to:

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

Następnie zostaną uruchomione instrukcje sql insert w każdym z tych plików, co pozwoli ci zachować porządek.

Jeśli umieścisz pliki w ścieżce klas, na przykład src/main/resourceszostaną one zastosowane. Lub wymienić classpath:z file:i bezwzględną ścieżkę do pliku

robjwilkins
źródło
5
jeśli chcesz mieć zewnętrzny plik, nie zapomnij go wstawić file:zamiast classpath:.
Aleksander Lech
gdzie mają się znajdować pliki (accounts.sql, ...)?
dpelisek
1
@dpelisek src / main / resources powinno działać. Zaktualizowano odpowiedź.
robjwilkins
14

Możesz użyć czegoś takiego:

@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"));
      };
   }
}
Grauzone
źródło
11

Spring Boot umożliwia użycie prostego skryptu do zainicjowania bazy danych za pomocą Spring Batch .

Jeśli jednak chcesz użyć czegoś bardziej złożonego do zarządzania wersjami DB i tak dalej, Spring Boot dobrze integruje się z Flyway .

Zobacz też:

Xtreme Biker
źródło
6
sugerowanie tutaj wiosennej partii wydaje się przesadą.
Nick
@Nick, PO nie wspomina o ilości danych .. Zresztą odpowiedź nie dotyczy wyłącznie wiosennej partii.
Xtreme Biker
Moim zdaniem Flyway lub Liquibase to właściwy sposób. Nie jestem pewien co do komentarza Nicka i więcej informacji o głosach za / src / main / resources. Tak, ta ostatnia sprawdzi się w przypadku małych projektów. Odpowiedź Xtreme Biker daje przy niewielkim wysiłku o wiele większą funkcjonalność.
Alexandros
10

W Spring Boot 2 data.sql nie działała ze mną tak jak w Spring Boot 1.5

import.sql

Ponadto plik o nazwie import.sql w katalogu głównym ścieżki klas jest wykonywany podczas uruchamiania, jeśli Hibernate tworzy schemat od podstaw (to znaczy, jeśli właściwość ddl-auto jest ustawiona na create lub create-drop).

Uwaga bardzo ważne jeśli wstawisz Klucze nie mogą być powielone nie używaj właściwości ddl-auto jest ustawiona na aktualizację ponieważ przy każdym ponownym uruchomieniu wstawi te same dane

Więcej informacji można znaleźć na wiosennej stronie internetowej

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

Ismail
źródło
Inicjalizacja bazy danych Spring 2 działa tylko dla osadzonych baz danych Jeśli chcesz jej użyć dla innych baz danych, musisz określić spring.datasource.initialization-mode = always
Edu Costa
6

Oto sposób, w jaki to zrozumiałem:

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

}

Podziękowania dla autora tego artykułu:

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

adkl
źródło
To nie działa, jeśli korzystasz z usługi, a jeśli usługa jest w repozytorium
autowiring
5

Możesz po prostu utworzyć import.sqlplik w programie, src/main/resourcesa Hibernate wykona go po utworzeniu schematu.

Francesco Papagno
źródło
4

Podobny problem rozwiązałem w ten sposób:

@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();
    }
}
Torba
źródło
1

Jeśli ktoś ma problemy, aby to zadziałało nawet po zaakceptowanej odpowiedzi , dla mnie pracuję tylko dodając w moim src/test/resources/application.ymlH2 datasourceszczegóły:

spring:
  datasource:
    platform: h2
    url: jdbc:h2:mem:test;DB_CLOSE_DELAY=-1
    driver-class-name: org.h2.Driver
    username: sa
    password:
Dherik
źródło
1

możesz zarejestrować się i słuchać wydarzeń, aby to osiągnąć, jak poniżej:

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

Po uruchomieniu ContextRefreshEvent uzyskujemy dostęp do wszystkich autowired bean w aplikacji - w tym modeli i repozytoriów.

Ahmed Ahmed
źródło
1

Jeśli chcesz wstawić tylko kilka wierszy i masz konfigurację JPA. Możesz użyć poniżej

    @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

     }
Niraj Sonawane
źródło
1
Używałem tego dawno temu, nie mogłem sobie przypomnieć. To jest to. dzięki.
Freelancer
0

To też zadziała.

    @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)));
        };
    }
Udara SS Liyanage
źródło
0

Najbardziej kompaktowe (dla danych dynamicznych) rozwiązanie @ mathias-dpunkt w MainApp (z 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()));
  }
}
Grigorij Kislin
źródło
0

Jesteś prawie na miejscu!

@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"));
    }
}
Emmanuel Osimosu
źródło
0

Możesz użyć poniższego kodu. W poniższym kodzie wstawienie bazy danych następuje podczas uruchamiania aplikacji rozruchu sprężynowego.

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

}
Senthuran
źródło