I have one generic class using single table inheritance and other sub classes :
File, the main class
Folder extends File
Card extends File
Abstract FileLink extends File : a FileLink abstract class extending File
FolderLink extends FileLink and is composed with a Folder.
CardLink extends FileLink and is composed with a Card.
In my File class I have a collection of File with a oneToMany relationship, that can contain any type of File, so folders or folder links or card links.
@DiscriminatorColumn(name = "file_type", discriminatorType = DiscriminatorType.INTEGER)
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class File{
@OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
List<File> subFiles = new Linkedlist()
@ManyToOne(fetch = FetchType.LAZY)
protected File parent;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(length= 40)
private String name;
@Lob
private String description;
}
A folder can have several links.
@Entity
@DiscriminatorValue(File.FOLDER + "")
public class Folder extends File {
@OneToMany(mappedBy = "linkedFolder", fetch = FetchType.LAZY, targetEntity = FolderLink.class)
protected Set<FolderLink> folderLinks = new HashSet<>();
private Object folderAttribute;
}
same for a card
@Entity
@DiscriminatorValue(File.CARD+ "")
public class Card extends File {
@OneToMany(mappedBy = "linkedCard", fetch = FetchType.LAZY, targetEntity = CardLink.class)
protected Set<CardLink> cardLinks = new HashSet<>();
private Object cardAttribute;
}
The Filelink class represent a link to a file and do not have much specific attributes used, just the parent file and the file linked.
In fact when I serialize my subfiles collection I want my links (FolderLink or Cardlinks) to return the linked files attributes values.
So the Folderlink class is supposed to return the Folder attributes values, CardLink the Card attributes values, and FileLink the File attributes values.
My actual modelisation is made this way :
@NoArgsConstructor
public abstract class FileLink extends File {
@Override
public String getName() {
return "Link to "+this.getLinkedFile().getName();
}
@Override
public String getDescription() {
return this.getLinkedFile().getDescription();
}
...
@JsonIgnore
public abstract File getLinkedFile();
}
@Entity
@DiscriminatorValue(File.FOLDER_LINK + "")
public class FolderLink extends FileLink {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "folder_linked_id", insertable = false, updatable = false)
protected Folder linkedFolder;
@JsonIgnore
public File getLinkedFolder() {
return this.linkedFolder;
}
@Override
public String getFolderAttribute() {
return "Link to "+this.getLinkedFile().getFolderAttribute();
}
@Override
public File getLinkedFile() {
return this.getLinkedFolder();
}
}
@Entity
@DiscriminatorValue(File.CARD_LINK + "")
public class CardLink extends FileLink {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "card_linked_id", insertable = false, updatable = false)
protected Card linkedCard;
@JsonIgnore
public File getLinkedCard() {
return this.linkedCard;
}
@Override
public String getCardAttribute() {
return "Link to "+this.getLinkedFile().getCardAttribute();
}
@Override
public File getLinkedFile() {
return this.getLinkedCard();
}
}
So when a FolderLink is serialized I can retrieve the linked folder attributes in addition with the attributes declared in its parent class (File) through the FileLink class, same for a card link.
But what I'd like to do is to declare the OneToMany relationship into the File and the FileLink classes only so :
@DiscriminatorColumn(name = "file_type", discriminatorType = DiscriminatorType.INTEGER)
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class File{
@OneToMany(mappedBy = "linkedFile", fetch = FetchType.LAZY, targetEntity = FileLink.class)
protected Set<FileLink> fileLinks = new HashSet<>();
}
The Filelink class would not be abstract anymore.
@NoArgsConstructor
@Entity
@DiscriminatorValue(File.FILE_LINK + "")
public class FileLink extends File {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "file_linked_id", insertable = false, updatable = false)
protected File linkedFile;
@Override
public String getName() {
return "Link to "+this.getLinkedFile().getName();
}
...
@JsonIgnore
public File getLinkedFile(){
return this.linkedfile;
}
}
@Entity
@DiscriminatorValue(File.FOLDER_LINK + "")
public class FolderLink extends FileLink {
@Override
public String getFolderAttribute() {
return "Link to "+((Folder)this.getLinkedFile()).getFolderAttribute();
}
}
@Entity
@DiscriminatorValue(File.CARD_LINK + "")
public class CardLink extends FileLink {
@Override
public String getCardrAttribute() {
return "Link to "+((Card)this.getLinkedFile()).getCardAttribute();
}
}
But it doesn't work. When I create a FolderLink I know that the linked file is a Folder but if I retrieve it from the super class I can't cast it into a Folder, hibernate tell me he cannot cast a File into a Folder, and it's normal because a File is not a Folder.
Is there a way to achieve that goal ? My current implementation is convenient enough to me but if I could do more it would be great.
what you are looking for is Polymorphic Association Mapping, as you mentioned
hibernate tell me he cannot cast a File into a Folder, and it's normal because a File is not a Folder.
you can define the type you expect by adding another column as type control.
package com.example;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.Column;
import org.hibernate.annotations.Any;
import org.hibernate.annotations.AnyMetaDef;
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.MetaValue;
@Entity
@DiscriminatorValue(File.FILE_LINK + "")
public class FileLink extends File {
@Any (
metaColumn = @Column(name = "linkedFileType"),
fetch=FetchType.LAZY
)
@AnyMetaDef(idType = "long", metaType = "string", metaValues = {
@MetaValue(targetEntity = Folder.class, value = "folder"),
@MetaValue(targetEntity = Card.class, value = "card")
})
@Cascade({org.hibernate.annotations.CascadeType.ALL})
@JoinColumn(name = "file_linked_id")
protected File linkedFile;
@Override
public String getName() {
return "Link to "+this.getLinkedFile().getName();
}
...
@JsonIgnore
public File getLinkedFile(){
return this.linkedfile;
}
}