Spring Boot - กำลังโหลดข้อมูลเริ่มต้น


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เพื่อเริ่มต้นข้อมูล .. ทั้งหมดนี้เป็นเอกสารในคู่มืออ้างอิง (ซึ่งฉันแนะนำให้อ่าน)
M. Deinum

โปรดทำเครื่องหมายคำตอบที่ถูกต้องหากช่วยคุณได้
เกิดใหม่

มีใครได้รับสิ่งนี้ให้ทำงาน ฉันยังคงไม่สามารถรวมมันเข้าด้วยกันและไม่แน่ใจว่าสิ่งที่ฉันหายไปที่นี่ git.io/v5SWx
srini

คำตอบ:


295

คุณสามารถสร้างไฟล์data.sqlในโฟลเดอร์src / main / resources ของคุณและมันจะถูกเรียกใช้งานโดยอัตโนมัติเมื่อเริ่มต้น ในไฟล์นี้คุณเพียงแค่เพิ่มคำสั่งแทรกเช่น:

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

คุณสามารถสร้างไฟล์schema.sql (หรือ schema-h2.sql) เพื่อสร้าง schema ของคุณได้เช่นกัน:

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

แม้ว่าโดยปกติแล้วคุณไม่ควรทำเช่นนี้เนื่องจาก Spring boot กำหนดค่า Hibernate เพื่อสร้างสคีมาของคุณตามเอนทิตีของคุณสำหรับฐานข้อมูลในหน่วยความจำ หากคุณต้องการใช้ schema.sql จริงๆคุณจะต้องปิดใช้งานคุณสมบัตินี้โดยเพิ่มสิ่งนี้ลงใน application.properties ของคุณ:

spring.jpa.hibernate.ddl-auto=none

ข้อมูลเพิ่มเติมสามารถพบได้ที่เอกสารเกี่ยวกับการเริ่มต้นฐานข้อมูล


หากคุณใช้Spring boot 2 การเริ่มต้นฐานข้อมูลจะทำงานเฉพาะกับฐานข้อมูลแบบฝัง (H2, HSQLDB, ... ) หากคุณต้องการใช้สำหรับฐานข้อมูลอื่นคุณต้องเปลี่ยนspring.datasource.initialization-modeคุณสมบัติ:

spring.datasource.initialization-mode=always

หากคุณใช้ผู้จำหน่ายฐานข้อมูลหลายรายคุณสามารถตั้งชื่อไฟล์data-h2.sqlหรือdata-mysql.sql ของคุณขึ้นอยู่กับแพลตฟอร์มฐานข้อมูลที่คุณต้องการใช้

ในการทำให้งานนั้นคุณจะต้องกำหนดค่าspring.datasource.platformคุณสมบัติแม้ว่า:

spring.datasource.platform=h2

ขอบคุณ @ g00glen00b สำหรับการล้างค่า: "และจะถูกเรียกใช้โดยอัตโนมัติเมื่อเริ่มต้น" ฉันได้รับข้อผิดพลาดเนื่องจากฉันรวมไฟล์ data.sql ไว้ในการกำหนดค่าของ bean โดยใช้ตัวเลือก addScript ณ จุดนี้ยังไม่ได้สร้างสคีมา
Benjamin Slabbert

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ในคุณสมบัติแอปพลิเคชันของคุณเช่นกัน
Jason Evans

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 ถูกสร้างขึ้น (และ / หรือschema.sqlจะถูกดำเนินการ), import.sqlจะถูกดำเนินการเช่นกัน ถึงกระนั้น: มันให้ความรู้สึกเหมือนเป็นวิธีแก้ปัญหาและไม่ใช่การสร้างข้อมูลเริ่มต้นที่สะอาด
อิกอร์

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)

หวังว่าจะช่วยใครซักคน


3
ตัวเลือกที่ 2 นั้นยอดเยี่ยมเพราะให้การพิสูจน์อย่างชัดเจนว่าเกิดอะไรขึ้น ด้วยหลายแหล่งข้อมูลโดยเฉพาะอย่างยิ่งอาจจำเป็นต้องปิดการใช้งาน DataSourceAutoConfiguration.class ของ Spring ซึ่งในกรณีนี้ data.sql และ schema.sql อื่น ๆ ทั้งหมดจะหยุดทำงาน
kaicarno

