Spring Boot - Carregando Dados Iniciais

179

Gostaria de saber qual é a melhor maneira de carregar dados iniciais do banco de dados antes do aplicativo iniciar? O que estou procurando é algo que preencha meu banco de dados H2 com dados.

Por exemplo, eu tenho um modelo de domínio "Usuário". Posso acessar os usuários acessando / users, mas inicialmente não haverá usuários no banco de dados, então tenho que criá-los. Existe alguma maneira de preencher o banco de dados com dados automaticamente?

No momento, tenho um Bean que é instanciado pelo contêiner e cria usuários para mim.

Exemplo:

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

Mas duvido muito que seja a melhor maneira de fazê-lo. Ou é?

Lithicas
fonte
4
Isso funcionará, ou simplesmente adicione data.sqle / ou schema.sqliniciará dados. Tudo isso está documentado no guia de referência (que eu sugiro que leia).
M. Deinum 27/06
Por favor, marque a resposta correta se isso o ajudou.
Re
Alguém fez isso funcionar? Ainda não consigo reunir isso e não tenho certeza do que estou perdendo aqui. git.io/v5SWx
srini

Respostas:

294

Você pode simplesmente criar um arquivo data.sql na pasta src / main / resources e ele será executado automaticamente na inicialização. Neste arquivo, você apenas adiciona algumas instruções de inserção, por exemplo:

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

Da mesma forma, você pode criar um arquivo schema.sql (ou schema-h2.sql) para criar seu esquema:

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

Embora normalmente você não precise fazer isso, pois a inicialização do Spring já configura o Hibernate para criar seu esquema com base em suas entidades para um banco de dados na memória. Se você realmente deseja usar o schema.sql, precisará desativar esse recurso adicionando-o ao seu application.properties:

spring.jpa.hibernate.ddl-auto=none

Mais informações podem ser encontradas na documentação sobre inicialização do banco de dados .


Se você estiver usando o Spring boot 2 , a inicialização do banco de dados funcionará apenas para bancos de dados incorporados (H2, HSQLDB, ...). Se você quiser usá-lo para outros bancos de dados, precisará alterar a spring.datasource.initialization-modepropriedade:

spring.datasource.initialization-mode=always

Se você estiver usando vários fornecedores de banco de dados, poderá nomear seu arquivo data-h2.sql ou data-mysql.sql dependendo da plataforma de banco de dados que deseja usar.

Para fazer isso funcionar, você precisará configurar a spring.datasource.platformpropriedade:

spring.datasource.platform=h2
g00glen00b
fonte
Obrigado @ g00glen00b por esclarecer: "e ele será executado automaticamente na inicialização". Eu estava recebendo erros ao incluir o arquivo data.sql na configuração do meu bean usando a opção addScript (s). Nesse ponto, o esquema ainda não havia sido construído.
Benjamin Slabbert
5
@nespapu Você está errado, porém, os arquivos schema.sql/ data.sqlserão executados quando spring.datasource.initializeestão true(que é o padrão). spring.jpa.hibernate.ddl-autopode ser usado para gerar suas tabelas com base na configuração da sua entidade, em vez de usar um arquivo SQL. Por padrão, isso está ativado nos bancos de dados em memória. É por isso que adicionei a nota na minha resposta, explicando que, se você usa um banco de dados na memória e deseja usá- schema.sqllo, precisa desativar, spring.jpa.hibernate.ddl-autocaso contrário, ambos tentarão criar sua tabela.
g00glen00b
7
Se você deseja usar o data-h2.sqlnome do arquivo para seus dados iniciais, defina spring.datasource.platform=h2também as propriedades do aplicativo.
Jason Evans
1
O arquivo data.sql é executado sempre que o aplicativo de inicialização por mola é acionado. Isso significa que, se você tiver instruções de inserção, elas podem causar uma org.h2.jdbc.JdbcSQLExceptionexceção, porque os dados já estão presentes no banco de dados. Estou usando um banco de dados H2 incorporado, mas o problema permanece o mesmo.
Igor
1
@ g00glen00b infelizmente isso é fácil, porque o banco de dados H2, por exemplo, tem problemas MERGE INTO. Eu descobri que existe uma maneira de contornar isso usando um arquivo import.sql em vez de um data.sql . Ela exige spring.jpa.hibernate.ddl-autoa criar ou criar-drop . Então, sempre que o arquivo do esquema for criado (e / ou um schema.sql for executado), o import.sql também será executado. Ainda: parece uma solução alternativa e não uma implementação limpa da criação de dados init.
Igor
82

Se eu apenas quiser inserir dados de teste simples, implementei ApplicationRunner . As implementações dessa interface são executadas na inicialização do aplicativo e podem usar, por exemplo, um repositório com conexão automática para inserir alguns dados de teste.

Acho que essa implementação seria um pouco mais explícita que a sua, porque a interface implica que sua implementação contém algo que você gostaria de fazer diretamente depois que seu aplicativo estivesse pronto.

Sua implementação seria sth. como isso:

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

Como sugestão, tente o seguinte:

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

Opção 2: inicializar com scripts de esquema e dados

Pré-requisitos: application.propertiesvocê deve mencionar isso:

