当内容超过 141 个字符时,VARCHAR 列安静地中断 Hibernate
Posted
技术标签:
【中文标题】当内容超过 141 个字符时,VARCHAR 列安静地中断 Hibernate【英文标题】:VARCHAR column breaks Hibernate quietly when content is more than 141 characters 【发布时间】:2020-01-08 23:04:42 【问题描述】:我们在使用 Spring Boot (v2.1.5) 和 Hibernate (5.3.10) 时遇到了一个非常奇怪的问题。我们有一个表DocumentRevision
,它有外键,例如Document -> DocumentRevision -> User
。当我们获取Document
时,DocumentRevision
是 eager-fetched,User
是lazy-fetched。这在大多数情况下都能正常工作,但在非常特定的情况下以非常特定的方式失败。
DocumentRevision
实际上有两个指向User
的外键:uploader (NOT NULL)
和approver (NULL)
。
DocumentRevision
中的一列是source VARCHAR(255) NULL
。
当DocumentRevision.source
包含142 个或更多字符的长度时,Hibernate 不会获取uploader
,并且会产生NullPointerException
。对于同一条记录,当source
列的长度为null
或0-141 个字符时,uploader
被正确提取。这是 100% 可重现的,142 个字符是断点。请注意,这些是 ASCII 字符,没什么特别的(排序规则是 utf8mb4_unicode_ci
)。
VARCHAR
字段没有效果 - 一切正常(实际上只有 source
列会导致此问题,并且仅当它包含 142 个或更多字符时)
我可以通过在 source
列中插入 142 个或更多字符来打破未损坏的记录
将 approverId
设置为 null
记录已损坏的记录可解决此问题
将其设置为有效的用户 ID,但与 uploaderId
不同不能解决问题
当creator
和approver
在数据库中保存完全相同的值(指向相同的User
)时,hibernate 能够获取approver
但不能获取creator
——也就是说,当我检查生成的Document
,它的DocumentRevision
没有creator
,但它确实有一个approver
-- 只有在调试和检查值时才这样
打开 SQL 日志记录会显示为损坏的记录执行 X 语句,为未损坏的记录执行 X+3 语句 - 额外的 SQL 语句与获取用户相关(它们根本不会在损坏的情况下发生)李>
调试失败
我尝试了以下方法,结果相同。
-
增加
source
列的大小
将@Column
注释替换为@Column(length = X)
重命名表
重命名列
升级 Spring/Hibernate
将uploader
标记为FetchType.EAGER
从uploader
中删除optional = false
更新:我们注意到,这也可以通过删除从 Document
到 DocumentRevision
(Document.documentRevisions
) 的多对一关系来解决。此外,删除从 DocumentRevision
到 Document
(DocumentRevision.document
) 的一对一关系可以解决此问题。我目前的理论是某种循环引用——但同样,日志中没有任何来自 Hibernate 的内容。
这里有一些sn-ps:
表格:文档
CREATE TABLE `Document` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`createdDateTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`deletedDateTime` timestamp NULL DEFAULT NULL,
`lastModifiedDateTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`version` bigint(20) NOT NULL,
`sectionId` int(11) NOT NULL,
`currentRevisionId` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `FK_Document_sectionId_Section_id` (`sectionId`),
KEY `FK_Document_currentRevisionId_DocumentRevision_id` (`currentRevisionId`),
CONSTRAINT `FK_Document_currentRevisionId_DocumentRevision_id` FOREIGN KEY (`currentRevisionId`) REFERENCES `DocumentRevision` (`id`),
CONSTRAINT `FK_Document_sectionId_Section_id` FOREIGN KEY (`sectionId`) REFERENCES `Section` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=34074 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
表格:DocumentRevision
CREATE TABLE `DocumentRevision` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`createdDateTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`deletedDateTime` timestamp NULL DEFAULT NULL,
`lastModifiedDateTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`version` bigint(20) NOT NULL,
`contentLength` bigint(20) DEFAULT NULL,
`uploaderId` binary(16) NOT NULL,
`approverId` binary(16) DEFAULT NULL,
`fileId` int(11) NOT NULL,
`parsed` tinyint(1) NOT NULL DEFAULT '0',
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`source` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`sourcePublishDateTime` timestamp NULL DEFAULT NULL,
`description` varchar(1000) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`coordinateSystem` varchar(10) COLLATE utf8mb4_unicode_ci NOT NULL,
`mgrs` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`latitude` varchar(10) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`longitude` varchar(10) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`location` point DEFAULT NULL,
`sourceUrl` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`marking` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL,
`approvedDateTime` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `FK_DocumentRevision_fileId_Document_id` (`fileId`),
KEY `FK_DocumentRevision_uploaderId_User_id` (`uploaderId`),
KEY `FK_DocumentRevision_approverId_User_id` (`approverId`),
CONSTRAINT `FK_DocumentRevision_approverId_User_id` FOREIGN KEY (`approverId`) REFERENCES `User` (`id`),
CONSTRAINT `FK_DocumentRevision_fileId_Document_id` FOREIGN KEY (`fileId`) REFERENCES `Document` (`id`),
CONSTRAINT `FK_DocumentRevision_uploaderId_User_id` FOREIGN KEY (`uploaderId`) REFERENCES `User` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=34080 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
表:用户
CREATE TABLE `User` (
`id` binary(16) NOT NULL,
`createdDateTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`deletedDateTime` timestamp NULL DEFAULT NULL,
`lastModifiedDateTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`version` bigint(20) NOT NULL,
`active` tinyint(1) NOT NULL,
`firstName` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`lastName` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`username` varchar(60) COLLATE utf8mb4_unicode_ci NOT NULL,
`primaryEmailAddress` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`hidden` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `IDX_Person_username` (`username`),
KEY `IDX_Person_primaryEmailAddress` (`primaryEmailAddress`),
KEY `IDX_Person_lastName` (`lastName`),
KEY `IDX_Person_firstName` (`firstName`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
映射:文档
@Entity
@Table(name = "Document")
@Data
@EqualsAndHashCode(callSuper = true)
public class Document extends BaseEntity<Integer>
@JsonIgnore
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "sectionId")
private Section section;
@ManyToOne(cascade = CascadeType.REMOVE)
@JoinColumn(name = "currentRevisionId")
private DocumentRevision currentRevision;
@OneToMany(mappedBy = "document", cascade = CascadeType.REMOVE)
private List<DocumentRevision> documentRevisions = new ArrayList<>();
映射:DocumentRevision
@Entity
@Table(name = "DocumentRevision")
@Data
@EqualsAndHashCode(callSuper = true)
public class DocumentRevision extends BaseEntity<Integer>
@JsonIgnore
@ManyToOne
@JoinColumn(name = "documentId", nullable = false)
private Document document;
@ManyToOne(optional = false, fetch = FetchType.LAZY)
@JoinColumn(name = "uploaderId")
private User uploader;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "approverId")
private User approver;
@Column
private String source;
// other fields removed for brevity
映射:用户
@EqualsAndHashCode
@Data
@Entity
@Table(name = "User")
public class User extends BaseEntity<UUID>
@Column(length = 60, nullable = false)
private String username;
@Column(length = 25, nullable = false)
private String firstName;
@Column(length = 25, nullable = false)
private String lastName;
@Column(length = 255)
private String primaryEmailAddress;
@Column(nullable = false)
private boolean active;
@Column(nullable = false)
private boolean hidden;
我意识到这是很多信息,但这个让我很难过!
【问题讨论】:
我很好奇您为什么使用二进制作为用户 ID 和 BTW 的 SQL 类型,该 ID 在您的 Java 类中映射到哪里?User.id
是 UUID
/ GUID,因此它以二进制形式存储。 id
字段在超类中定义(已更新以显示)。
我认为这需要通过 Hibernate 进行调试,直到它获取急切的关系。您是否偶然有一个最小的复制器项目?
是的,我们为此花了几天时间,但不得不继续前进。将其中一个关系更改为 FetchType.LAZY
是解决方法。
记录了一个带有 Hibernate ORM 的错误:hibernate.atlassian.net/browse/HHH-14481
【参考方案1】:
我们不久前又遇到了这个问题,然后就在上周。我做了一些更麻烦的事情,并且能够通过将 mysql 连接器依赖项从 6.0.6
升级到 8.0.22
来修复它。
【讨论】:
以上是关于当内容超过 141 个字符时,VARCHAR 列安静地中断 Hibernate的主要内容,如果未能解决你的问题,请参考以下文章
Mysql一个表中 最多能存多少文字? 一列中varchar最大值允许多少?
实体框架oracle:将超过2000个字符插入VARCHAR2(4000 CHAR)失败
当数据大小增加到超过 5100 个字符时,Android WebView 不显示