Search code examples
javahibernatejpajakarta-eeeclipselink

JPA: How should I annotate this Map collections?


I will explain myself by giving you a descriptive example. Let's say we are programming a very simple Cloud Storage using JPA, so we have two main classes, User and File coded as follows:

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

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

How should I annotate this fields? I've tried, but I always get NotSerializableException "data too long for column".

  • By the way, File is just an example, my real class is small, only composed of some Strings. So I'm pretty sure the NotSerializableException is thrown because of the Map annotations.

  • By the way, here you have the meaning of the Maps values,

    • In the case of User, the Integer value of the files is representing the permission level of the user over the file
    • And in the case of File the Keys are those Users who the file has been shared to and the Value is the permission level of that User over the File

P.S: I'm using Eclipselink


Solution

  • Based on this tutorial I annotated the fields in the following way:

    class User {
           @OneToMany
           List<File> ownFiles;
    
           @ElementCollection
           Map<File, Integer> sharedFiles; 
    }
    
    class File {
           @ManyToOne
           User author;
    
           @ElementCollection
           Map<User, Integer> sharedUsers;
    }
    

    More detailed:

    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;
    }
    

    Now it works as I expected, generating the following tables:

    • 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)

    The @ElementCollection annotation is useful to map Java.Map<Entity, basicType>