看Hibernate源碼之 ID Generator

Hibernate的id生成有N種策略, 可以通過hbm文件或者annotation配置.

支持的策略包括:uuid, hilo, assigned, identity, select, sequence, seqhilo, increment, foreign, guid, uuid.hex, sequence-identity.

對應這些策略, 可以在org.hibernate.id包下找到, 其中有一個IdentifierGeneratorFactory類用於根據實體類的配置(hbm文件的<id>元素或@Id,@GeneratedValue註解)來創建相應的策略.

public final class IdentifierGeneratorFactory {
... //註冊所有支持的ID生成策略
private static final HashMap GENERATORS = new HashMap();
static {
   GENERATORS.put( "uuid", UUIDHexGenerator.class );
   GENERATORS.put( "hilo", TableHiLoGenerator.class );
   GENERATORS.put( "assigned", Assigned.class );
   GENERATORS.put( "identity", IdentityGenerator.class );
   GENERATORS.put( "select", SelectGenerator.class );
   GENERATORS.put( "sequence", SequenceGenerator.class );
   GENERATORS.put( "seqhilo", SequenceHiLoGenerator.class );
   GENERATORS.put( "increment", IncrementGenerator.class );
   GENERATORS.put( "foreign", ForeignGenerator.class );
   GENERATORS.put( "guid", GUIDGenerator.class );
   GENERATORS.put( "uuid.hex", UUIDHexGenerator.class ); // uuid.hex is deprecated
   GENERATORS.put( "sequence-identity", SequenceIdentityGenerator.class );
}
...
public static IdentifierGenerator create(String strategy, Type type, Properties params, Dialect dialect)
    throws MappingException {
   try {
    Class clazz = getIdentifierGeneratorClass( strategy, dialect );
...
}

public static Class getIdentifierGeneratorClass(String strategy, Dialect dialect) {
   Class clazz = ( Class ) GENERATORS.get( strategy );
...
}
}
顯然create()方法是用於創建ID生成器的, 而且用到了參數strategy和dialect. Hibernate在初始化SessionFactory的時候就會準備這些ID生成器. 見以下代碼

SessionFactoryImpl(){
...
   Iterator classes = cfg.getClassMappings();
   while ( classes.hasNext() ) {
    PersistentClass model = (PersistentClass) classes.next();
    if ( !model.isInherited() ) {
     IdentifierGenerator generator = model.getIdentifier().createIdentifierGenerator(
       settings.getDialect(),
            settings.getDefaultCatalogName(),
            settings.getDefaultSchemaName(),
            (RootClass) model
      );
     identifierGenerators.put( model.getEntityName(), generator );
    }
   }

model.getIdentifier().createIdentifierGenerator() 最終會引用到
return IdentifierGeneratorFactory.create(
     identifierGeneratorStrategy,
     getType(),
     params,
     dialect
    );

看來SessionFactory做了太多的工作. 爲了能對這麼多中ID生成策略有最直觀的瞭解, 下面給出一個各種ID策略的類關係圖.

看Hibernate源碼之 ID Generator - wolfgangkiefer - wolfgangkiefer的博客

該圖分爲兩部分, 藍色先左邊的ID生成器是不需要藉助數據庫, 採用本地算法生成的, 而右邊的ID生成器則需要藉助數據庫提供的ID生成能力.

最主要的接口有4個, 圖中用藍色標出.
* IdentifierGenerator: ID生成器接口, 只有一個generate方法, 返回生成的ID.
* PostInsertIdentifierGenerator: 針對MySQL, MSSQL這類能爲主鍵設置自動增長的ID生成器
* PersistentIdentifierGenerator: 針對需要訪問DB來生成ID的生成器, 比如GUID.
* Configurable: 用於讀取配置信息

具體每個類如何處理:
1) uuid: 是採用128位的算法生成惟一值,支持大部分的數據庫
   public Serializable generate(SessionImplementor session, Object obj) {
   return new StringBuffer(36)
    .append( format( getIP() ) ).append(sep)
    .append( format( getJVM() ) ).append(sep)
    .append( format( getHiTime() ) ).append(sep)
    .append( format( getLoTime() ) ).append(sep)
    .append( format( getCount() ) ) //注: 每次加1, JVM內唯一, 通過synchronized來保證實現
    .toString();
}
protected String format(int intval) {
   String formatted = Integer.toHexString(intval);
   StringBuffer buf = new StringBuffer("00000000");
   buf.replace( 8-formatted.length(), 8, formatted );
   return buf.toString();
}
protected String format(short shortval) {
   String formatted = Integer.toHexString(shortval);
   StringBuffer buf = new StringBuffer("0000");
   buf.replace( 4-formatted.length(), 4, formatted );
   return buf.toString();

2)GUID: 通過使用數據庫本身的uuid算法來實現
public class GUIDGenerator implements IdentifierGenerator {
   public Serializable generate(SessionImplementor session, Object obj)
   throws HibernateException {
       final String sql = session.getFactory().getDialect().getSelectGUIDString();
   ...
}
假如getDialect()返回的是MySQLDialect, 則返回的是
public String getSelectGUIDString() {
   return "select uuid()";
}
但是如果不支持uuid的數據庫, 則拋出異常
public String getSelectGUIDString() {
   throw new UnsupportedOperationException( "dialect does not support GUIDs" );
}

3)increment:
public class IncrementGenerator implements IdentifierGenerator, Configurable {
...
public synchronized Serializable generate(SessionImplementor session, Object object)
throws HibernateException {

   if (sql!=null) {
    getNext( session ); //注:使用了一個sql: "select max(" + column + ") from " + buf.toString();
   }
   return IdentifierGeneratorFactory.createNumber(next++, returnClass);
}
}
留意這裏對generate方法使用了同步, 可想如果所有ID都通過hibernate創建, 則是安全的...

4) foreign key 簡而言之, 就是要取到關聯的對象的ID
foreign-key的配置稍微繁瑣一點, 附上一個例子: 對於帖子(Post)和評論(Comment), Comment表有一個外鍵fk_post, comment.post_id關聯到Post.id. 那麼在Comment.hbm.xml中就可以這樣配置

