hibernate id策略

Hibernate的id生成有N种策略, 可以通过hbm文件或者annotationhibernate 源码配置.

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


Hibernate主键生成方式 Key Generator
Hibernate主键生成方式 Key Gehibernate 主键nerator主键产生器可选项说明:1) assigned主键由外部程序负责生成,无需Hibernate参与。


2) hilo通过hi/lo 算hibernate 联合主键法实现的主键生成机制,需要额外的数据库表保存主键生成历史状态。


3) seqhilo与hilo 类似,通过hi/lohibernate 复合主键 算法实现的主键生成机制,只是主键历史状态保存在Sequence中,适用于支持Sequence的数据库,如Oracle.


4) increment主键按数值顺序递增。此方式的实hibernate 没有主键现机制为在当前应用实例中维持一个变量,以保存着当前的最大值,之后每次需要生成主键的时候将此值加1作为主键。


这种方式可能产生的问题是:如果当前有多个实例访问同一个数据库,那么由于各个实例各自维护主键状态,不同实例可能生成同样的主键,从而hibernate 无主键造成主键重复异常。因此,如果同一数据库有多个实例访问,此方式必须避免使用。


5) identity采用hibernate 指定主键数据库提供的主键生成机制。如DB2、SQL Server、MySQL中的主键生成机制。


6) sequence采用数据库提供的sequence 机制生成主键。如Oralce 中hibernate主键策略的Sequence.


7) native由Hibernate根据底层数据库自行判断采用identity、hilo、sequence其中一hibernate 主键生成种作为主键生成方式。


8) uuid.hex由Hibernate基于128 位唯一值产生算法生成16 进制数值(编码后以长度32 的字符hibernate 自动主键串表示)作为主键。


9) uuid.string与uuid.hex 类似,只prop key hibernate是生成的主键未进行编码(长度16)。在某些数据库中可能出现问题(如PostgreSQL)。


10) foreign使用外部表的字段作为主键。


一般而言,利用uuid.hex方式生成主键将提供最好的性能和数据库平台适应性。


另外由于常用的数据库,如Oracle、DB2、SQLServer、MySql 等,都提供了易用的主键生成机制(Auto-Increase 字段或者Sequence)。我们可以在数据库提供的主键生成机制上,采用generator-class=native的主键生成方式。不过值得注意的是,一些数据库提供的主键生成机制在效率上未必最佳,大量并发insert数据时可能会引起表之间的互锁。


数据库提供的主键生成机制,往往是通过在一个内部表中保存当前主键状态(如对于自增型主键而言,此内部表中就维护着当前的最大值和递增量),之后每次插入数据会读取这个最大值,然后加上递增量作为新记录的主键,之后再把这个新的最大值更新回内部表中,这样,一次Insert操作可能导致数据库内部多次表读写操作,同时伴随的还有数据的加锁解锁操作,这对性能产生了较大影响。


因此,对于并发Insert要求较高的系统,推荐采用uuid.hex 作为主键生成机制。

Related posts:

Hibernate对各数据库的连接方言
Hibernate简介
Java程序连接各种数据库的方法
常用的数据库连接方法
Java生成UUID通用唯一识别码


对应这些策略, 可以在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().createhibernate3 源码IdentifierGenerator
(
settings.getDialect(),
settings.getDefaultCatalogName(),
settings.getDefaultSchemaName(),
(RootClass) model
);
identifierGenerators.put( model.getEntityName(), generator );
}
}

model.getIdentifier().createhibernate3 源码IdentifierGenerator() 最终会引用到

return IdentifierGeneratorFactory.create(
identifierGeneratorStrategy,
getType(),
params,
dialect
);

看来Sesshibernate 源码编译ionFactory做了太多的工作. 为了能对这么多中ID生成策略有最直观的了解, 下面给出一个各种ID策略的类关系图.

该图分为两部分, 蓝色先左边的ID生成器是不需要借助数据库, 采用本地算法生成的,hibernate源码解析 而右边的ID生成器则需要借助数据库提供的ID生成能力.

最主要的接口有4个, 图中用蓝色标出.
* identifiergenerator: id生成器接口, 只有一个ghibernate idenerate方法, 返回生成的id.
* hibernate save idpostinsertidentifiergenerator: 针对mysql, mssql这类能为主键设置自动增长的id生成器
* persistentidentifihibernate id自增ergenerator: 针对需要访问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”>
<paid generatorram 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和它的实现类, 等等…
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值