当内容超过 141 个字符时,VARCHAR 列会安静地中断 Hibernate

Jos*_* M. 6 java mysql database orm hibernate

我们在使用 Spring Boot (v2.1.5) 和 Hibernate (5.3.10) 时遇到了一个非常奇怪的问题。我们有一个DocumentRevision带有外键的表,例如Document -> DocumentRevision -> User. 当我们 fetch 时DocumentDocumentRevision是eager-fetched 和Userlazy-fetched。这在大多数情况下都能正常工作,但在非常具体的情况下以非常具体的方式失败。

DocumentRevision实际上有两个外键Useruploader (NOT NULL)approver (NULL)

中的列之一DocumentRevisionsource VARCHAR(255) NULL

DocumentRevision.source包含142 个或更多字符的长度时uploaderHibernate 不获取,并且NullPointerException结果。对于同一条记录,当该source列的null长度为0-141 个字符时,uploader正确提取。这是 100% 可重现的,142 个字符是突破点。请注意,这些是 ASCII 字符,没什么特别的(排序规则是utf8mb4_unicode_ci)。

  • 完全填充VARCHAR未损坏记录中的所有其他字段没有任何影响 - 一切正常(实际上只是source导致此问题的列,并且仅当它包含 142 个或更多字符时)
  • 我可以通过在source列中插入 142 个或更多字符来打破未损坏的记录
  • 设置approverIdnull了一个破纪录的修复问题
  • 将其设置为一个有效的用户ID,但不同的人比uploaderId 解决问题
  • 当两个creatorapprover数据库(指向同持有完全相同的值User),Hibernate是能够获取approver,但不creator-那就是,当我检查生成的Document,它DocumentRevision没有creator,但它确实有一个approver-这仅在调试和检查值时为真
  • 打开 SQL 日志显示为损坏的记录执行 X 条语句,为未损坏的记录执行 X+3 条语句——额外的 SQL 语句与获取用户有关(它们根本不会发生在损坏的情况下)

调试失败

我已经尝试了以下相同的结果。

  1. 增加source列的大小
  2. @Column注释替换为@Column(length = X)
  3. 重命名表
  4. 重命名列
  5. 升级 Spring/Hibernate
  6. 标记uploaderFetchType.EAGER
  7. 除去optional = falseuploader

更新:我们注意到,这也可以通过从Documentto DocumentRevision( Document.documentRevisions) 中删除多对一关系来解决。此外,从DocumentRevisionto Document( DocumentRevision.document) 中删除一对一关系可以解决此问题。我目前的理论是某种循环引用——但同样,日志中没有任何来自 Hibernate 的内容。

以下是一些片段:

表:文件

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;
Run Code Online (Sandbox Code Playgroud)

表:文档修订

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;
Run Code Online (Sandbox Code Playgroud)

表:用户

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;
Run Code Online (Sandbox Code Playgroud)

映射:文档

@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<>();
}
Run Code Online (Sandbox Code Playgroud)

映射:文档修订

@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
}
Run Code Online (Sandbox Code Playgroud)

映射:用户

@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;
}
Run Code Online (Sandbox Code Playgroud)

我意识到这是很多信息,但这让我难住了!

Jos*_* M. 0

不久前我们再次遇到了这个问题,然后就在上周。我做了一些更乱的事情,并能够通过将 MySQL Connector 依赖项从 升级6.0.6到 来修复它8.0.22