Comment.hbm.xml
------------------------------------------------------------
<hibernate-mapping package="work">
    <class name="Comment" lazy="false">
        <id name="id">
            <generator class="foreign">
                <param name="property">post</param>
            </generator>
        </id>
...
        <many-to-one name="post" column="post_id"></many-to-one>
    </class>
</hibernate-mapping>


hibernate源代碼:
------------------------------------------------------------
public Serializable generate(SessionImplementor sessionImplementor, Object object)
throws HibernateException {
    //注:這裏object是Comment對象
 
   Session session = (Session) sessionImplementor;

   //注:這裏associatedObject是Post對象
   Object associatedObject = sessionImplementor.getFactory()
          .getClassMetadata( entityName )
          .getPropertyValue( object, propertyName, session.getEntityMode() );
 
   if ( associatedObject == null ) {
    throw new IdentifierGenerationException(
      "attempted to assign id from null one-to-one property: " +
      propertyName
     );
   }
 
   EntityType type = (EntityType) sessionImplementor.getFactory()
        .getClassMetadata( entityName )
        .getPropertyType( propertyName );

   Serializable id;
   try {
    id = ForeignKeys.getEntityIdentifierIfNotUnsaved(
      type.getAssociatedEntityName(),
     associatedObject,
      sessionImplementor
     );
   }
   catch (TransientObjectException toe) {
    id = session.save( type.getAssociatedEntityName(), associatedObject );
   //注: 嘗試保存該對象來生成ID, 這個操作可能觸發一系列其他的東西, 如事件, 緩存寫入等等
   }

   if ( session.contains(object) ) {
    //abort the save (the object is already saved by a circular cascade)
    return IdentifierGeneratorFactory.SHORT_CIRCUIT_INDICATOR;
    //throw new IdentifierGenerationException("save associated object first, or disable cascade for inverse association");
   }
   return id;
}

5) Identity: 利用數據庫的自增長方式來生成ID
相比前面的策略, 這是很有意思的ID生成策略, 因爲hibernate並不能在insert前預先獲得ID, 而是在insert後, 依賴於JDBC API的PreparedStatement.getGeneratedKeys()方法來取得ID, 該方法返回的是一個ResultSet, 只有一列, 名稱爲GENERATED_KEY. 所以Hibernate也是採用一種後置處理的方式: 即在調用到IdentifierGenerator.getnerate()方法的時候(其實這個時候的實現是IdentityGenerator類) , 直接返回一個Serilizable對象--IdentifierGeneratorFactory.POST_INSERT_INDICATOR. 接着, 在我們使用session.save(object)方法的時候, 會判斷save操作的類型是否爲IdentifierGeneratorFactory.POST_INSERT_INDICATOR,再進行相應處理.

save部分代碼量太大, 免了. 看一些關鍵的.

先是"奇怪"的generate()方法
public abstract class AbstractPostInsertGenerator implements PostInsertIdentifierGenerator {
public Serializable generate(SessionImplementor s, Object obj) {
   return IdentifierGeneratorFactory.POST_INSERT_INDICATOR;
}
}
public class IdentityGenerator extends AbstractPostInsertGenerator {
.. //沒有覆蓋generate()
}

然後是session.save()對應的事件監聽器 AbstractSaveEventListener 的saveWithGeneratedId()
protected Serializable saveWithGeneratedId(...){
...
if ( generatedId == null ) {
    throw new IdentifierGenerationException( "null id generated for:" + entity.getClass() );
   }
   else if ( generatedId == IdentifierGeneratorFactory.SHORT_CIRCUIT_INDICATOR ) {
    return source.getIdentifier( entity );
   }
   else if ( generatedId == IdentifierGeneratorFactory.POST_INSERT_INDICATOR ) {
    return performSave( entity, null, persister, true, anything, source, requiresImmediateIdAccess );
   }
...
}
該方法一直執行到protected Serializable performSaveOrReplicate(...)方法的
if ( useIdentityColumn ) {
    EntityIdentityInsertAction insert = new EntityIdentityInsertAction(
      values, entity, persister, source, shouldDelayIdentityInserts
    );
    if ( !shouldDelayIdentityInserts ) {
     log.debug( "executing identity-insert immediately" );
     source.getActionQueue().execute( insert ); //這裏有文章,hibernate已先把先前的操作先轉換成sql執行

經過N多處理後, 最後回到剛纔提到的JDBC API上. 在IdentityGenerator.GetGeneratedKeysDelegate子類中
   public Serializable executeAndExtract(PreparedStatement insert) throws SQLException {
    insert.executeUpdate();
    ResultSet rs = null;
    try {
     rs = insert.getGeneratedKeys();
     return IdentifierGeneratorFactory.getGeneratedIdentity(rs,persister.getIdentifierType());
    ... 
}
~_~

不得不佩服Hibernate團隊的重構能力, 許多功能都被按照"行爲向上集中"的規則處理, 即相同的行爲, 放在更高層次的父類去. 調式過程中能看到很多類都是繼承於AbstractXXX. 並且每個類的責任很明確. 像EntityPersister, Batcher, ActionQueue, Executable和它的實現類, 等等...

TODO: 其他的策略

原文:http://www.cnblogs.com/firstdream/archive/2012/04/22/2464681.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章