Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hibernate mapping: one column to multiple tables

I have a 'best practice' question for a scenario.

Scenario: Multiple entities in a DB, for example, Document, BlogPost, Wiki can be shared by individuals. Instead of creating a share table for each entity, a single Share table is created. The issue is, how to map the share table with different entities?

I have three options, please advise which option is best, and if there is a better option.

Option1: Create table Shares as:

SHARES  
id (unique)
entityId (non DB enforced FK to DOCUMENTS, WIKIS, POSTS etc.)
entityType
sharedBy
sharedWith
sharedDate

Here, entityId will be a FK to documentId, wikiId, postId etc. etc. and entityType will identity what type the entityId is.

This has issues in Hibernate modelling, when creating Share to entity mapping, such as share.getDocument() or share.getWiki() etc.

Option 2: Create table Shares which only holds share information, and then create resolution tables that tie the share to the entity.

SHARES
id(PK)
sharedBy
sharedWith
sharedDate
shareType (helper field for searches)

SHARES_DOCUMENTS
share_id (unique ID and FK, one to one with SHARES)
document_id (FK to DOCUMENTS)

SHARES_POST
share_id (unique ID and FK, one to one with SHARES)
post_id (FK to POSTS)

more share tables here.

So, hibernate wise, Share can have one to one for each of the share types (like share.getDocument(), share.getPost(), and shareType will identify which relationship is 'active' )

Option 3 Similar to option 1, but create individual columns instead of entity id

SHARES
id (unique ID)
documentId (FK to DOCUMENTS, nullable)
postId (FK to POSTS, nullable)
wikiId (FK to WIKIS, nullable)
sharedBy
sharedWith
sharedDate
sharedType

Here, each column could be mapped to respective entity, but they are nullable. sharedType can identify which relationship is 'active'.

So, the question is , which practice is best, both database wise as well as hibernate mapping (and eventual querying, performance wise).

Thanks M. Rather

like image 843
M. Rather Avatar asked Dec 01 '11 16:12

M. Rather


People also ask

How do you map an entity to multiple tables in hibernate?

Yes, you can map an entity to 2 database tables in 2 simple steps: You need to annotate your entity with JPA's @Table and @SecondaryTable annotations and provide the names of the first and second table as the value of the name parameters.

How do you map multiple entities on the same table?

Entity Mappings If you want to map the same database table to two entities, you should create a simple inheritance hierarchy. The superclass should be abstract and contain all attributes that are shared by both entities. You should map it as a mapped superclass so that it is not an entity itself.


1 Answers

As suggested by TheStijn, after looking into different ways to setup inheritance relationships, I went with 'Single Table per class hierarchy' approach, and ended up with the table like:

SHARES
---------
id PK
shared_by FK to User
shared_with FK to User
shared_Date
document_id nullable FK to Document
post_id nullable FK to Posts
... more ids here to link to more entities
type_discriminator (values, DOCUMENT, POST ... )

On Hibernate/Java side, One Share abstract class as...

@Entity
@Table(name="SHARES")
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="TYPE_DISCRIMINATOR", discriminatorType=DiscriminatorType.STRING)
public abstract class Share {
    @Id
    @Column( name="ID", nullable=false )
    @GeneratedValue(generator="system-uuid")
    @GenericGenerator(name="system-uuid", strategy = "uuid")
    private String id;

    @ManyToOne
    @JoinColumn( name="SHARED_BY", nullable=false )
    private User sharedBy;

    @ManyToOne
    @JoinColumn( name="SHARED_WITH", nullable=false )
    private User sharedWith;

    @Column(name="SHARED_DATE", columnDefinition="TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP", nullable=false)
    @Temporal(TemporalType.TIMESTAMP)
    private Date sharedDate;        
    ...

}

And two normal classes..

@Entity
@DiscriminatorValue("DOCUMENT")
public class SharedDocument extends Share { 
    @ManyToOne
    @JoinColumn( name="DOCUMENT_ID", nullable=true )
    private Document document;
    ....

}

@Entity
@DiscriminatorValue("POST")
public class SharedPost extends Share {
    @ManyToOne
    @JoinColumn( name="POST_ID", nullable=true )
    private Post post;
    ....

}

As for usage, use the concrete classes only as:

@Test
public void saveNewDocumentShare(){
    SharedDocument sharedDocument = new SharedDocument();
    sharedDocument.setDocument(document1);
    sharedDocument.setSharedBy(teacher1);
    sharedDocument.setSharedWith(teacher2);
    sharedDocument.setSharedDate(new Date());

    sharedDocument.setCreatedBy("1");
    sharedDocument.setCreatedDate(new Date());
    sharedDocument.setModifiedBy("1");
    sharedDocument.setModifiedDate(new Date());


    SharedDocument savedSharedDocument = dao.saveSharedDocument(sharedDocument);

    assertNotNull(savedSharedDocument);
    assertThat(savedSharedDocument.getId(),notNullValue());
}

@Test
public void saveNewPostShare(){
    SharedPost sharedWikiPage = new SharedWikiPage();
    sharedPost.setPost(post1);
    sharedPost.setSharedBy(teacher1);
    sharedPost.setSharedWith(teacher2);
    sharedPost.setSharedDate(new Date());

    sharedPost.setCreatedBy("1");
    sharedPost.setCreatedDate(new Date());
    sharedPost.setModifiedBy("1");
    sharedPost.setModifiedDate(new Date());


    SharedPost savedSharedPost = dao.saveSharedPost(sharedPost);

    assertNotNull(savedSharedPost);
    assertThat(savedSharedPost.getId(),notNullValue());

}
like image 80
M. Rather Avatar answered Oct 19 '22 03:10

M. Rather