Spring Boot - Carga de datos iniciales

179

Me pregunto cuál es la mejor manera de cargar los datos iniciales de la base de datos antes de que se inicie la aplicación. Lo que estoy buscando es algo que llene mi base de datos H2 con datos.

Por ejemplo, tengo un modelo de dominio "Usuario". Puedo acceder a los usuarios yendo a / users pero inicialmente no habrá ningún usuario en la base de datos, así que tengo que crearlos. ¿Hay alguna forma de llenar la base de datos con datos automáticamente?

Por el momento, tengo un Bean que el contenedor crea instancias y crea usuarios para mí.

Ejemplo:

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

Pero dudo mucho que sea la mejor manera de hacerlo. ¿O es eso?

Lithicas
fuente
44
Eso funcionará, o simplemente agregará data.sqly / o schema.sqliniciará datos. Todo esto está documentado en la guía de referencia (que sugiero leer).
M. Deinum
Marque la respuesta correcta si eso le ayudó.
Reborn
¿Alguien consiguió que esto funcione? Todavía no puedo armar esto y no estoy seguro de lo que me estoy perdiendo aquí. git.io/v5SWx
srini

Respuestas:

294

Simplemente puede crear un archivo data.sql en su carpeta src / main / resources y se ejecutará automáticamente al inicio. En este archivo solo agrega algunas instrucciones de inserción, por ejemplo:

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

Del mismo modo, también puede crear un archivo schema.sql (o schema-h2.sql) para crear su esquema:

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

Aunque normalmente no debería tener que hacer esto ya que Spring boot ya configura Hibernate para crear su esquema basado en sus entidades para una base de datos en memoria. Si realmente desea usar schema.sql, tendrá que deshabilitar esta característica agregando esto a su application.properties:

spring.jpa.hibernate.ddl-auto=none

Se puede encontrar más información en la documentación sobre la inicialización de la base de datos .


Si está utilizando Spring boot 2 , la inicialización de la base de datos solo funciona para bases de datos integradas (H2, HSQLDB, ...). Si desea usarlo también para otras bases de datos, debe cambiar la spring.datasource.initialization-modepropiedad:

spring.datasource.initialization-mode=always

Si está utilizando múltiples proveedores de bases de datos, puede nombrar su archivo data-h2.sql o data-mysql.sql dependiendo de la plataforma de base de datos que desee usar.

Sin spring.datasource.platformembargo, para que eso funcione, deberá configurar la propiedad:

spring.datasource.platform=h2
g00glen00b
fuente
Gracias @ g00glen00b por aclarar: "y se ejecutará automáticamente al inicio". Recibía errores al incluir el archivo data.sql en la configuración de mi bean usando la opción addScript (s). Como en este punto, el esquema aún no se había construido.
Benjamin Slabbert
55
@nespapu Sin embargo, se equivoca, los archivos schema.sql/ data.sqlse ejecutarán cuando spring.datasource.initializesea true(que es el valor predeterminado). spring.jpa.hibernate.ddl-autopuede usarse para generar sus tablas en función de la configuración de su entidad en lugar de usar un archivo SQL. Esto está habilitado por defecto en las bases de datos en memoria. Es por eso que agregué la nota en mi respuesta, explicando que si usa una base de datos en memoria y desea usarla schema.sql, debe deshabilitarla; de lo spring.jpa.hibernate.ddl-autocontrario, ambos intentarán crear su tabla.
g00glen00b
77
Si desea utilizar el data-h2.sqlnombre de archivo para sus datos iniciales, también debe establecer las spring.datasource.platform=h2propiedades de su aplicación.
Jason Evans
1
El archivo data.sql se ejecuta cada vez que se inicia la aplicación Spring-boot. Esto significa que si tiene instrucciones de inserción, pueden causar una org.h2.jdbc.JdbcSQLExceptionexcepción, porque los datos ya están presentes en la base de datos. Estoy usando una base de datos H2 incrustada, pero el problema sigue siendo el mismo.
Igor
1
@ g00glen00b lamentablemente eso es muy fácil, porque la base de datos H2, por ejemplo, tiene problemas MERGE INTO. Me di cuenta de que hay una manera de evitar esto usando un archivo import.sql en lugar de un data.sql . Se requiere spring.jpa.hibernate.ddl-autoa crear o crear soltar . Luego, cada vez que se crea el archivo de esquema (y / o se ejecuta un schema.sql ), también se ejecuta import.sql . Aún así: parece una solución alternativa y no una implementación limpia de la creación de datos de inicio.
Igor
82

Si solo quiero insertar datos de prueba simples, a menudo implemento a ApplicationRunner. Las implementaciones de esta interfaz se ejecutan al inicio de la aplicación y pueden usar, por ejemplo, un repositorio con conexión automática para insertar algunos datos de prueba.

Creo que dicha implementación sería un poco más explícita que la suya porque la interfaz implica que su implementación contiene algo que le gustaría hacer directamente después de que su aplicación esté lista.

Su implementación se vería algo. Me gusta esto:

@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
fuente
32

Como sugerencia intente esto:

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

Opción 2: inicializar con esquemas y scripts de datos

