Spring Boot中@PostConstruct为何先于schema.sql执行及解决办法
问题描述
我使用Spring Boot开发Java程序,通过schema.sql初始化数据库,建表语句如下:
CREATE TABLE IF NOT EXISTS products ( id varchar(200), tid varchar(200), price numeric, category varchar(20), quantity INTEGER, product_name varchar(200), stock INTEGER, img varchar(200) );
想要实现程序启动后自动爬取网页数据并插入数据库,尝试使用@PostConstruct注解,但执行时提示表products不存在,相关代码如下:
public class PosServiceImp implements PosService { ... @PostConstruct public void init() throws IOException{ List<Product> JDProducts = parseJD("java"); // 从网页爬取的数据 for (Product product:JDProducts){ addProduct(product); } } ... }
错误信息片段:
Caused by: org.h2.jdbc.JdbcSQLSyntaxErrorException: Table "PRODUCTS" not found (this database is empty); SQL statement: select p1_0.id,p1_0.category,p1_0.img,p1_0.product_name,p1_0.price,p1_0.quantity,p1_0.stock,p1_0.tid from products p1_0 where p1_0.id=? [42104-224] at org.h2.message.DbException.getJdbcSQLException(DbException.java:514) ... ... 115 common frames omitted
请问该现象的原因是什么?还有哪些方法可以实现我需要的功能?
原因分析
@PostConstruct标记的方法会在Spring Bean初始化完成后立刻执行,但这个时机早于Spring Boot执行schema.sql的时间:
- Spring Boot启动流程中,先完成所有Bean的创建和初始化,触发
@PostConstruct方法 - 而
schema.sql的执行是在数据源初始化、ORM框架(如Hibernate)配置完成之后才会启动,时间点晚于@PostConstruct的执行时机 - 所以当
init()方法调用addProduct()操作数据库时,表还未被创建,自然抛出表不存在的错误
可行解决方案
1. 实现ApplicationRunner接口
这是Spring Boot官方推荐的启动后执行逻辑的方式,run()方法会在应用完全启动、数据库初始化完成后执行:
@Component public class ProductDataInitializer implements ApplicationRunner { @Autowired private PosService posService; @Override public void run(ApplicationArguments args) throws IOException { List<Product> JDProducts = posService.parseJD("java"); for (Product product : JDProducts) { posService.addProduct(product); } } }
只需将该类标记为@Component,让Spring自动扫描并加载即可。
2. 监听ContextRefreshedEvent事件
Spring容器刷新完成(所有Bean初始化、数据库初始化完成)后会发布该事件,通过监听事件触发数据插入逻辑:
@Component public class ProductDataListener implements ApplicationListener<ContextRefreshedEvent> { @Autowired private PosService posService; private boolean isInitialized = false; // 标记是否已执行,避免容器多次刷新重复执行 @Override public void onApplicationEvent(ContextRefreshedEvent event) { if (!isInitialized) { try { List<Product> JDProducts = posService.parseJD("java"); for (Product product : JDProducts) { posService.addProduct(product); } isInitialized = true; } catch (IOException e) { e.printStackTrace(); } } } }
3. 配置DataSourceInitializer(适用于旧版Spring Boot)
通过自定义DataSourceInitializer来控制SQL执行和数据插入的顺序,确保表创建完成后再插入数据:
@Configuration public class DataInitConfig { @Autowired private DataSource dataSource; @Autowired private ApplicationContext applicationContext; @Bean public DataSourceInitializer dataSourceInitializer() { DataSourceInitializer initializer = new DataSourceInitializer(); initializer.setDataSource(dataSource); // 执行schema.sql建表 ResourceDatabasePopulator schemaPopulator = new ResourceDatabasePopulator(); schemaPopulator.addScript(new ClassPathResource("schema.sql")); initializer.setDatabasePopulator(schemaPopulator); // 建表完成后执行数据爬取和插入 initializer.setDatabasePopulator(ds -> { PosService posService = applicationContext.getBean(PosService.class); try { List<Product> JDProducts = posService.parseJD("java"); for (Product product : JDProducts) { posService.addProduct(product); } } catch (IOException e) { e.printStackTrace(); } }); return initializer; } }
注意:如果Spring Boot已经自动配置了schema.sql执行,需要避免重复执行建表语句。
内容的提问来源于stack exchange,提问作者Baykin
相关产品推荐
相关产品推荐

