Spring Boot - Chargement des données initiales

181

Je me demande quelle est la meilleure façon de charger les données initiales de la base de données avant le démarrage de l'application? Ce que je recherche, c'est quelque chose qui remplira ma base de données H2 de données.

Par exemple, j'ai un modèle de domaine "Utilisateur". Je peux accéder aux utilisateurs en accédant à / users mais au départ il n'y aura pas d'utilisateurs dans la base de données donc je dois les créer. Est-il possible de remplir automatiquement la base de données avec des données?

Pour le moment, j'ai un Bean qui est instancié par le conteneur et crée des utilisateurs pour moi.

Exemple:

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

Mais je doute fort que ce soit la meilleure façon de procéder. Ou est-ce?

Lithicas
la source
4
Cela fonctionnera, ou simplement ajouter data.sqlet / ou schema.sqlinitier des données .. Tout cela est documenté dans le guide de référence (que je suggère de lire).
M. Deinum
Veuillez cocher la bonne réponse si cela vous a aidé.
Reborn
Quelqu'un a-t-il fait fonctionner ça? Je suis toujours incapable de mettre cela ensemble et je ne suis pas sûr de ce qui me manque ici. git.io/v5SWx
srini

Réponses:

297

Vous pouvez simplement créer un fichier data.sql dans votre dossier src / main / resources et il sera automatiquement exécuté au démarrage. Dans ce fichier, vous ajoutez simplement des instructions d'insertion, par exemple:

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

De même, vous pouvez également créer un fichier schema.sql (ou schema-h2.sql) pour créer votre schéma:

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

Bien que normalement, vous ne devriez pas avoir à le faire car Spring Boot configure déjà Hibernate pour créer votre schéma en fonction de vos entités pour une base de données en mémoire. Si vous voulez vraiment utiliser schema.sql, vous devrez désactiver cette fonctionnalité en l'ajoutant à votre application.properties:

spring.jpa.hibernate.ddl-auto=none

Plus d'informations peuvent être trouvées dans la documentation sur l' initialisation de la base de données .


Si vous utilisez Spring boot 2 , l'initialisation de la base de données ne fonctionne que pour les bases de données embarquées (H2, HSQLDB, ...). Si vous souhaitez également l'utiliser pour d'autres bases de données, vous devez modifier la spring.datasource.initialization-modepropriété:

spring.datasource.initialization-mode=always

Si vous utilisez plusieurs fournisseurs de bases de données, vous pouvez nommer votre fichier data-h2.sql ou data-mysql.sql en fonction de la plate-forme de base de données que vous souhaitez utiliser.

Pour que cela fonctionne, vous devrez configurer la spring.datasource.platformpropriété:

