Spring Boot中在PhysicalNamingStrategyStandardImpl中访问@Repository的问题
解决方案
你的问题核心是Hibernate初始化时机早于Spring Data JPA Repository:PhysicalNamingStrategy在JPA容器启动阶段就会被调用,而此时LookupRepo还在初始化流程中,导致循环依赖。下面是几个可行的解决思路:
1. 延迟获取Repository实例
用Spring的Provider接口延迟加载Repo,避免在初始化阶段直接注入依赖,直到真正需要查询时才获取实例:
@Component public class LookupNamingStrategy extends PhysicalNamingStrategyStandardImpl { @Autowired private Provider<LookupRepo> repoProvider; @Override public Identifier toPhysicalTableName(Identifier logicalName, JdbcEnvironment context) { if(logicalName.getCanonicalName().equalsIgnoreCase("LOOKUP")) { return super.toPhysicalTableName(logicalName, context); } // 延迟获取Repo,此时Spring已完成Repo的初始化 LookupRepo repo = repoProvider.get(); LookUp lookUp = repo.findFirstByOrderById(); return Identifier.toIdentifier(lookUp.getTable_name()); } }
2. 绕过Spring Data JPA,直接用JDBC查询
既然Repo还没准备好,直接用原生JDBC查询查找表,不需要依赖Spring Data的封装:
@Component public class LookupNamingStrategy extends PhysicalNamingStrategyStandardImpl { @Autowired private DataSource dataSource; @Override public Identifier toPhysicalTableName(Identifier logicalName, JdbcEnvironment context) { if(logicalName.getCanonicalName().equalsIgnoreCase("LOOKUP")) { return super.toPhysicalTableName(logicalName, context); } String tableName = null; // 用原生JDBC查询查找表 try (Connection conn = dataSource.getConnection()) { String sql = "SELECT table_name FROM lookup ORDER BY id LIMIT 1"; try (PreparedStatement stmt = conn.prepareStatement(sql); ResultSet rs = stmt.executeQuery()) { if(rs.next()) { tableName = rs.getString("table_name"); } } } catch (SQLException e) { throw new RuntimeException("查询表名失败", e); } // 若未查询到表名,返回默认逻辑名 return tableName != null ? Identifier.toIdentifier(tableName) : super.toPhysicalTableName(logicalName, context); } }
3. 提前加载表名到配置中
在Spring上下文初始化完成前,先查询表名并存入环境变量,后续NamingStrategy直接从配置读取:
第一步:写初始化类加载表名
@Component public class TableNameInitializer implements ApplicationListener<ContextRefreshedEvent> { @Autowired private DataSource dataSource; @Autowired private ConfigurableEnvironment environment; @Override public void onApplicationEvent(ContextRefreshedEvent event) { // 确保只执行一次(根上下文触发时执行) if(event.getApplicationContext().getParent() == null) { String tableName = queryTableName(); if(tableName != null) { // 将表名存入环境变量,优先级最高 Map<String, Object> props = Collections.singletonMap("dynamic.table.name", tableName); environment.getPropertySources().addFirst(new MapPropertySource("dynamicTableName", props)); } } } private String queryTableName() { try (Connection conn = dataSource.getConnection()) { String sql = "SELECT table_name FROM lookup ORDER BY id LIMIT 1"; try (PreparedStatement stmt = conn.prepareStatement(sql); ResultSet rs = stmt.executeQuery()) { return rs.next() ? rs.getString("table_name") : null; } } catch (SQLException e) { throw new RuntimeException("预加载表名失败", e); } } }
第二步:修改NamingStrategy读取配置
@Component public class LookupNamingStrategy extends PhysicalNamingStrategyStandardImpl { @Autowired private Environment environment; @Override public Identifier toPhysicalTableName(Identifier logicalName, JdbcEnvironment context) { if(logicalName.getCanonicalName().equalsIgnoreCase("LOOKUP")) { return super.toPhysicalTableName(logicalName, context); } String tableName = environment.getProperty("dynamic.table.name"); return tableName != null ? Identifier.toIdentifier(tableName) : super.toPhysicalTableName(logicalName, context); } }
关于“检查Bean是否初始化”的思路
你提到的编程式检查Bean状态是可行的,但实用性不高:因为Hibernate只会在启动时调用一次toPhysicalTableName,如果此时Repo还在初始化,返回默认表名后就不会再更新了。如果非要尝试,可以实现BeanFactoryAware来获取BeanFactory,调用isSingletonCurrentlyInCreation方法判断,但不推荐作为最终解决方案。
内容的提问来源于stack exchange,提问作者ng.newbie
相关产品推荐
相关产品推荐