Prerrequisitos: en esto application.propertiesdebes mencionar esto:

spring.jpa.hibernate.ddl-auto=none(de lo contrario las secuencias de comandos serán ignorados por hibernación, y se explorará proyecto para @Entityy / o @Tableclases anotadas)

Luego, en tu MyApplicationclase, pega esto:

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

Dónde scriptsse encuentra la resourcescarpeta debajo de la carpeta (IntelliJ Idea)

Espero que ayude a alguien

Renacido
fuente
3
La opción 2 es excelente ya que proporciona una prueba explícita de lo que está sucediendo. Con múltiples fuentes de datos, especialmente puede ser necesario deshabilitar la clase DataSourceAutoConfiguration. Spring de Spring, en cuyo caso todas las demás soluciones data.sql y schema.sql proporcionadas aquí dejan de funcionar.
kaicarno
1
Si desea cargar datos iniciales pero aún quiere que Hibernate cree el DDL pero tiene múltiples fuentes de datos y los configure manualmente, entonces una mejor opción en este caso es declarar el bean DataSourceInitializer de Spring según stackoverflow.com/a/23036217/3092830 ya que tomará del problema @PostConstruct para usted.
kaicarno
32

Puede agregar una spring.datasource.datapropiedad para application.propertiesenumerar los archivos sql que desea ejecutar. Me gusta esto:

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

Las instrucciones de inserción sql en cada uno de estos archivos se ejecutarán, permitiéndole mantener las cosas ordenadas.

Si coloca los archivos en el classpath, por ejemplo src/main/resources, se aplicarán. O reemplace classpath:con file:y use una ruta absoluta al archivo

robjwilkins
fuente
55
en caso de que desee un archivo externo, no olvide ponerlo en file:lugar de classpath:.
Aleksander Lech
¿Dónde se ubicarán los archivos (accounts.sql, ...)?
dpelisek
1
@dpelisek src / main / resources debería funcionar. Respuesta actualizada
Robjwilkins
14

Puedes usar algo como esto:

@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
fuente
11

Spring Boot le permite usar un script simple para inicializar su base de datos, utilizando Spring Batch .

Aún así, si desea usar algo un poco más elaborado para administrar versiones de DB, etc., Spring Boot se integra bien con Flyway .

Ver también:

Xtreme Biker
fuente
66
sugiriendo que el lote de primavera aquí parece excesivo.
Nick
@ Nick, el OP no menciona la cantidad de datos. De todos modos, la respuesta no se trata solo del lote de primavera.
Xtreme Biker
En mi opinión, Flyway o Liquibase es el camino correcto. No estoy seguro sobre el comentario de Nick y más sobre los votos a favor de / src / main / resources. Sí, este último funcionaría para pequeños proyectos. La respuesta de Xtreme Biker da a través de un esfuerzo muy pequeño mucha más funcionalidad.
Alexandros
10

En Spring Boot 2 data.sql no funcionaba conmigo como en spring boot 1.5

import.sql

Además, un archivo nombrado import.sqlen la raíz del classpath se ejecuta al inicio si Hibernate crea el esquema desde cero (es decir, si la propiedad ddl-auto está configurada para crear o crear-soltar).

Tenga en cuenta que es muy importante que si inserta las claves no se pueden duplicar, no use la propiedad ddl-auto está configurada para actualizar porque con cada reinicio insertará los mismos datos nuevamente

Para más información visita primavera websit

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

Ismail
fuente
En Spring 2, la inicialización de la base de datos solo funciona para bases de datos integradas. Si desea utilizarla para otras bases de datos, debe especificar spring.datasource.initialization-mode = always
Edu Costa
6

Aquí está la forma en que obtuve eso:

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

}

Gracias al autor de este artículo:

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

adkl
fuente
Esto no funciona si está utilizando el servicio, y si el servicio está en el repositorio de
cableado automático
5

Simplemente puede crear un import.sqlarchivo src/main/resourcese Hibernate lo ejecutará cuando se cree el esquema.

Francesco Papagno
fuente
4

Resolví un problema similar de esta manera:

@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();
    }
}
Una bolsa
fuente
1

Si alguien está luchando para que esto funcione incluso después de la respuesta aceptada , para mí solo trabajo agregando mis detalles 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
fuente
1

puedes registrarte y escuchar el evento para lograr eso como a continuación:

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

Cuando se activa ContextRefreshEvent, tenemos acceso a todos los beans con conexión automática en la aplicación, incluidos los modelos y repositorios.

Ahmed Ahmed
fuente
1

Si desea insertar solo algunas filas y tiene la configuración JPA. Puedes usar a continuación

    @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
fuente
1
Estaba usando este largo respaldo, no pude recordarlo. Eso es todo. Gracias.
Freelancer
0

Esto también funcionará.

    @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
fuente
0

El más compacto (para datos dinámicos) puso la solución @ mathias-dpunkt en MainApp (con 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
fuente
0

¡Ya casi estás ahí!

@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
fuente
0

Puedes usar el siguiente código. En el siguiente código, se produce una inserción de la base de datos durante el inicio de la aplicación de inicio de 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);
        }
    }

}
Senthuran
fuente