当内容超过 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 不同不能解决问题 当creatorapprover 在数据库中保存完全相同的值(指向相同的User)时,hibernate 能够获取approver 但不能获取creator——也就是说,当我检查生成的Document,它的DocumentRevision 没有creator,但它确实有一个approver -- 只有在调试和检查值时才这样 打开 SQL 日志记录会显示为损坏的记录执行 X 语句,为未损坏的记录执行 X+3 语句 - 额外的 SQL 语句与获取用户相关(它们根本不会在损坏的情况下发生)李>

调试失败

我尝试了以下方法,结果相同。

    增加source 列的大小 将@Column 注释替换为@Column(length = X) 重命名表 重命名列 升级 Spring/Hibernate 将uploader标记为FetchType.EAGERuploader 中删除optional = false

更新:我们注意到,这也可以通过删除从 DocumentDocumentRevision (Document.documentRevisions) 的多对一关系来解决。此外,删除从 DocumentRevisionDocument (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.idUUID / 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 不显示

web基础重难点

如何使用 ExecuteNonQuery 在 VARCHAR(MAX) 列中插入超过 8000 个字符?

varchar(n)