spring.datasource.platform=h2
g00glen00b
la source
Merci @ g00glen00b pour l'effacement: "et il sera automatiquement exécuté au démarrage". J'obtenais des erreurs lorsque j'ai inclus le fichier data.sql dans la configuration de mon bean en utilisant l'option addScript (s). À ce stade, le schéma n'avait pas encore été construit.
Benjamin Slabbert
5
@nespapu Vous vous trompez cependant, les fichiers schema.sql/ data.sqlseront exécutés quand spring.datasource.initializeest true(qui est la valeur par défaut). spring.jpa.hibernate.ddl-autopeut être utilisé pour générer vos tables en fonction de la configuration de votre entité plutôt qu'en utilisant un fichier SQL. Ceci est activé par défaut sur les bases de données en mémoire. C'est pourquoi j'ai ajouté la note dans ma réponse, expliquant que si vous utilisez une base de données en mémoire et que vous souhaitez utiliser le schema.sql, vous devez désactiver, spring.jpa.hibernate.ddl-autosinon les deux essaieront de créer votre table.
g00glen00b
7
Si vous souhaitez utiliser le data-h2.sqlnom de fichier pour vos données initiales, vous devez également définir spring.datasource.platform=h2les propriétés de votre application.
Jason Evans
1
Le fichier data.sql est exécuté à chaque fois que l'application Spring-Boot est lancée. Cela signifie que si vous avez des instructions d'insertion, elles peuvent provoquer une org.h2.jdbc.JdbcSQLExceptionexception -exception, car les données sont déjà présentes dans la base de données. J'utilise une base de données H2 intégrée, mais le problème reste le même.
Igor
1
@ g00glen00b, malheureusement, c'est presque facile, car la base de données H2, par exemple, a des problèmes avec MERGE INTO. J'ai compris qu'il existe un moyen de contourner cela en utilisant un fichier import.sql au lieu d'un data.sql . Il nécessite spring.jpa.hibernate.ddl-autode créer ou de créer-déposer . Ensuite, chaque fois que le fichier de schéma est créé (et / ou qu'un schema.sql est exécuté), le fichier import.sql est également exécuté. Pourtant: cela ressemble à une solution de contournement et non à une implémentation propre de la création de données init.
Igor
82

Si je veux simplement insérer des données de test simples, j'implémente souvent un fichier ApplicationRunner. Les implémentations de cette interface sont exécutées au démarrage de l'application et peuvent utiliser par exemple un référentiel auto-câblé pour insérer des données de test.

Je pense qu'une telle implémentation serait légèrement plus explicite que la vôtre car l'interface implique que votre implémentation contient quelque chose que vous aimeriez faire directement après que votre application soit prête.

Votre implémentation ressemblerait à qc. comme ça:

@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
la source
33

Vous pouvez ajouter une spring.datasource.datapropriété pour application.propertiesrépertorier les fichiers SQL que vous souhaitez exécuter. Comme ça:

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

Les instructions d'insertion SQL dans chacun de ces fichiers seront ensuite exécutées, vous permettant de garder les choses en ordre.

Si vous placez les fichiers dans le classpath, par exemple, src/main/resourcesils seront appliqués. Ou remplacez classpath:par file:et utilisez un chemin absolu vers le fichier

Robjwilkins
la source
5
au cas où vous voudriez un fichier externe, n'oubliez pas de le mettre à la file:place de classpath:.
Aleksander Lech
où doivent être situés les fichiers (accounts.sql, ...)?
dpelisek
1
@dpelisek src / main / resources devrait fonctionner. Réponse mise à jour.
robjwilkins
32

Comme suggestion, essayez ceci:

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

Option 2: initialiser avec des scripts de schéma et de données

Prérequis: dans application.propertiesvous devez mentionner ceci:

spring.jpa.hibernate.ddl-auto=none(sinon les scripts seront ignorés par hibernate, et il analysera le projet pour @Entityet / ou les @Tableclasses annotées)

Ensuite, dans votre MyApplicationclasse, collez ceci:

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

Emplacement du scriptsdossier sous le resourcesdossier (IntelliJ Idea)

J'espère que ça aide quelqu'un

Reborn
la source
3
L'option 2 est excellente car elle donne une preuve explicite de ce qui se passe. Avec plusieurs sources de données, en particulier, il peut être nécessaire de désactiver DataSourceAutoConfiguration.class de Spring, auquel cas toutes les autres solutions data.sql et schema.sql fournies ici cessent de fonctionner.
kaicarno
1
Si vous souhaitez charger les données initiales mais que vous souhaitez toujours qu'Hibernate crée le DDL mais que vous avez plusieurs sources de données et que vous les configurez manuellement, une meilleure option dans ce cas est de déclarer le bean DataSourceInitializer de Spring selon stackoverflow.com/a/23036217/3092830 car cela prendra pour vous le problème @PostConstruct.
kaicarno
14

Vous pouvez utiliser quelque chose comme ceci:

@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
la source
11

Spring Boot vous permet d'utiliser un script simple pour initialiser votre base de données, à l'aide de Spring Batch .

Néanmoins, si vous souhaitez utiliser quelque chose d'un peu plus élaboré pour gérer les versions de base de données, etc., Spring Boot s'intègre bien à Flyway .

Voir également:

Motard Xtreme
la source
6
suggérer ici un lot de printemps semble exagéré.
Nick
@Nick, l'OP ne mentionne pas la quantité de données .. Quoi qu'il en soit, la réponse n'est pas que du lot de printemps.
Xtreme Biker
À mon avis, Flyway ou Liquibase est la bonne voie à suivre. Je ne suis pas sûr du commentaire de Nick et plus sur les votes positifs de / src / main / resources. Oui, ce dernier fonctionnerait pour de petits projets. La réponse de Xtreme Biker donne par un très petit effort tellement plus de fonctionnalités.
Alexandros
10

Dans Spring Boot 2 data.sql ne fonctionnait pas avec moi comme dans Spring Boot 1.5

import.sql

De plus, un fichier nommé import.sqlà la racine du chemin de classe est exécuté au démarrage si Hibernate crée le schéma à partir de zéro (c'est-à-dire si la propriété ddl-auto est définie sur create ou create-drop).

Remarque très important si vous insérez les clés ne peuvent pas être dupliquées, n'utilisez pas la propriété ddl-auto est définie sur mise à jour car à chaque redémarrage, les mêmes données seront à nouveau insérées

Pour plus d'informations, visitez le site Web du printemps

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

Ismail
la source
Au printemps 2, l'initialisation de la base de données ne fonctionne que pour les bases de données embarquées Si vous souhaitez l'utiliser pour d'autres bases de données, vous devez spécifier spring.datasource.initialization-mode = always
Edu Costa
6

Voici comment je l'ai obtenu:

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

}

Merci à l'auteur de cet article:

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

adkl
la source
Cela ne fonctionne pas si vous utilisez le service, et si le service dans le référentiel d'
autowiring
5

Vous pouvez simplement créer un import.sqlfichier dans src/main/resourceset Hibernate l'exécutera lorsque le schéma sera créé.

Francesco Papagno
la source
4

J'ai résolu un problème similaire de cette façon:

@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();
    }
}
Un sac
la source
1

Si quelqu'un a du mal à faire fonctionner cela même en suivant la réponse acceptée , pour moi, je ne travaille qu'en ajoutant mes détails src/test/resources/application.ymlH2 datasource:

spring:
  datasource:
    platform: h2
    url: jdbc:h2:mem:test;DB_CLOSE_DELAY=-1
    driver-class-name: org.h2.Driver
    username: sa
    password:
Dherik
la source
1

vous pouvez vous inscrire et écouter l'événement pour y parvenir comme ci-dessous:

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

Lorsque le ContextRefreshEvent est déclenché, nous avons accès à tous les beans câblés automatiquement dans l'application, y compris les modèles et les référentiels.

Ahmed Ahmed
la source
1

Si vous voulez insérer seulement quelques lignes et que vous avez JPA Setup. Vous pouvez utiliser ci-dessous

    @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
la source
1
J'utilisais ce long dos, je ne pouvais pas me souvenir. Ça y est. Merci.
Freelance
0

Cela fonctionnera également.

    @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
la source
0

La solution la plus compacte (pour les données dynamiques) met @ mathias-dpunkt dans MainApp (avec 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()));
  }
}
Grigory Kislin
la source
0

Tu y es presque!

@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
la source
0

Vous pouvez utiliser le code ci-dessous. Dans le code suivant, une insertion de base de données se produit lors du démarrage de l'application 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);
        }
    }

}
Senthuran
la source