zoukankan      html  css  js  c++  java
  • 看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: 其他的策略

  • 相关阅读:
    leetcode 300. 最长上升子序列
    JAVA基础系列:Arrays.binarySearch二分查找
    leetcode 674. 最长连续递增序列
    小红书:笔试题(棋盘最短路径,笔记本草稿栈,迷宫游戏)
    VIPKID:笔试题(数组中和为0的一对数的数量,十进制转二进制中1的个数)
    [******] 树问题:普通二叉树的创建与遍历
    [******] 链表问题:将单向链表按某值划分成左边小、中间相等、右边大的形式
    [******] java多线程连续打印abc
    快手:笔试题(版本号比较,平方和为1,合并两个流)
    京东:笔试题(合唱队找剩余的最小值,考场安排搬出的人数尽可能少)
  • 原文地址:https://www.cnblogs.com/firstdream/p/2464681.html
Copyright © 2011-2022 走看看