1
หากคุณต้องการโหลดข้อมูลเริ่มต้น แต่ยังคงต้องการให้ไฮเบอร์เนตสร้าง DDL แต่คุณมีหลายแหล่งข้อมูลและตั้งค่าด้วยตนเองตัวเลือกที่ดีกว่าในกรณีนี้คือการประกาศ DataSourceInitializer ของ Spring ของ Spring ตาม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 insert ในแต่ละไฟล์เหล่านี้จะถูกเรียกใช้ทำให้คุณสามารถเก็บสิ่งต่าง ๆ ได้

หากคุณวางไฟล์ใน classpath ตัวอย่างเช่นไฟล์src/main/resourcesเหล่านั้นจะถูกนำไปใช้ หรือแทนที่classpath:ด้วยfile:และใช้พา ธ สัมบูรณ์ไปยังไฟล์


5
ในกรณีที่คุณต้องการแฟ้มภายนอกไม่ลืมที่จะใส่แทนfile: classpath:
Aleksander Lech

จะอยู่ที่ไหนไฟล์ (accounts.sql, ... ) ตั้งอยู่?
dpelisek

1
@dpelisek src / main / resources ควรทำงานได้ อัปเดตคำตอบแล้ว
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ชุดฤดูใบไม้ผลิ

อย่างไรก็ตามหากคุณต้องการใช้ข้อมูลเพิ่มเติมเล็กน้อยเพื่อจัดการเวอร์ชัน DB เป็นต้น Spring Spring จะทำงานร่วมกับFlywayได้ดี

ดูสิ่งนี้ด้วย:


6
แนะนำชุดสปริงที่นี่ดูเหมือน overkill
Nick

@Nick, OP ไม่ได้พูดถึงจำนวนข้อมูล .. อย่างไรก็ตามคำตอบนั้นไม่ได้เกี่ยวกับสปริงแบตช์
Xtreme Biker

ในความคิดของฉัน Flyway หรือ Liquibase เป็นวิธีที่ถูกต้อง ไม่แน่ใจเกี่ยวกับความคิดเห็นของ Nick และเพิ่มเติมเกี่ยวกับ upvotes ของ / src / main / resources ใช่สิ่งหลังนี้จะใช้ได้กับโครงการขนาดเล็ก คำตอบของ Xtreme Biker มอบให้ผ่านความพยายามเล็ก ๆ น้อย ๆ เพื่อการใช้งานที่มากขึ้น
Alexandros

10

ใน Spring Boot 2 data.sql ไม่ทำงานกับฉันเหมือนใน spring boot 1.5

import.sql

นอกจากนี้ไฟล์ที่มีชื่อimport.sqlในรูทของ classpath จะถูกเรียกใช้งานเมื่อเริ่มต้นหาก Hibernate สร้างสคีมาจากศูนย์ (กล่าวคือถ้าคุณสมบัติ ddl-auto ถูกตั้งค่าให้สร้างหรือสร้าง - ปล่อย)

หมายเหตุสำคัญมากถ้าคุณแทรกคีย์ไม่สามารถทำซ้ำได้อย่าใช้คุณสมบัติ ddl-auto ถูกตั้งค่าให้อัปเดตเพราะแต่ละครั้งที่รีสตาร์ทจะแทรกข้อมูลเดิมอีกครั้ง

สำหรับข้อมูลเพิ่มเติมคุณเยี่ยมชม spring websit

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/


สิ่งนี้ไม่ทำงานหากคุณใช้บริการและหากบริการในที่เก็บข้อมูลอัตโนมัติ
silentsudo

5

คุณสามารถสร้างimport.sqlไฟล์ในsrc/main/resourcesและไฮเบอร์เนตจะดำเนินการเมื่อสร้างสคีมา


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.ymlH2 ของฉันเท่านั้น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 ถูกไล่ออกเราจะได้รับการเข้าถึง autowired beans ทั้งหมดในแอปพลิเคชัน - รวมถึงรุ่นและที่เก็บ


1

หากคุณต้องการแทรกเพียงไม่กี่แถวและคุณมี JPA Setup คุณสามารถใช้ด้านล่าง

    @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
ฉันใช้หลังยาวนี้ไม่สามารถเรียกคืนได้ นี่ไง. ขอบคุณ
Freelancer

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 solution ลงใน MainApp (พร้อมลอมบอก@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);
        }
    }

}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.