【问题标题】:JPA: How should I annotate this Map collections?JPA:我应该如何注释这个地图集合?
【发布时间】:2026-01-03 16:35:01
【问题描述】:

我将通过一个描述性的例子来解释我自己。假设我们正在使用 JPA 编写一个非常简单的云存储,所以我们有两个主要类,UserFile,编码如下:

class User {
       List<File> ownFiles;
       Map<File, Integer> sharedFiles; 
}

class File {
       User author;
       Map<User, Integer> sharedUsers;
}

我应该如何注释这些字段?我试过了,但我总是得到NotSerializableException“data too long for column”

  • 顺便说一下,File只是一个例子,我的真实班级很小,只是由一些字符串组成。所以我很确定 NotSerializableException 是因为 Map 注释而被抛出的。

  • 顺便说一下,这里有地图值的含义,

    • User的情况下,文件的整数值代表用户对该文件的权限级别
    • File 的情况下,密钥是文件已共享给的那些用户,值是该用户对File 的权限级别

P.S:我正在使用 Eclipselink

【问题讨论】:

  • NotSerializableException 是因为如果你没有注释它,它会默认为一个基本的映射并尝试序列化对象。仅仅向我们展示集合并不能帮助我们告诉您如何映射它 - 您需要告诉我们您想要用它做什么,整数值来自哪里以及应该存储等等。从任何显示的 JPA 教程开始多对多映射。
  • 可能引擎希望以“残酷”、非关系的方式(如 BLOB/CLOB 等)存储此字段,因此请尝试序列化。 JPA 不能以对象关系的方式存储地图。建议重新设计,JPA 中的项目必须保持规则并不总是与经典 OOP 一致,即 Map 不好。
  • 建议文件使用不同的类名

标签: java hibernate jpa jakarta-ee eclipselink


【解决方案1】:

如评论中所述,Map 无法由 JPA 1.0 以这种方式处理(因此尝试进行序列化)

JPA 2.0 增加注解,繁杂的小技巧,细腻的属性(个人观点) https://wiki.eclipse.org/EclipseLink/Examples/JPA/2.0/ElementCollections 我并不奇怪生成的数据库可以相似

在我看来,我可以通过 JPA 1 方式更好地控制数据库(对我来说更干净)。让我展示我的建议。

我对你在 1.x 哲学中的逻辑结构的理解是:

class User {
     // ... fields waht You want
}

class FileEtiquette  {
       User author;
}

class SharingIncident {
  User user;
  FileEtiquette  file;
  int level;
}

也许我没有完全理解你的命名法,所以名字可能不够优雅。

下一次尝试,未编译,仅作为示例。

    @Entity
    class User {
        @Id
        int id;
        @Basic
        Set<SharingIncident> shares;
        @Basic
        Set<FileEtiquette> myFiles;
         // ... fields waht You want
    }

@Entity
class FileEtiquette  {
    @Id
    int id;
    @Basic
    Set<SharingIncident> sharedBy;
    User author;
}

@Entity
class SharingIncident {
    @Id
    int id;
    User user;
    FileEtiquette  file;
    int level;
}

几句话。

  • 多对多可以“自动”实现,但在您的 示例整数字段很有趣,值得设计关系 手动。
  • 通常当普通程序员认为 List&lt;&gt; 时,在 JPA 中 Set&lt;&gt; 可以 变得更好
  • 更改您的 File 名称是我的个人喜好
  • 根据我对您的意图的理解,这只是基础,可能有许多改进。我的目标是展示如何以经典方式替换 Map

可能的下一步,让我考虑一下

【讨论】:

  • 我确信您的解决方案将正常工作,但目前我想通过使用 JPA 2.1 的地图来解决它。如果最终我最终使用您的建议,我会将您的答案标记为最佳。谢谢!
【解决方案2】:

基于this tutorial,我对字段进行了如下注释:

class User {
       @OneToMany
       List<File> ownFiles;

       @ElementCollection
       Map<File, Integer> sharedFiles; 
}

class File {
       @ManyToOne
       User author;

       @ElementCollection
       Map<User, Integer> sharedUsers;
}

更详细:

class User {
       @OneToMany
       @JoinTable(name = "USER_HAS__OWN_FILES", joinColumns = {
       @JoinColumn(name = "AUTHOR", referencedColumnName = "USERNAME")}, 
            inverseJoinColumns = {@JoinColumn(name = "OWN_FILE_ID", referencedColumnName = "ID") })
       List<File> ownFiles;

       @ElementCollection
       @CollectionTable(name = "USER_HAS_SHARED_FILES", joinColumns = 
       @JoinColumn(name = "USERNAME", referencedColumnName = "USERNAME"))
       @MapKeyJoinColumn(name = "SHARED_FILE_ID", referencedColumnName = "ID")
       @Column(name = "PERMISSION_LEVEL")
       Map<File, Integer> sharedFiles; 
}

class File {
       @ManyToOne
       @JoinColumn(name = "FILE_AUTHOR", referencedColumnName = "USERNAME")
       User author;

       @ElementCollection
       @CollectionTable(name = "FILE_HAS_BEEN_SHARED_TO_USER", 
           joinColumns = @JoinColumn(name = "FILE_ID", referencedColumnName = "ID"))
       @MapKeyJoinColumn(name = "USER_THAT_FILE_HAS_BEEN_SHARED_TO", referencedColumnName = "USERNAME")
       @Column(name = "PERMISSION_LEVEL")
       Map<User, Integer> sharedUsers;
}

现在它可以按我的预期工作,生成以下表格:

  • User (ID, Username, ...)
  • File (ID, Filename, Author, ...)
  • User_has_shared_files (Username, Shared_file_id, permission_level)
  • User_has__own_files (Username, Own_file_id)
  • File_has_been_shared_to_user (File_id, Username_id, permission_level)

@ElementCollection 注解对于映射Java.Map&lt;Entity, basicType&gt; 很有用

【讨论】:

  • 我刚刚发现这样数据在数据库中存储了两次,因为 User_has_shared_files 和 File_has_been_shared_to_user 是等价的
最近更新 更多