-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 2 posts ] 
Author Message
 Post subject: HOWTO: A base entity with a flexible primary key
PostPosted: Fri Feb 23, 2007 10:29 am 
Beginner
Beginner

Joined: Sat Aug 19, 2006 8:04 pm
Posts: 30
I've seen a few base entity implementations before, and the one thing that always kept me from doing it was

@Id @GeneratedValue(strategy=GenerationType.AUTO)
protected Long id;


What if i don't want to auto number rows with a bigint? Maybe i want to use a String or a composite primary key? This lack of flexibilty was a deal breaker for me.

I found a really good implementation of this here, this guy has some good stuff http://burtbeckwith.com/blog/?p=17 but unfortunately still the auto id. my recent learning all about tricky stuff with generics (lots of fun!) inspired me to take a stab at this.

here's what I've come up with, i think it's pretty good but flaws & criticism is highly encouraged. in a few months i will have millions of subclasses of this thing so it needs to be the best it can be.


first the AbstractEntity class. The primary key type is generic, and we use a custom GenericGenerator where the subclass tells us how to generate it.

log4j is available to all subclasses, we use commons lang for reflective equals & hashcode. this might be a performance issue. you could also do the same for toString, but since i'm using the best web framework ever in stripes, http://stripes.mc4j.org it likes the id for toString.

we timestamp when the row is updated (might add a seperate one for created), and instead of deleting objects, we soft delete them - mark it as deleted and filter these out in finder methods. see that blog for a very nice spring dao implementation, mine is a combination of that and this gem of an article, http://www.ibm.com/developerworks/java/ ... icdao.html


Code:
@MappedSuperclass
public abstract class AbstractEntity<PK extends Serializable> implements
      Serializable {

   protected transient final Log LOG = LogFactory.getLog(getClass());


   @Id
   @GeneratedValue(generator = IdGen.NAME)
   protected PK id;


   protected Date timeStamp;


   protected boolean deleted;


   public AbstractEntity() {
      this(null);
   }


   public AbstractEntity(PK id) {
      super();
      this.id = id;
   }


   @Override
   public String toString() {
      return id.toString();
   }


   @Override
   public boolean equals(final Object that) {
      return EqualsBuilder.reflectionEquals(this, that);
   }


   @Override
   public int hashCode() {
      return HashCodeBuilder.reflectionHashCode(this);
   }

// getters & setters
}



here's a stupid class just to avoid typing those strings everywhere,
Code:
public class IdGen {

   public static final String NAME                 = "CustomIdGenerator";
   public static final String MANUAL          = "assigned";
   public static final String AUTO       = "identity";
}


a subclass with an auto long primary key,
Code:
@Entity
@GenericGenerator(name=IdGen.NAME, strategy=IdGen.AUTO)
public class AutoLongPK extends AbstractEntity<Long> {

   private static final long serialVersionUID = 1L;

   private String prop1;

   private String prop2;
// getters & setters
}




and a subclass with a manual string pk,
Code:
@Entity
@GenericGenerator(name=IdGen.NAME, strategy=IdGen.MANUAL)
public class ManualStringPK extends AbstractEntity<String> {

   private static final long serialVersionUID = 1L;

   private String prop1;

   private String prop2;
// getters & setters
}


that is some good stuff, hibernate is the shit! lets get some ideas in here and make this better.

damn i lost a credit for this


Top
 Profile  
 
 Post subject:
PostPosted: Fri Mar 02, 2007 5:14 pm 
Beginner
Beginner

Joined: Sat Aug 19, 2006 8:04 pm
Posts: 30
hopefully this thread will not turn into a blog, i'm surprised there's not more interest in this. my initial stab at this worked way better than expected but still did not give the total flexibility i'm looking for.

this allows you to specify the pk type & generation strategy, but you can't specify custom sql, length, names, etc etc. this was important for me to have since i need to roll it out onto a few legacy systems that do this sort of thing.

the solution was to introduce another base class,