spring.jpa.hibernate.ddl-auto=none(caso contrário, os scripts serão ignorados pelo hibernate e verificarão o projeto em busca de classes @Entitye / ou @Tableanotadas)

Em seguida, em sua MyApplicationclasse, cole este:

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

Onde a scriptspasta está localizada na resourcespasta (IntelliJ Idea)

Espero que ajude alguém

Renascido
fonte
3
A opção 2 é ótima, pois fornece prova explícita do que está acontecendo. Com várias fontes de dados, especialmente, pode ser necessário desativar o DataSourceAutoConfiguration.class do Spring. Nesse caso, todas as outras soluções data.sql e schema.sql fornecidas aqui param de funcionar.
kaicarno 18/05/19
1
Se você deseja carregar dados iniciais, mas ainda assim deseja que o Hibernate crie a DDL, mas você tem várias fontes de dados e as configura manualmente, então uma opção melhor nesse caso é declarar o bean DataSourceInitializer do Spring como stackoverflow.com/a/23036217/3092830 pois será necessário o problema do @PostConstruct para você.
kaicarno 18/05/19
32

Você pode adicionar uma spring.datasource.datapropriedade para application.propertieslistar os arquivos sql que deseja executar. Como isso:

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

As instruções sql insert em cada um desses arquivos serão executadas, permitindo que você mantenha as coisas organizadas.

Se você colocar os arquivos no caminho de classe, por exemplo, src/main/resourceseles serão aplicados. Ou substitua classpath:por file:e use um caminho absoluto para o arquivo

robjwilkins
fonte
5
caso você queira um arquivo externo, não esqueça de colocar em file:vez de classpath:.
Aleksander Lech
onde devem estar os arquivos (accounts.sql, ...)?
dpelisek
1
@dpelisek src / main / resources deve funcionar. Resposta atualizada.
robjwilkins
14

Você pode usar algo como isto:

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

O Spring Boot permite que você use um script simples para inicializar seu banco de dados, usando o Spring Batch .

Ainda assim, se você deseja usar algo um pouco mais elaborado para gerenciar versões de banco de dados e assim por diante, o Spring Boot se integra bem ao Flyway .

Veja também:

Xtreme Biker
fonte
6
sugerir o lote da primavera aqui parece um exagero.
Nick
@ Nick, o OP não menciona a quantidade de dados. De qualquer forma, a resposta não é toda sobre o lote da primavera.
Xtreme Biker
Na minha opinião, Flyway ou Liquibase é o caminho certo a seguir. Não tenho certeza sobre o comentário de Nick e mais ainda sobre os upvotes de / src / main / resources. Sim, o último funcionaria para pequenos projetos. A resposta do Xtreme Biker oferece, através de um esforço muito pequeno, muito mais funcionalidade.
Alexandros
10

No Spring Boot 2, o data.sql não estava funcionando comigo, como no Spring Boot 1.5

import.sql

Além disso, um arquivo nomeado import.sqlna raiz do caminho de classe é executado na inicialização se o Hibernate criar o esquema do zero (ou seja, se a propriedade ddl-auto estiver configurada para criar ou criar e soltar).

Observe muito importante se você inserir Chaves não puderem ser duplicadas, não use a propriedade ddl-auto configurada para atualizar, pois a cada reinicialização, os mesmos dados serão inseridos novamente.

Para mais informações, visite o site Spring Websit

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

Ismail
fonte
Na Primavera de 2 de inicialização do banco de dados funciona somente para bancos de dados incorporados Se você quiser usá-lo para outros bancos de dados você precisa especificar spring.datasource.initialization-mode = sempre
Edu Costa
6

Aqui está a maneira que eu consegui isso:

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

}

Graças ao autor deste artigo:

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

adkl
fonte
Isso não funciona se você estiver usando o serviço, e se a notificação de autowiring repositório
silentsudo
5

Você pode simplesmente criar um import.sqlarquivo src/main/resourcese o Hibernate o executará quando o esquema for criado.

Francesco Papagno
fonte
4

Resolvi um problema semelhante desta maneira:

@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();
    }
}
Uma mochila
fonte
1

Se alguém está lutando para que isso funcione, mesmo seguindo a resposta aceita , para mim só trabalho adicionando meus detalhes src/test/resources/application.ymldo H2 datasource:

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

você pode se registrar e ouvir eventos para conseguir isso como abaixo:

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

Quando o ContextRefreshEvent é acionado, obtemos acesso a todos os beans conectados automaticamente no aplicativo - incluindo modelos e repositórios.

Ahmed Ahmed
fonte
1

Se você deseja inserir apenas algumas linhas e você tem a Instalação JPA. Você pode usar abaixo

    @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
fonte
1
Eu estava usando tanto tempo atrás, não consegui me lembrar. É isso. obrigado.
Freelancer
0

Isso também irá 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
fonte
0

A solução mais compacta (para dados dinâmicos) colocou a solução @ mathias-dpunkt no MainApp (com 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
fonte
0

Você está quase lá!

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

Você pode usar o código abaixo. No código a seguir, ocorre uma inserção de banco de dados durante a inicialização do aplicativo de inicialização por mola.

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