From e4a1482d6431a11057c990e49049bdc9381fa3a6 Mon Sep 17 00:00:00 2001 From: pwklppg <123456@.com> Date: Tue, 15 Sep 2026 14:20:09 +0800 Subject: [PATCH] fix: normalize skill proxy boolean column --- .../.superpowers/sdd/progress.md | 3 + .../sdd/reviews/runtime-startup-fix-review.md | 151 ++++++++++++++++++ .../sdd/runtime-startup-failure.txt | 14 ++ .../skill/domain/OccupationSkill.java | 4 - .../V37__skill_cloud_boolean_type.sql | 12 ++ .../skill/SkillCloudQueryServiceTest.java | 9 +- .../SkillCloudSeedDataIntegrationTest.java | 16 +- .../skill/SkillTaxonomyMigrationTest.java | 33 +++- .../skill/SkillTaxonomyRepositoryTest.java | 26 ++- 9 files changed, 253 insertions(+), 15 deletions(-) create mode 100644 changes/n03-skill-cloud/.superpowers/sdd/reviews/runtime-startup-fix-review.md create mode 100644 changes/n03-skill-cloud/.superpowers/sdd/runtime-startup-failure.txt create mode 100644 mydreamjob-server/src/main/resources/db/migration/V37__skill_cloud_boolean_type.sql diff --git a/changes/n03-skill-cloud/.superpowers/sdd/progress.md b/changes/n03-skill-cloud/.superpowers/sdd/progress.md index 0804835..266f2cd 100644 --- a/changes/n03-skill-cloud/.superpowers/sdd/progress.md +++ b/changes/n03-skill-cloud/.superpowers/sdd/progress.md @@ -27,3 +27,6 @@ - 2026-09-15T03:29:18Z W3 wave review FAIL: duplicate operationId getMatch and 401 response contract mismatch; full-suite external blocker remains open. - 2026-09-15T03:40:12Z W3 rereview FAIL: N-I1 existing cancel operationIds changed by global uniqueness check; restore legacy and scope check to new operations. - 2026-09-15T03:46:09Z W3 final review PASS for T8-T10; 73 targeted tests and OpenAPI/package checks pass. W3 code complete. Final closure blocked by unrelated full-suite failures: 2 failures + 9 errors. Waiver or out-of-scope repair authorization required. +- 2026-09-15T05:48:03Z Runtime startup bug: Hibernate schema validation failed on occupation_skills.importance_is_proxy (found BIT, expected TINYINT). Root cause: forced @JdbcTypeCode(SqlTypes.TINYINT) unlike existing Boolean mappings. Fix dispatched with mapping regression test. +- 2026-09-15T06:03:54Z Runtime bug fix attempt 2: first fix removed forced TINYINT but H2 still stores TINYINT and expects BOOLEAN; 34 tests failed. New hypothesis: add V37 to normalize column to BOOLEAN (MySQL alias TINYINT(1)) and remove test-only preferred type override. +- 2026-09-15T06:20:09Z Runtime startup fix PASS: V37 normalizes importance_is_proxy to cross-dialect BOOLEAN, entity uses default Boolean mapping; 74 targeted tests and package pass without extra JVM properties. diff --git a/changes/n03-skill-cloud/.superpowers/sdd/reviews/runtime-startup-fix-review.md b/changes/n03-skill-cloud/.superpowers/sdd/reviews/runtime-startup-fix-review.md new file mode 100644 index 0000000..46b17fa --- /dev/null +++ b/changes/n03-skill-cloud/.superpowers/sdd/reviews/runtime-startup-fix-review.md @@ -0,0 +1,151 @@ +# N03 运行时启动缺陷修复独立复审报告 + +## Verdict + +**PASS** + +运行时启动缺陷的本轮修复通过独立复审:实体不再强制 `TINYINT` 映射,V37 将 H2 侧列规范化为 `BOOLEAN`,MySQL 侧保持 `BOOLEAN/TINYINT(1)` 语义;标准 T2–T10 回归和跳过测试的打包均通过。完整 `mvn test` 仍有既有、非本次修复引入的失败,不能据此声称全量回归通过。 + +## 审查对象 + +- `mydreamjob-server/src/main/resources/db/migration/V37__skill_cloud_boolean_type.sql` +- `mydreamjob-server/src/main/java/com/mydreamjob/skill/domain/OccupationSkill.java` +- `SkillTaxonomyRepositoryTest.java` +- `SkillTaxonomyMigrationTest.java` +- `SkillCloudSeedDataIntegrationTest.java` +- `SkillCloudQueryServiceTest.java` +- 已有证据日志:`target/skill-cloud-v37-red.log`、`skill-cloud-v37-final-green.log`、`skill-cloud-v37-final-package.log` + +## 根因闭环 + +原始启动失败为: + +```text +Schema-validation: wrong column type encountered in column [importance_is_proxy] +found [bit (Types#BIT)], but expecting [tinyint (Types#TINYINT)] +``` + +闭环判断: + +1. `OccupationSkill.importanceIsProxy` 原先使用显式 `@JdbcTypeCode(SqlTypes.TINYINT)`,把 Hibernate 的 JDBC 期望固定为 `TINYINT`。 +2. 本轮已移除该显式注解及其 import;字段仍是 `Boolean`,列名、可空性和其他字段映射未改变。 +3. 新回归测试同时断言: + - Hibernate 属性返回类型为 `Boolean.class`; + - 字段不再存在显式 `JdbcTypeCode` 注解。 +4. V37 将 `occupation_skills.importance_is_proxy` 规范化为 `BOOLEAN NOT NULL DEFAULT FALSE`,并重建原 CHECK 约束。H2 `MODE=MySQL` 下迁移测试确认元数据为 `BOOLEAN / FALSE / NO`。 +5. MySQL 8.0.43 临时实例实际执行同一 DDL 后,物理元数据为 `tinyint / tinyint(1) / default 0 / NO`,读写结果为默认 `0`、`FALSE=0`、`TRUE=1`。 + +因此根因修复成立:实体不再强制 TINYINT;H2 使用真正的 BOOLEAN;MySQL 保留 BOOLEAN 别名对应的 TINYINT(1) 物理语义。 + +## V35/V36 与 Flyway 安全检查 + +独立比较结果: + +- `V35__skill_cloud_taxonomy.sql`:工作区与 `HEAD` 的 Git hash 均为 `db81f934...`,无差异。 +- `V36__skill_cloud_seed.sql`:工作区与 `HEAD` 的 Git hash 均为 `e4a03a40...`,无差异。 +- V35/V36 均未改写,已应用环境的 Flyway checksum 不受影响。 +- V37 为追加迁移,未修改历史迁移内容。 + +H2 `MODE=MySQL` 兼容性由实际迁移与测试上下文启动验证;MySQL 兼容性由 MySQL 8.0.43 实例实际执行 `DROP CONSTRAINT`、`MODIFY COLUMN BOOLEAN`、`ADD CONSTRAINT` 验证。MySQL 8.4 未在本机安装,未做 8.4 二进制验证;所用 DDL 语法属于 MySQL 8.0/8.4 的通用受支持语法。 + +## 布尔语义、默认值与 NULL 检查 + +V37 的顺序为: + +1. 删除旧的 `ck_occupation_skills_importance_proxy`; +2. 将列改为 `BOOLEAN NOT NULL DEFAULT FALSE`; +3. 重建 `CHECK (importance_is_proxy IN (FALSE, TRUE))`。 + +经验证: + +- 默认值:新行未显式赋值时为 `FALSE/0`。 +- 非空:迁移测试断言 `IS_NULLABLE=NO`,并将 NULL 更新验证为失败。 +- 布尔读写:`FALSE`、`TRUE` 可读写,查询结果与布尔比较均正确。 +- JDBC 断言:查询测试和种子数据测试已兼容 H2 返回 `Boolean` 以及旧驱动可能返回数值两种形式,不会因方言差异误判。 +- `preferred_boolean_jdbc_type=TINYINT`:源代码与配置中未发现该属性;标准测试命令不需要额外 `-D` 参数。 + +## 独立测试与打包结果 + +环境: + +```text +JAVA_HOME=D:\java\jdk +Maven=D:\maven\apache-maven-3.9.7\bin\mvn.cmd +``` + +### T2–T10 指定测试组 + +在 `mydreamjob-server` 下执行,未附加任何布尔类型属性: + +```text +mvn -Dtest=SkillCloudAuthorizationIntegrationTest,SkillCloudControllerTest,SkillCloudEndToEndIntegrationTest,SkillCloudQueryServiceTest,SkillCloudSeedDataIntegrationTest,SkillLearningPathServiceTest,SkillMatchServiceTest,SkillTaxonomyMigrationTest,SkillTaxonomyRepositoryTest test +``` + +结果: + +```text +Tests run: 74, Failures: 0, Errors: 0, Skipped: 0 +BUILD SUCCESS +Total time: 55.561 s +``` + +### 打包 + +```text +mvn -DskipTests package +BUILD SUCCESS +Total time: 2.439 s +``` + +### MySQL 8.0.43 冒烟 + +实际执行结果: + +```text +id importance_is_proxy +1 0 +2 0 +3 1 + +DATA_TYPE COLUMN_TYPE COLUMN_DEFAULT IS_NULLABLE +tinyint tinyint(1) 0 NO +``` + +说明:MySQL 将 `BOOLEAN` 规范化为 `tinyint(1)` 属正常行为;这与 V37 目标及 H2 的 `BOOLEAN` 元数据要求一致。 + +## 完整 `mvn test` 状态 + +独立执行完整命令: + +```text +mvn test +Tests run: 1396, Failures: 2, Errors: 9, Skipped: 0 +BUILD FAILURE +``` + +失败项仍为与本功能无关的既有问题: + +- `CareerProgressServiceTest.fullAggregation_returnsCorrectSummaries`:固定过期时间导致期望 1、实际 0。 +- `JobReviewDimensionControllerTest.listReviews_shouldReturnDimensionScores`:测试仍使用旧服务方法签名,返回 500。 +- `JobReviewDimensionServiceTest.listReviews_shouldReturnDimensionScoresWithoutN1`:测试仍 stub 旧 Repository 方法,结果为 null。 +- `LocalResumeStorageServiceTest`:Windows JUnit 临时目录清理的 8 个 `AccessDeniedException` 错误。 + +Skill Cloud 相关测试在完整套件中通过;但按退出条件,完整 `mvn test` 仍未全绿,不能声称全量回归通过。 + +## 范围与遮蔽检查 + +- 本次修复的生产代码变更仅涉及 `OccupationSkill` 和新增 V37;其余变更为相应专项测试的布尔兼容与回归断言。 +- 未修改 `SecurityConfig`、接口、DTO、业务查询逻辑或其他 schema 映射。 +- `SkillTaxonomyRepositoryTest` 原有的 schema filter 仍只限制 H2 测试环境的旧表验证范围,本轮没有扩大或借助该 filter 掩盖 V37 相关 schema 问题;V37 专项迁移测试直接检查 `information_schema` 元数据和约束。 +- 工作区中 `.trae/*` 等文件属于本任务之外的既有未提交内容,不构成本次运行时修复差异。 + +## 非阻断观察 + +1. `V37__skill_cloud_boolean_type.sql` 当前索引版本与工作区版本存在一个仅涉及列注释的文本差异;两者不影响 DDL 语义,但提交前应统一内容,避免最终提交与独立测试的具体文本不一致。 +2. V37 文件工作区版本末尾缺少换行;非功能问题。 +3. MySQL 仅完成 8.0.43 二进制冒烟,未在实际 8.4 二进制上复跑;判断为语法兼容,但如需严格的 8.4 认证应补充运行环境验证。 + +## 剩余阻断 + +- 本次运行时启动修复:无阻断。 +- 项目最终关闭:完整 `mvn test` 仍有 2 failures + 9 errors;需要修复这些既有测试,或由项目按流程正式记录豁免后,才能推进全量回归结论和最终关闭。 diff --git a/changes/n03-skill-cloud/.superpowers/sdd/runtime-startup-failure.txt b/changes/n03-skill-cloud/.superpowers/sdd/runtime-startup-failure.txt new file mode 100644 index 0000000..321ef80 --- /dev/null +++ b/changes/n03-skill-cloud/.superpowers/sdd/runtime-startup-failure.txt @@ -0,0 +1,14 @@ +Runtime failure evidence +======================= +Task: manual application startup from IntelliJ +Symptom: Hibernate SessionFactory build failed +Column: occupation_skills.importance_is_proxy +Found: bit (Types#BIT) +Expected: tinyint (Types#TINYINT) +Root cause: OccupationSkill.importanceIsProxy is explicitly forced to SqlTypes.TINYINT, unlike existing Boolean mappings. + +Attempt 2 evidence +================== +Removing @JdbcTypeCode(TINYINT) alone fixed the MySQL expectation but broke H2 schema validation: +found tinyint, expected boolean. +Conclusion: the column must be normalized to a cross-dialect BOOLEAN/BIT form, without modifying already-applied V35. diff --git a/mydreamjob-server/src/main/java/com/mydreamjob/skill/domain/OccupationSkill.java b/mydreamjob-server/src/main/java/com/mydreamjob/skill/domain/OccupationSkill.java index 9b77bba..38046c6 100644 --- a/mydreamjob-server/src/main/java/com/mydreamjob/skill/domain/OccupationSkill.java +++ b/mydreamjob-server/src/main/java/com/mydreamjob/skill/domain/OccupationSkill.java @@ -11,8 +11,6 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import org.hibernate.annotations.JdbcTypeCode; -import org.hibernate.type.SqlTypes; import java.math.BigDecimal; import java.time.LocalDateTime; @@ -47,8 +45,6 @@ public class OccupationSkill { @Column(name = "importance_element_id", length = 30) private String importanceElementId; - - @JdbcTypeCode(SqlTypes.TINYINT) @Column(name = "importance_is_proxy", nullable = false) private Boolean importanceIsProxy; diff --git a/mydreamjob-server/src/main/resources/db/migration/V37__skill_cloud_boolean_type.sql b/mydreamjob-server/src/main/resources/db/migration/V37__skill_cloud_boolean_type.sql new file mode 100644 index 0000000..6fdc95d --- /dev/null +++ b/mydreamjob-server/src/main/resources/db/migration/V37__skill_cloud_boolean_type.sql @@ -0,0 +1,12 @@ +-- Flyway V37: 将技能代理标记规范化为跨方言 BOOLEAN。 +-- 不改写 V35/V36,确保已应用迁移的 Flyway checksum 保持有效。 + +ALTER TABLE occupation_skills + DROP CONSTRAINT ck_occupation_skills_importance_proxy; + +ALTER TABLE occupation_skills + MODIFY COLUMN importance_is_proxy BOOLEAN NOT NULL DEFAULT FALSE; + +ALTER TABLE occupation_skills + ADD CONSTRAINT ck_occupation_skills_importance_proxy + CHECK (importance_is_proxy IN (FALSE, TRUE)); \ No newline at end of file diff --git a/mydreamjob-server/src/test/java/com/mydreamjob/skill/SkillCloudQueryServiceTest.java b/mydreamjob-server/src/test/java/com/mydreamjob/skill/SkillCloudQueryServiceTest.java index daa3253..c207304 100644 --- a/mydreamjob-server/src/test/java/com/mydreamjob/skill/SkillCloudQueryServiceTest.java +++ b/mydreamjob-server/src/test/java/com/mydreamjob/skill/SkillCloudQueryServiceTest.java @@ -221,7 +221,7 @@ class SkillCloudQueryServiceTest { assertThat(react.heat()).isEqualByComparingTo((BigDecimal) row.get("importance")); assertThat(react.heatSourceCode()).isEqualTo(row.get("importance_source_code")); assertThat(react.heatElementId()).isEqualTo(row.get("importance_element_id")); - assertThat(react.heatProxy()).isEqualTo(((Number) row.get("importance_is_proxy")).intValue() == 1); + assertThat(react.heatProxy()).isEqualTo(toBoolean(row.get("importance_is_proxy"))); SkillCloudResponse languages = service.query(1L, 1L, 101L); SkillCloudItem javascript = languages.items().stream() @@ -256,6 +256,13 @@ class SkillCloudQueryServiceTest { assertBadRequest(() -> service.query(1L, 1L, 9999L), "category not found"); } + private boolean toBoolean(Object value) { + if (value instanceof Boolean booleanValue) { + return booleanValue; + } + return ((Number) value).intValue() != 0; + } + private void assertBadRequest(ThrowingCallable action, String detailFragment) { BusinessException exception = catchThrowableOfType(action, BusinessException.class); assertThat(exception).isNotNull(); diff --git a/mydreamjob-server/src/test/java/com/mydreamjob/skill/SkillCloudSeedDataIntegrationTest.java b/mydreamjob-server/src/test/java/com/mydreamjob/skill/SkillCloudSeedDataIntegrationTest.java index 59aaab9..4df5e59 100644 --- a/mydreamjob-server/src/test/java/com/mydreamjob/skill/SkillCloudSeedDataIntegrationTest.java +++ b/mydreamjob-server/src/test/java/com/mydreamjob/skill/SkillCloudSeedDataIntegrationTest.java @@ -202,7 +202,7 @@ class SkillCloudSeedDataIntegrationTest { "OR importance_element_id IS NULL OR TRIM(importance_element_id) = '')")); assertEquals(0L, count( "SELECT COUNT(*) FROM occupation_skills " + - "WHERE importance_is_proxy IS NULL OR importance_is_proxy NOT IN (0, 1)")); + "WHERE importance_is_proxy IS NULL OR importance_is_proxy NOT IN (FALSE, TRUE)")); assertEquals(0L, count( "SELECT COUNT(*) FROM occupation_skills os " + "JOIN skill_source_versions sv ON sv.id = os.source_version_id " + @@ -226,7 +226,7 @@ class SkillCloudSeedDataIntegrationTest { String sourceCode = (String) row.get("importance_source_code"); String elementId = (String) row.get("importance_element_id"); BigDecimal importance = (BigDecimal) row.get("importance"); - int proxy = ((Number) row.get("importance_is_proxy")).intValue(); + int proxy = toInteger(row.get("importance_is_proxy")); assertTrue(proxy == 0 || proxy == 1, "Invalid importance_is_proxy: relationId=" + relationId @@ -581,7 +581,13 @@ class SkillCloudSeedDataIntegrationTest { } private Integer toInteger(Object value) { - return value == null ? null : ((Number) value).intValue(); + if (value == null) { + return null; + } + if (value instanceof Boolean booleanValue) { + return booleanValue ? 1 : 0; + } + return ((Number) value).intValue(); } private void assertSameMappingKeys( @@ -979,7 +985,7 @@ class SkillCloudSeedDataIntegrationTest { assertEquals(new BigDecimal(expectedValue), ((BigDecimal) row.get("importance")).setScale(2)); assertEquals(sourceCode, row.get("importance_source_code")); assertEquals(elementId, row.get("importance_element_id")); - assertEquals(1, ((Number) row.get("importance_is_proxy")).intValue()); + assertEquals(1, toInteger(row.get("importance_is_proxy"))); } private void assertNullImportance(String occupationCode, String skillCode) { @@ -994,7 +1000,7 @@ class SkillCloudSeedDataIntegrationTest { assertNull(row.get("importance")); assertNull(row.get("importance_source_code")); assertNull(row.get("importance_element_id")); - assertEquals(0, ((Number) row.get("importance_is_proxy")).intValue()); + assertEquals(0, toInteger(row.get("importance_is_proxy"))); } private boolean hasSkillInIndustry(String industryCode, String skillCode) { diff --git a/mydreamjob-server/src/test/java/com/mydreamjob/skill/SkillTaxonomyMigrationTest.java b/mydreamjob-server/src/test/java/com/mydreamjob/skill/SkillTaxonomyMigrationTest.java index 2bb97e1..70b4242 100644 --- a/mydreamjob-server/src/test/java/com/mydreamjob/skill/SkillTaxonomyMigrationTest.java +++ b/mydreamjob-server/src/test/java/com/mydreamjob/skill/SkillTaxonomyMigrationTest.java @@ -20,6 +20,7 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.TreeMap; @@ -95,6 +96,13 @@ class SkillTaxonomyMigrationTest { assertIndex("occupation_skills", "occupation_id", "category_id"); assertIndex("occupation_skills", "occupation_id", "importance"); + assertBooleanColumnMetadata("occupation_skills", "importance_is_proxy"); + assertEquals(1L, jdbcTemplate.queryForObject( + "SELECT COUNT(*) FROM information_schema.table_constraints " + + "WHERE UPPER(table_name) = 'OCCUPATION_SKILLS' " + + "AND UPPER(constraint_name) = 'CK_OCCUPATION_SKILLS_IMPORTANCE_PROXY' " + + "AND UPPER(constraint_type) = 'CHECK'", + Long.class)); } @Test @@ -148,14 +156,17 @@ class SkillTaxonomyMigrationTest { assertNull(joined.get("importance")); assertNull(joined.get("importance_source_code")); assertNull(joined.get("importance_element_id")); - assertEquals(0, ((Number) joined.get("importance_is_proxy")).intValue()); + assertEquals(Boolean.FALSE, joined.get("importance_is_proxy")); + assertEquals(1L, jdbcTemplate.queryForObject( + "SELECT COUNT(*) FROM occupation_skills WHERE id = ? AND importance_is_proxy = FALSE", + Long.class, occupationSkillId)); assertConstraintFailure("UPDATE occupation_skills SET importance = ? WHERE id = ?", new BigDecimal("4.25"), occupationSkillId); jdbcTemplate.update("UPDATE occupation_skills SET importance = ?, " + "importance_source_code = ?, importance_element_id = ?, importance_is_proxy = ? " + "WHERE id = ?", - new BigDecimal("4.25"), "15-1252.00", "4.A.3.b.1", 1, occupationSkillId); + new BigDecimal("4.25"), "15-1252.00", "4.A.3.b.1", Boolean.TRUE, occupationSkillId); Map weighted = jdbcTemplate.queryForMap( "SELECT importance, importance_source_code, importance_element_id, importance_is_proxy " + "FROM occupation_skills WHERE id = ?", @@ -163,7 +174,10 @@ class SkillTaxonomyMigrationTest { assertEquals(new BigDecimal("4.25"), weighted.get("importance")); assertEquals("15-1252.00", weighted.get("importance_source_code")); assertEquals("4.A.3.b.1", weighted.get("importance_element_id")); - assertEquals(1, ((Number) weighted.get("importance_is_proxy")).intValue()); + assertEquals(Boolean.TRUE, weighted.get("importance_is_proxy")); + assertEquals(1L, jdbcTemplate.queryForObject( + "SELECT COUNT(*) FROM occupation_skills WHERE id = ? AND importance_is_proxy = TRUE", + Long.class, occupationSkillId)); assertConstraintFailure( "INSERT INTO skill_source_versions " + "(source_name, source_version, license, attribution_url) VALUES (?, ?, ?, ?)", @@ -202,7 +216,8 @@ class SkillTaxonomyMigrationTest { assertConstraintFailure( "UPDATE occupation_skills SET importance_is_proxy = ? WHERE id = ?", - 2, occupationSkillId); assertTrue(jdbcTemplate.queryForObject( + (Object) null, occupationSkillId); + assertTrue(jdbcTemplate.queryForObject( "SELECT COUNT(*) FROM skill_aliases WHERE skill_id = ? AND normalized_alias = ?", Integer.class, skillId, "java se") == 1); } @@ -234,6 +249,16 @@ class SkillTaxonomyMigrationTest { } } + private void assertBooleanColumnMetadata(String table, String column) { + Map metadata = jdbcTemplate.queryForMap( + "SELECT data_type, column_default, is_nullable FROM information_schema.columns " + + "WHERE UPPER(table_name) = ? AND UPPER(column_name) = ?", + table.toUpperCase(Locale.ROOT), column.toUpperCase(Locale.ROOT)); + assertEquals("BOOLEAN", String.valueOf(metadata.get("data_type")).toUpperCase(Locale.ROOT)); + assertEquals("FALSE", String.valueOf(metadata.get("column_default")).toUpperCase(Locale.ROOT)); + assertEquals("NO", String.valueOf(metadata.get("is_nullable")).toUpperCase(Locale.ROOT)); + } + private void assertTableAndColumns(String table, String... columns) throws SQLException { try (Connection connection = dataSource.getConnection()) { DatabaseMetaData metadata = connection.getMetaData(); diff --git a/mydreamjob-server/src/test/java/com/mydreamjob/skill/SkillTaxonomyRepositoryTest.java b/mydreamjob-server/src/test/java/com/mydreamjob/skill/SkillTaxonomyRepositoryTest.java index 322abba..40f4103 100644 --- a/mydreamjob-server/src/test/java/com/mydreamjob/skill/SkillTaxonomyRepositoryTest.java +++ b/mydreamjob-server/src/test/java/com/mydreamjob/skill/SkillTaxonomyRepositoryTest.java @@ -1,5 +1,6 @@ package com.mydreamjob.skill; +import com.mydreamjob.skill.domain.OccupationSkill; import com.mydreamjob.skill.domain.Skill; import com.mydreamjob.skill.domain.SkillAlias; import com.mydreamjob.skill.dto.SkillCloudSkillProjection; @@ -10,6 +11,7 @@ import com.mydreamjob.skill.repository.SkillIndustryRepository; import com.mydreamjob.skill.repository.SkillOccupationRepository; import com.mydreamjob.skill.repository.SkillRepository; import com.mydreamjob.skill.repository.SkillSourceVersionRepository; +import jakarta.persistence.EntityManagerFactory; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -19,8 +21,11 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.TestPropertySource; import org.springframework.transaction.annotation.Transactional; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.boot.model.relational.Namespace; import org.hibernate.boot.model.relational.Sequence; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.persister.entity.EntityPersister; import org.hibernate.mapping.Table; import org.hibernate.tool.schema.spi.SchemaFilter; import org.hibernate.tool.schema.spi.SchemaFilterProvider; @@ -66,6 +71,9 @@ class SkillTaxonomyRepositoryTest { @Autowired private JdbcTemplate jdbcTemplate; + @Autowired + private EntityManagerFactory entityManagerFactory; + @MockBean private RedisTemplate redisTemplate; @@ -131,7 +139,7 @@ class SkillTaxonomyRepositoryTest { ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) """, 1L, 1L, 201L, new BigDecimal("4.61"), - "15-1252.00", "4.A.3.b.1", 1, "ESSENTIAL", 2L); + "15-1252.00", "4.A.3.b.1", Boolean.TRUE, "ESSENTIAL", 2L); List occupationOneSkills = occupationSkillRepository.findSkillProjectionsByOccupationId(1L); @@ -211,6 +219,22 @@ class SkillTaxonomyRepositoryTest { .containsExactly(3L, 4L); } + @Test + void importanceIsProxyUsesDefaultBooleanMapping() throws NoSuchFieldException { + SessionFactoryImplementor sessionFactory = entityManagerFactory.unwrap(SessionFactoryImplementor.class); + EntityPersister persister = sessionFactory.getMappingMetamodel().getEntityDescriptor(OccupationSkill.class); + + org.hibernate.type.Type propertyType = persister.getPropertyType("importanceIsProxy"); + assertThat(propertyType.getReturnedClass()).isEqualTo(Boolean.class); + + JdbcTypeCode explicitJdbcType = OccupationSkill.class + .getDeclaredField("importanceIsProxy") + .getAnnotation(JdbcTypeCode.class); + assertThat(explicitJdbcType) + .as("importanceIsProxy must use the default Boolean mapping without a forced JDBC type") + .isNull(); + } + /** * The legacy H2 schema has TINYINT/Boolean mismatches outside the V35 taxonomy. * Scope validation to the seven T4 tables without changing historical migrations or entities.