CodeMed
CodeMed

Reputation: 9191

retrieving polymorphic entities with hibernate and mysql

In a spring mvc application using hibernate over a MySQL database, I have an AccessLog entity that records the activities performed by various types of actors on various types of target resources. For simplicity, I am using polymorphism to store all the actor ids in one column and all the target resource ids in another column, regardless of their type. But how do I query the target entities and actor entities later?

Here is the AccessLog entity. It currently uses an actor_type string property to indicate the type of the entity whose id is stored in actorentity_id, and another string property target_type to indicate the type of entity whose id is stored in targetentity_id. But this could get messy because it relies on logic in the application's business layer to manage all the string type values. I could add a type property to BaseEntity, but that would still involve managing the type values in the business layer. How can I set things up so that this is designed most elegantly?

@Entity
@Table(name = "accesslogs")
public class AccessLog extends BaseEntity{

    @ManyToOne
    @JoinColumn(name = "actorentity_id")
    private BaseEntity actor_entity;

    @ManyToOne
    @JoinColumn(name = "targetentity_id")
    private BaseEntity target_entity;

    @Column(name="actorentity_type")//this could get messy
    private String actor_type;

    @Column(name="targetentity_type")//this could get messy
    private String target_type;

    @Column(name="action_code")
    private String action;

    @Column(name="access_date")
    @Type(type="org.jadira.usertype.dateandtime.joda.PersistentDateTime")
    private DateTime accessdate;

    //getters and setters
}

Here is the BaseEntity

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.TABLE)
    protected Integer id;

    public void setId(Integer id) {this.id = id;}
    public Integer getId() {return id;}
}

Here are a couple of examples of types of actor entities that extend BaseEntity, and which could be stored in the AccessLog.actor_entity property:

@Entity
@Table(name="users")
public class User extends BaseEntity{
    //other stuff
}

@Entity
@Table(name="externalsystems")
public class ExternalSystem extends BaseEntity{
    //other stuff
}

Here is the DDL:

CREATE TABLE IF NOT EXISTS accesslogs(
  id int(11) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
  actorentity_id int(11) UNSIGNED NOT NULL,
  targetentity_id int(11) UNSIGNED NOT NULL,
  actorentity_type varchar(100), #This could get messy
  targetentity_type varchar(100), #This could get messy
  action_code varchar(100),
  access_date DATETIME
)engine=InnoDB;SHOW WARNINGS;

CREATE TABLE IF NOT EXISTS users(
  id int(11) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
  #other stuff
)engine=InnoDB;SHOW WARNINGS;

CREATE TABLE IF NOT EXISTS externalsystems(
  id int(11) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
  #other stuff
)engine=InnoDB;SHOW WARNINGS;

Upvotes: 0

Views: 189

Answers (1)

Angel Villalain
Angel Villalain

Reputation: 595

Based on the problem you propose, there are two possible solutions. If you want to code a custom solution for the problem you should do the following code.

Here is how the BaseEntity will look like

@MappedSuperClass
public abstract class BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.TABLE)
    private Integer id;

    public Integer getId() {return id;}
}

An intermediate class would be needed (Let's call it AuditableEntity)

@Entity
@Inheritance(strategy=InheritanceType.Joined)    
public abstract class AuditableEntity extends BaseEntity {

    @OneToMany(mappedBy="actor")
    private List<AccessLog> actorLogs;

    @OneToMany(mappedBy="target")
    private List<AccessLog> targetLogs;
}

All your actors should extend from this class, so for example your User entity will look like this:

@Entity
@PrimaryKeyJoinColumn
public class User extends AuditableEntity{
    //other stuff
}

At last your AccessLog will look like this:

@Entity
@Table(name = "accesslogs")
public class AccessLog extends BaseEntity{

    @ManyToOne
    @JoinColumn
    private AuditableEntity actor;

    @ManyToOne
    @JoinColumn
    private AuditableEntity target;

    @Column(name="action_code")
    private String action;

    @Column(name="access_date")
    @Type(type="org.jadira.usertype.dateandtime.joda.PersistentDateTime")
    private DateTime accessdate;

    //getters and setters
}

But I will not implement this approach, a better solution is to use Hibernate Envers. Check the link I posted here related to that project. Your solution will require much code in all aspects each time you made changes to the data.

Upvotes: 1

Related Questions