AbstractEntity --> IdEntity --> Your entity with the standard ID column
AbstractEntity --> Your entity with a totally custom id property,

Code:
/**
* Abstract base class for all database entities.  Keeps track
* of creation date, last modification date, allows 'deleted' objects
* to remain in the database w/o destroying existing relationships.
* Primary key property is flexible in terms of type, generation strategy
* and custom attributes.
*
* The main purposes of this class are to keep common properties in one
* place, give the middle layer a better bound to go by than <? extends Object>,
* have proper implementations of equals and hashcode for collections, and
* most importantly not impose any constraints on the identity property for the
* end user.
*
* Subclasses must define getId() & place all annotations on getter
* methods.
*/
@MappedSuperclass
public abstract class AbstractEntity<PK extends Serializable> implements
      Serializable {

   // log4j is available to all subclasses
   protected transient final Log LOG = LogFactory.getLog(getClass());


   protected PK id;         // the objects primary key
   @Transient               // marked as transient so hibernate won't
   public abstract PK getId();   // think this is the ID (crashes w/o it)


   protected Date created;      // when the entity was originally persisted


   protected Date modified;   // last time it was modifiied


   protected Boolean deleted;   // whether or not the object is deleted,
                        // useful for retaining exsisting relationships
                        // and not showing deleted stuff to the user


   /**
    * We employ some intelligence in here, if
    * deleted was a boolean and the column is null
    * it fails.  So we use a Boolean which permits nulls,
    * and just treat null = false.
    */
   public void setDeleted(Boolean deleted) {
      this.deleted = (deleted == null ? false : deleted);
   }


   /**
    * Default constructor, does nothing
    */
   public AbstractEntity() {
      this(null);
   }


   /**
    * Construct a new entity with a given PK
    * @param id the primary key
    */
   public AbstractEntity(PK id) {
      super();
      this.id = id;
   }


   /**
    * Stripes likes the id for toString to populate
    * select boxes.  This can be overridden if not needed.
    */
   @Override
   public String toString() {
      return getId().toString();
   }


   /**
    * Use commons-lang for a reflexive equals
    */
   @Override
   public boolean equals(final Object that) {
      return EqualsBuilder.reflectionEquals(this, that);
   }


   /**
    * Use commons-lang for reflexive hashCode
    */
   @Override
   public int hashCode() {
      return HashCodeBuilder.reflectionHashCode(this);
   }



   // useless accessor methods


   public Date getCreated() {
      return this.created;
   }



   public void setCreated(Date created) {
      this.created = created;
   }



   public Boolean getDeleted() {
      return this.deleted;
   }



   public void setId(PK id) {
      this.id = id;
   }



   public Date getModified() {
      return this.modified;
   }



   public void setModified(Date modified) {
      this.modified = modified;
   }
}



Code:
/**
* Base entity that has a default identifier specification.
* Extends AbstractEntity to have all those properties, main
* purpose of this class was to alleviate the burden of
* defining the identifier property on the end user.
*
* Class techincally should not be abstract since there
* are not any abstract methods - it is a complete entity.
* However it is useless to have this in the database by
* itself so the constructor is kept protected.
*
*/
@MappedSuperclass
public class IdEntity<PK extends Serializable> extends
      AbstractEntity<PK> {

   private static final long serialVersionUID = 1L;

   /**
    * Default identifier property, no specified
    * sql or attributes.
    */
   @Override
   @Id
   @GeneratedValue(generator = IdGen.NAME)
   @Column(name="id", nullable=false, insertable=false, updatable=false)
   public PK getId() {
      return this.id;
   }


   /**
    * Constructors are protected, class
    * must be extended to use.
    */
   protected IdEntity()  {
      this(null);
   }

   protected IdEntity(PK id)  {
      super(id);
   }
}


then if you need to specify your own identifier, you derive from AbstractEntity and place your annotations on getId() ... if you just want a default identifer derive from IdEntity

i hope we can get some good discussion going on this techinque, i think it is pretty solid design.


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 2 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.