Весняне завантаження - завантаження початкових даних


180

Мені цікаво, який найкращий спосіб завантажити початкові дані бази даних до запуску програми? Що я шукаю - це те, що заповнить мою базу даних H2 даними.

Наприклад, у мене є доменна модель "Користувач". Я можу отримати доступ до користувачів, перейшовши на / користувачів, але спочатку в базі даних не буде користувачів, тому мені доведеться їх створювати. Чи все-таки є можливість заповнення бази даних автоматично?

На даний момент у мене є 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вводити дані. Все це задокументовано у довідковому посібнику (який я пропоную прочитати).
М. Дейнум

Будь ласка, позначте правильну відповідь, якщо це допомогло вам.
Відродження

Хтось змусив це працювати? Я досі не в змозі скласти це разом і не впевнений, чого мені тут не вистачає. git.io/v5SWx
srini

Відповіді:


295

Ви можете просто створити файл data.sql у папці src / main / ресурси, і він буде автоматично виконаний при запуску. У цей файл ви просто додаєте деякі заяви про вставку, наприклад:

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

Хоча зазвичай вам не доведеться цього робити, оскільки весняний завантажувач вже налаштовує сплячий режим для створення вашої схеми на основі ваших об'єктів для бази даних в пам'яті. Якщо ви дійсно хочете використовувати schema.sql, вам доведеться відключити цю функцію, додавши її у свою програму application.properties:

spring.jpa.hibernate.ddl-auto=none

Більше інформації можна знайти в документації про ініціалізацію бази даних .


Якщо ви використовуєте Spring boot 2 , ініціалізація бази даних працює лише для вбудованих баз даних (H2, HSQLDB, ...). Якщо ви хочете використовувати його і для інших баз даних, вам потрібно змінити spring.datasource.initialization-modeвластивість:

spring.datasource.initialization-mode=always

Якщо ви використовуєте декількох постачальників баз даних, ви можете назвати дані вашого файлу -h2.sql або data-mysql.sql залежно від платформи бази даних, яку ви хочете використовувати.

Щоб зробити це, вам доведеться налаштувати spring.datasource.platformвластивість:

spring.datasource.platform=h2

Дякуємо @ g00glen00b за очищення: "і це буде автоматично виконано при запуску". Я отримував помилки, коли я включав файл data.sql у конфігурацію мого боба за допомогою параметра addScript (s). Оскільки в цей момент схема ще не була побудована.
Бенджамін Слабберт

5
@nespapu Ви помилилися, але schema.sql/ data.sqlфайли виконуватимуться, коли spring.datasource.initializeє true(що за замовчуванням). spring.jpa.hibernate.ddl-autoможе використовуватися для створення таблиць на основі конфігурації вашої сутності, а не для використання файлу SQL. Це за замовчуванням увімкнено в базах даних пам'яті. Ось чому я додав примітку до своєї відповіді, пояснюючи, що якщо ви використовуєте базу даних в пам'яті, і ви хочете використовувати її schema.sql, вам потрібно відключити, spring.jpa.hibernate.ddl-autoінакше обидва спробують створити вашу таблицю.
g00glen00b

7
Якщо ви хочете використовувати data-h2.sqlім'я файлу для своїх початкових даних, вам слід також встановити spring.datasource.platform=h2властивості програми.
Джейсон Еванс

1
Файл data.sql виконується кожного разу при запуску програми Spring -boot. Це означає, що якщо у вас є оператори вставки, вони можуть спричинити org.h2.jdbc.JdbcSQLException-виділення, оскільки дані вже є в базі даних. Я використовую вбудовану базу даних H2, але проблема залишається тією ж.
Ігор

1
@ g00glen00b, на жаль, це все, але не просто, тому що, наприклад, у базі даних H2 є проблеми MERGE INTO. Я зрозумів, що є спосіб обійти це за допомогою файлу import.sql замість data.sql . Вона вимагає , spring.jpa.hibernate.ddl-autoщоб створити або створити падіння . Потім, коли створюється файл схеми (та / або виконується schema.sql ), також виконується import.sql . І все-таки: це виглядає як обхідне рішення, а не чиста реалізація створення даних init.
Ігор

82

Якщо я просто хочу вставити прості тестові дані, я часто реалізую 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's DataSourceAutoConfiguration.class, і в цьому випадку всі інші рішення data.sql і schema.sql, що надаються тут, перестають працювати.
kaicarno

1
Якщо ви хочете завантажити початкові дані, але все ще хочете, щоб Hibernate створив DDL, але у вас є декілька джерел даних та встановити їх вручну, тоді кращим варіантом у цьому випадку є оголошення оголошень SpringSean DataSourceInitializer відповідно до stackoverflow.com/a/23036217/3092830 як це займе проблему @PostConstruct для вас.
kaicarno

32

Ви можете додати spring.datasource.dataвластивість до application.propertiesсписку файлів sql, які потрібно запустити. Подобається це:

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

Після цього запускаються оператори sql вставки у кожному з цих файлів, що дозволяє тримати речі в порядку.

Якщо ви помістите файли в classpath, наприклад, в src/main/resourcesних вони будуть застосовані. Або замінити classpath:з file:і використовувати абсолютний шлях до файлу


5
якщо ви хочете, щоб зовнішній файл не забудьте поставити file:замість classpath:.
Олександр Лех

де повинні бути розміщені файли (account.sql, ...)?
dpelisek

1
@dpelisek src / main / ресурси повинні працювати. Відповідь оновлено.
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, ОП не згадує кількість даних .. У всякому разі, відповідь не вся про весняну партію.
Xtreme Biker

На мою думку, Flyway або Liquibase - це правильний шлях. Не впевнений у коментарі Ніка та більше про оновлення / src / main / ресурсів. Так, останні працювали б для невеликих проектів. Відповідь Xtreme Biker дає за дуже малі зусилля набагато більше функціональності.
Олександрос

10

У Spring Boot 2 data.sql не працював зі мною, як у весняному завантаженні 1.5

import.sql

Крім того, import.sqlпри запуску виконується файл, названий у корені classpath, якщо Hibernate створює схему з нуля (тобто, якщо властивість ddl-auto встановлено для створення або створення-падіння).

Зауважте, що дуже важливо, якщо ви вставляєте Ключі не можна дублювати, не використовуйте властивість ddl-auto, встановлене для оновлення, оскільки при кожному перезапуску знову вставляти однакові дані

Для отримання додаткової інформації ви переглядаєте весняний веб-сайт

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/


Це не працює, якщо ви користуєтесь сервісом, а якщо служба в сховищі
автопроводки


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про Н2 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 запускається, ми отримуємо доступ до всіх автоматичних бобів у програмі - включаючи моделі та сховища.


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

Ви можете використовувати наведений нижче код. У наступному коді вставлення бази даних відбувається під час запуску програми для весняного завантаження.

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

}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.