
h2 2.0+ 版本升级后,users 表名会与系统表 information_schema.users 冲突,导致元数据查询(如 getmetadata().gettables())意外返回双份结果;根本原因在于 h2 默认 schema 为 public,但元数据接口默认包含所有 schema(含 information_schema),需显式过滤或调整权限策略。
h2 2.0+ 版本升级后,users 表名会与系统表 information_schema.users 冲突,导致元数据查询(如 getmetadata().gettables())意外返回双份结果;根本原因在于 h2 默认 schema 为 public,但元数据接口默认包含所有 schema(含 information_schema),需显式过滤或调整权限策略。
在 H2 数据库中,USERS 并非保留关键字,但它是 INFORMATION_SCHEMA 模式下真实存在的系统表(存储用户账户信息)。当您创建名为 USERS 的业务表时,若未明确限定 schema,H2 会将其置于默认 schema —— 即 PUBLIC(由 Constants.SCHEMA_MAIN 常量定义)。此时数据库中实际存在两个 USERS 表:
- INFORMATION_SCHEMA.USERS:只读系统表,记录数据库用户(如 SA, pinf 等);
- PUBLIC.USERS:您创建的业务表(如用户信息表)。
问题核心不在于“表名冲突报错”,而在于 JDBC 元数据 API 的行为变更:H2 2.0+ 中,DatabaseMetaData.getTables(null, null, null, ...) 默认返回所有 schema 下匹配的表(包括 INFORMATION_SCHEMA),而 H2 1.x 时代该方法默认仅返回 PUBLIC 及用户可写 schema 的表。这正是您在切换用户 pinf 后仍看到两份 USERS 的根本原因——REVOKE ALL ON SCHEMA INFORMATION_SCHEMA FROM pinf 仅限制 DML 权限,不影响元数据可见性。
✅ 正确解决方案(三选一)
方案 1:元数据查询时显式指定 schema 模式(推荐)
在调用 getTables() 时,将 schemaPattern 参数设为 "PUBLIC",排除系统 schema:
// ✅ 正确:只查 PUBLIC schema 下的表
ResultSet rs = metaData.getTables(null, "PUBLIC", "%", new String[]{"TABLE"});
while (rs.next()) {
System.out.println("Table: " + rs.getString("TABLE_NAME")
+ ", Schema: " + rs.getString("TABLE_SCHEM"));
}⚠️ 注意:null 或 "%" 作为 schemaPattern 会导致 INFORMATION_SCHEMA 表被包含;必须明确传入 "PUBLIC"。
方案 2:连接 URL 强制设置默认 schema(简洁有效)
在 JDBC URL 中添加 ;DEFAULT_SCHEMA=PUBLIC 参数,确保会话级默认 schema 为 PUBLIC,并避免隐式解析到 INFORMATION_SCHEMA:
# application.properties 示例 spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DEFAULT_SCHEMA=PUBLIC
此参数使 SELECT * FROM USERS 默认解析为 PUBLIC.USERS,同时元数据接口在无 schema 指定时也优先聚焦 PUBLIC(行为更接近 H2 1.x)。
方案 3:重命名业务表(一劳永逸,强烈建议)
遵循 SQL 最佳实践:避免使用任何系统表/关键字同名的业务标识符。H2 官方文档明确建议以 _ 结尾规避冲突(SQL 标准保证永不使用尾随下划线):
-- ✅ 推荐:业务表名加后缀,彻底隔离
CREATE TABLE users_ (
id BIGINT PRIMARY KEY,
username VARCHAR(50) NOT NULL
);
-- 查询时无需指定 schema
SELECT * FROM users_;? 小贴士:不仅 USERS,类似 SCHEMATA、TABLES、COLUMNS 等 INFORMATION_SCHEMA 中的表名均应避开。完整列表见 H2 System Tables 文档。
❌ 无效尝试说明
- jdbc:h2:...;SCHEMA=PUBLIC:该参数在 H2 中不存在,属误用(H2 不支持 SCHEMA 连接参数);
- REVOKE ... FROM pinf:仅控制 DML 权限,无法隐藏 INFORMATION_SCHEMA 在元数据中的可见性;
- SET SCHEMA PUBLIC:虽可切换当前会话 schema,但 getTables() 是 metadata 方法,不受会话 schema 影响。
总结:关键认知升级
| 维度 | H2 1.x 行为 | H2 2.0+ 行为 | 应对策略 |
|---|---|---|---|
| 默认 schema | PUBLIC | PUBLIC(不变) | 无需更改 |
| 元数据可见性 | 默认仅 PUBLIC | 默认所有 schema(含 INFORMATION_SCHEMA) | 显式传参 schemaPattern="PUBLIC" |
| 表名冲突风险 | 低(系统表较少暴露) | 高(getTables() 暴露全部) | 业务表名加 _ 后缀 |
| 连接参数支持 | 无 DEFAULT_SCHEMA | 支持 DEFAULT_SCHEMA=xxx | 优先配置该参数 |
升级 H2 2.0+ 后,请将“避免系统表同名”纳入建表规范,并在元数据操作中养成显式指定 schema 的习惯——这既是兼容性保障,更是生产环境健壮性的基石。

















