如何判断Spring Boot启动时Hibernate是否执行数据库建表操作
实现方案
你当前配置spring.jpa.hibernate.ddl-auto: update的前提下,要实现「仅数据库表不存在时导入初始数据」,有两种稳定可落地的方式,不需要修改现有ddl配置:
方案1:绑定Hibernate生命周期,精准识别建表动作
通过Hibernate开放的SchemaFilter SPI拦截表存在性校验逻辑,标记本次启动是否触发了建表操作,判断逻辑和Hibernate的建表判断完全一致,不会出现误判。
- 第一步:定义状态标记组件,存储是否需要导入初始数据的标识
@Component public class DataInitMarker { private volatile boolean needInit = false; public boolean isNeedInit() { return needInit; } public void markNeedInit() { this.needInit = true; } }
- 第二步:自定义SchemaFilter,在Hibernate判定核心业务表不存在、即将执行建表时打标记
public class InitMarkSchemaFilter implements SchemaFilter { private final SchemaFilter delegate; private final DataInitMarker initMarker; // 替换为你业务里的核心表,只要这张表是首次启动才会创建即可,比如用户表、租户表 private static final String CORE_TABLE_NAME = "sys_user"; public InitMarkSchemaFilter(SchemaFilter delegate, DataInitMarker initMarker) { this.delegate = delegate; this.initMarker = initMarker; } @Override public boolean doTableExist(Table table) { boolean tableExist = delegate.doTableExist(table); if (!tableExist && CORE_TABLE_NAME.equalsIgnoreCase(table.getName())) { initMarker.markNeedInit(); } return tableExist; } // 其余接口方法全部直接委托给默认过滤器实现,不需要额外逻辑 @Override public boolean doNamespaceExist(Namespace namespace) { return delegate.doNamespaceExist(namespace); } @Override public boolean doSequenceExist(Sequence sequence) { return delegate.doSequenceExist(sequence); } @Override public boolean shouldCreateTable(Table table, Metadata metadata) { return delegate.shouldCreateTable(table, metadata); } @Override public boolean shouldCreateSequence(Sequence sequence, Metadata metadata) { return delegate.shouldCreateSequence(sequence, metadata); } @Override public boolean shouldCreateNamespace(Namespace namespace, Metadata metadata) { return delegate.shouldCreateNamespace(namespace, metadata); } @Override public boolean shouldDropTable(Table table) { return delegate.shouldDropTable(table); } @Override public boolean shouldDropSequence(Sequence sequence) { return delegate.shouldDropSequence(sequence); } @Override public boolean shouldDropNamespace(Namespace namespace) { return delegate.shouldDropNamespace(namespace); } @Override public boolean shouldMigrateTable(Table table, Metadata metadata) { return delegate.shouldMigrateTable(table, metadata); } @Override public boolean shouldMigrateSequence(Sequence sequence, Metadata metadata) { return delegate.shouldMigrateSequence(sequence, metadata); } }
- 第三步:注册自定义SchemaFilterProvider,替换Hibernate默认的过滤器提供方
@Component public class InitMarkSchemaFilterProvider implements SchemaFilterProvider { @Autowired private DataInitMarker initMarker; @Override public SchemaFilter getCreateFilter() { return new InitMarkSchemaFilter(SchemaFilterProvider.CREATE_FILTER, initMarker); } @Override public SchemaFilter getDropFilter() { return SchemaFilterProvider.DROP_FILTER; } @Override public SchemaFilter getMigrateFilter() { return new InitMarkSchemaFilter(SchemaFilterProvider.MIGRATE_FILTER, initMarker); } @Override public SchemaFilter getValidateFilter() { return SchemaFilterProvider.VALIDATE_FILTER; } }
如果用Hibernate 6版本,除了注册Spring Bean之外,还需要在resources/META-INF/services路径下新建名为org.hibernate.tool.schema.spi.SchemaFilterProvider的文件,文件内容写你自定义Provider的全限定类名,才能被Hibernate正确加载。
- 第四步:在应用启动完成后,根据标记执行初始数据导入
@Component @Order(Ordered.LOWEST_PRECEDENCE) // 保证最低优先级,等Hibernate DDL执行完再运行 public class InitDataRunner implements ApplicationRunner { @Autowired private DataInitMarker initMarker; @Autowired private InitDataService initDataService; // 自己封装的初始数据导入逻辑 @Override @Transactional(rollbackFor = Exception.class) public void run(ApplicationArguments args) throws Exception { if (initMarker.isNeedInit()) { initDataService.importInitData(); } } }
方案2:JDBC直连校验表存在性(无版本兼容问题)
如果不想对接Hibernate内部SPI,避免Hibernate版本升级API变动带来的兼容问题,可以直接在启动阶段通过JDBC校验核心表是否存在,逻辑更直白:
@Component @Order(Ordered.LOWEST_PRECEDENCE) // 必须加,确保Hibernate建表逻辑先执行 public class InitDataRunner implements ApplicationRunner { @Autowired private JdbcTemplate jdbcTemplate; @Autowired private InitDataService initDataService; private static final String CORE_TABLE_NAME = "sys_user"; @Override @Transactional(rollbackFor = Exception.class) public void run(ApplicationArguments args) throws Exception { boolean coreTableExist = jdbcTemplate.execute((ConnectionCallback<Boolean>) conn -> { DatabaseMetaData metaData = conn.getMetaData(); // 注意表名参数要匹配数据库的大小写规则:MySQL默认不区分大小写直接传即可,PostgreSQL/Oracle需要传大写表名 ResultSet tableRs = metaData.getTables(null, null, CORE_TABLE_NAME, new String[]{"TABLE"}); return tableRs.next(); }); if (!coreTableExist) { initDataService.importInitData(); } } }
注意:如果你的数据库配置了不同的schema,
getTables方法的第二个catalog、第三个schemaPattern参数要传入你实际使用的schema值,避免查不到表。
内容的提问来源于stack exchange,提问作者kensuke1984
相关产品推荐
相关产品推荐

