Spring Boot 3.1.4+Hibernate6.2.9升级后JSON存储字符集截断错误
近期将应用升级至Spring Boot 3.1.4、Java 21及Hibernate 6.2.9版本后,向MySQL数据库存储JSON数据时遇到以下错误:
WARN 1 --- [erContainer#0-2] o.h.engine.jdbc.spi.SqlExceptionHelper
: SQL Error: 3144, SQLState: 22001
Data truncation: Cannot create a JSON value from a string with CHARACTER SET 'binary'.
相关代码
@Service public class JpaDataWritingServiceImpl implements DataWritingService { @Autowired TestDataRepository testDataRepository; @Autowired TestRepository testRepository; @Override public void writetestData(Long testId, String type, byte[] data, testStatus testStatus, boolean needsBackgroundtest) { Optional<Test> maybeTest = testRepository.findById(testId); if (maybeTest.isPresent()) { Test test = maybeTest.get(); String metadata = generateMetadataForTestData(data); TestData testData = new TestData(test, type, data, metadata); LOG.debug("Saving TestData======> data={}, metadata={}", dataString, metadata); // 保存前验证数据 testDataRepository.save(new TestData(test, type, data, metadata)); // 此处报错 } else { LOG.warn("Didn't find testData with id {}", testId); } } private String generateMetadataForTestData(byte[] data) { String byteToStringData = new String(data, StandardCharsets.UTF_8); JSONObject byteToString = new JSONObject(byteToStringData); JSONObject stringToJson = new JSONObject(); if (byteToString.has("platform")) { stringToJson.put("content_id", byteToString.getString("id")); try { Date date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(byteToString.getString("timestamp")); long epochTime = date.getTime() / 1000; stringToJson.put("timestamp", epochTime); } catch (ParseException e) { LOG.error("DateTime parse exception: {}", e.getMessage()); } } return stringToJson.length() == 0 ? null : stringToJson.toString(); } }
错误堆栈跟踪
Caused by: org.springframework.dao.DataIntegrityViolationException: could not execute statement [Data truncation: Cannot create a JSON value from a string with CHARACTER SET 'binary'.]
[insert into test_data (test_id, data, metadata, received_at, type) values (?,?,?,?,?)]
实体定义
@Entity @Table(name="test_data") public class TestData { public TestData(){} public TestData(Test test, String type, byte[] data, String metadata) { this.test = test; this.type = type; this.data = data; this.metadata = metadata; this.receivedAt = Instant.now(); } @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; @ManyToOne private Test test; private String type; @NotNull @Lob private byte[] data; @JsonIgnore @OneToOne(cascade = CascadeType.ALL, mappedBy = "testData") private TestDataTestStatus testStatus; @Column(name = "received_at") private Instant receivedAt; @Lob @Column(columnDefinition = "json") private String metadata; // 其他字段及getter/setter... }
数据库配置
CREATE TABLE `test_data` ( `id` bigint NOT NULL AUTO_INCREMENT, `test_id` bigint NOT NULL, `data` mediumblob NOT NULL, `received_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, `metadata` json DEFAULT NULL, PRIMARY KEY (`id`), KEY `fk_test_data_test` (`test_id`), CONSTRAINT `fk_test_data_test` FOREIGN KEY (`test_id`) REFERENCES `test` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT ) ENGINE=InnoDB AUTO_INCREMENT=175613200 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_ci
观察结果
- 升级前代码运行正常
data字段是序列化后的JSON字节数组,我将其转为字符串后存入metadata列- 应用日志显示
data和metadata都是合法的JSON字符串:
Saving testData======> data={"id":"113662220040053124", ...}, metadata={"content_id":"113662220040053124","timestamp":1734347839}
问题
- 为何现在存储JSON
metadata或byte[] data时会出现CHARACTER SET 'binary'错误? - Hibernate 6.2.x或Spring Boot 3.1.x在处理JSON或byte[]字段时有新要求吗?
1. 错误原因
问题出在metadata字段的映射逻辑:你给该字段加了@Lob注解,在Hibernate 6.2.x中,@Lob标记的字符串会被默认当作二进制大对象(BLOB)处理,JDBC驱动会以binary字符集将字符串发送给MySQL。但MySQL的JSON列要求传入的字符串必须是UTF-8字符集,无法识别binary类型的输入,因此触发了数据截断错误。
升级前的Hibernate版本对@Lob+columnDefinition="json"的处理逻辑不同,可能会忽略@Lob的优先级,直接按JSON类型处理字符串;但Hibernate 6.x调整了类型映射的优先级,@Lob的权重更高,导致字段被当作BLOB处理,最终引发字符集不匹配问题。
2. Hibernate 6.2.x/Spring Boot 3.1.x的新要求
Hibernate 6.x对类型映射做了更严格的规范:
@Lob注解会明确将字段映射为BLOB/CLOB类型,不再与自定义columnDefinition的类型自动兼容- 对于JSON类型字段,Hibernate 6.x推荐使用专门的JSON映射方式,而非
@Lob+columnDefinition的组合
解决方法
直接移除metadata字段上的@Lob注解即可:
@Column(columnDefinition = "json") private String metadata;
如果需要兼容更复杂的JSON场景,也可以使用Hibernate的@JdbcTypeCode注解明确指定JSON类型:
@Column(columnDefinition = "json") @JdbcTypeCode(SqlTypes.JSON) private String metadata;
另外说明:byte[] data字段的@Lob注解是合理的(对应数据库的mediumblob),该字段与本次错误无关,错误仅来自metadata的类型映射问题。
内容的提问来源于stack exchange,提问作者Rohan Razdan

