Hibernate 主键生成策略的选择

xyz0101123132 2012-02-27 11:22:50
介绍hibernate主键生成策略的文章网上比比皆是。但是如何选择一个适合于自己项目的主键生成策略缺没有什么好的指导性文章。在此希望与大家议论。
hibernate的主键生成策略主要包括了"uuid2","guid","uuid","uuid.hex","hilo","assigned","identity","select","sequence","seqhilo","increment","foreign","sequence-identity","enhanced-sequence","enhanced-table",全部在org.hibernate.id.factory.DefaultIdentifierGeneratorFactory中定义,至于每种生成策略的简单描述不是本文重点议论的话题,我们主要将着眼点放到各生成器的优缺点上去(当然都有优点只是适合不适合,本文就想议论这个)
hibernate主键生成采用策略模式进行设计,各个生成策略都直接或或者间接实现了IdentifierGenerator接口,此接口只有一个方法publicSerializablegenerate(SessionImplementorsession,Objectobject)throwsHibernateException;这个方法由各个类实现具体的生成逻辑。
我们来一个一个看一下:
1、uuid2,IdentifierGenerator的实现类是UUIDGenerator,具体由UUIDGenerationStrategy策略负责生成,它有两个实现StandardRandomStrategy和CustomVersionOneStrategy,他们都是使用java.util.UUID的api生成主键的,StandardRandomStrategy最终由UUID.randomUUID();生成,而CustomVersionOneStrategy则采用版本号与位运算通过构造函数newUUID(mostSignificantBits,leastSignificantBits);生成。
特点是:不需要和数据库交互,可根据RFC4122定义的5中变量控制具体的生成策略

2、guid,IdentifierGenerator的实现类是GUIDGenerator,通过session.getFactory().getDialect().getSelectGUIDString();获得各个数据库中的标示字符串,mySql用"selectuuid()";oracle9g用return"selectrawtohex(sys_guid())fromdual";其他看源码吧。
特点是:需要和数据库进行一次查询才能生成。数据库全局唯一。

3、uuid和uuid.hex 两个一个东西。IdentifierGenerator的实现类是UUIDHexGenerator,通过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()))生成。
特点:不需要和数据库交互,全网唯一。

4、hilo,IdentifierGenerator的实现类TableHiLoGenerator,逻辑较为复杂,通过高低位酸腐生成,但是需要给定表和列作为高值的源。加上本地的地位计算所得。复杂有兴趣看"数据建模101"(Ambler,2002)
特点;需要和数据库交互,全数据库唯一,与guid不同的是,在标识符的单个源必须被多个插入访问时可以避免拥堵。

5、assigned IdentifierGenerator的实现类Assigned,没有生成逻辑,如果为空就抛出异常。
特点:不需要和数据库交互,自己管理主键生成,显示的指定id.

6、identity,IdentityGenerator并没有直接实现IdentifierGenerator,而是扩展了AbstractPostInsertGenerator,并实现PostInsertIdentifierGenerator,而PostInsertIdentifierGenerator实现了IdentifierGenerator. 通过IdentifierGeneratorHelper类生成,这个比较特殊,它返回是个常量"POST_INSERT_INDICATOR",指在数据库插入后时生成,然后返回数据库生成的id,还有个常量"SHORT_CIRCUIT_INDICATOR",是用外键ForeignGenerator时使用的。
特点:需要和数据库交互,数据插入后返回(反查)id,同一列唯一

7、select, SelectGenerator扩展了AbstractPostInsertGenerator实现了Configurable接口,而AbstractPostInsertGenerator实现了PostInsertIdentifierGenerator。所以具有和identity类似的行为,有数据库触发器生成。
特点:需要和数据库交互,

8、sequence,SequenceGenerator实现了PersistentIdentifierGenerator接口,和Configurable接口,PersistentIdentifierGenerator接口扩展IdentifierGenerator接口,通过数据库不同获取不同的取值语句dialect.getSequenceNextValString( sequenceName );然后进行查询,缓存到IntegralDataTypeHolder中,通过generateHolder( session ).makeValue();获得。
特点:需要和数据库交互(但不是每次都是)。sequence唯一

9、seqhilo,扩展了SequenceGenerator,处理逻辑和hilo相同,值不过是使用一个具名的数据库序列来生成高值部分。
特点:同4

10、increment,IdentifierGenerator的实现类IncrementGenerator,并实现了Configurable接口。数据库启动时查询表的最大主键列支,并通过IntegralDataTypeHolder缓存。插入一条,它自加一。
特点:仅需要首次访问数据库。

11、foreign,IdentifierGenerator的实现类ForeignGenerator,通过给定的entityName和propertyName查询获得值。
特点:需要和数据库访问。

后面的几种基本上是上面各种逻辑的组合,不在一一分析了。enhanced-table是通过数据库中的表生成id的。


从上面可以看到,虽然这么多,但是大体可以分为三类
1、不需要和数据库交互就可以生成id的。包括uuid,uuid2,uuid.hex
2、需要和数据库交互以生成id的。guid,hilo,identity,select,sequence,seqhilo,increment、foreign
可细分为一个id一个sql:guid,identity,select,foreign
一个sql多个id:hilo,sequence,seqhilo,increment
3、不用交互我自己管理assigned

提高系统新能的主要做法就是显著减少数据库的访问次数。通过上面的分析,可作为我们考虑的一个指标。

我所在公司目前采用的主键生成策略是assigned,这种策略可以有效避免数据库的访问次数。而且处理比较简单,如下一个业务场景。
我需要新建一个实体A,并且给A绑定附件。
首先说下我们附件的绑定原理,为了时附件上传可以绑定到任意实体,并且支持事务。我们通过封装好的js进行绑定。处理逻辑如下
1、打开新建页面,判断AForm.id是否为null,是则method=save,否则method=update;
2、jsp的dom加载完成后调用,upload.js中的upload函数 upload(A.id)(注意这时候就要用id了,其他生成策略做不到)
3、upload会自动绘制上传按钮,点击上传,选择文件,上传到当前session中。(为了支持事务,并没有立即保存)
4、填写其他业务数据,点击保存。
5、service开启事务保存业务数据,保存session中的文件(写入磁盘、插入数据表——记录id和文件的关系)提交事务,关闭。
6、完成。
如果采用其他方式如identity,
问题1、如何判断是新建还是修改?用saveOrupdate?你的实体加入时间戳或版本控制了吗?用乐观锁了吗?如果不用,多发条select你如何避免?
问题2、文件上传时就要获得id,这个id该怎么来?有没有更优秀的方法。


总觉的assigned策略不太靠谱,因为在Hibernate In Action中他被列入了处理遗留数据库的章节。这是我一直没有弄明白的问题。大家议论下。
...全文
561 14 打赏 收藏 转发到动态 举报
写回复
用AI写文章
14 条回复
切换为时间正序
请发表友善的回复…
发表回复
xyz0101123132 2012-02-28
  • 打赏
  • 举报
回复
yami251139,其实基本解决了我以上两个问题。这两个问题其实不是什么问题,只是一时脑子转不过弯了。
ldh911,的回答是我最想要的结果。并且我就想以此为方向深入下去。
以上鸣谢两位大牛。
-----------------以下请议论:你选择了何种主键生成策略,以及为什么选择它?-------------------
MiceRice 2012-02-28
  • 打赏
  • 举报
回复
帖子质量很高啊,写得很认真。

不过没太看懂问题,感觉背景没介绍清楚,我觉得9楼也是类似的感觉。

分几个环节说下我判断的依据:
◎ 关于ID生成
—— 我一贯认为入手都是Assigned,也就是说我考虑的方式是:如果我自己来生成这个ID的话,最优选择是啥?然后再看看Hibernate之类的是否能提供我最优选择所期望的。
—— 是否集群环境?是第一个要考虑的因素,但基本上我都以集群环境为必要条件;
—— 然后是:超高并发生成?超高并发查询?ID是否需要存在语义?等问题。

◎ 不同的ID对我来说有啥区别?
—— UUID:极其适用于分布式计算环境(超越集群了),但ID将不能承载任何语义,高并发生成支持较好,超高并发查询存在数据库端不易优化的问题。
—— 数据库端seq:适用于集群环境,ID可承载某些语义(比如生成时间上较大范围的先后顺序),超高并发生成较搓,超高并发查询可做特定分区优化。

◎ 最终呢?
—— 最终基本上都选择UUID或其变体,因为有时候我还是需要ID承载少量语义的,比如我可能会关心这个UUID究竟是哪个终端生成的,你可能会说:可以换个字段保存啊?这类问题就智者见智仁者见仁了。


◎ 回到问题本身
—— 其实我确实没看懂你的问题,因为我没能想象出你的业务场景;
—— 常规业务场景中,如果是update,基本上就是带着原实体的ID进来的;如果没有带着原实体的ID进来,那么基本上就是new;所以确实没看懂问题,真是抱歉 =_=
xyz0101123132 2012-02-28
  • 打赏
  • 举报
回复
不知道你们公司是如何使用id生成策略的,用那一个?
xyz0101123132 2012-02-28
  • 打赏
  • 举报
回复
你们公司使用什么主键生成策略啊?
xyz0101123132 2012-02-28
  • 打赏
  • 举报
回复
写这么多,这么不火热,难道大家从来都没有考虑过这些东西吗?
yami251139 2012-02-27
  • 打赏
  • 举报
回复
新建修改同一个页面很简单,你修改总要有个可以选择的东西让人选了改吧,总是要改现有数据吧,那就是有id的。新建就是没id的
至于文件上传绑定到事物,你配置文件里面spring管理一下实现上传的类就over了,他会自动帮你处理的。
xyz0101123132 2012-02-27
  • 打赏
  • 举报
回复
[Quote=引用 6 楼 yami251139 的回复:]
看set不set id啊,根据id判断,没有id,hibernate自动认为你是save,有id,就是update
[/Quote]
你1楼不是让我不set id吗,set不set,我不管了,都由hibernate做,new xxx().getId()有值。
sessiong.get(xxx.class, id).getId,也有值,能判断吗?
笨拙,请明示
xyz0101123132 2012-02-27
  • 打赏
  • 举报
回复
[Quote=引用 5 楼 yami251139 的回复:]
我只想到一个方法,你上传的时候自己写个类似oracle sequence的自增小程序,位数自己定好,每拿一次自增,取消上传不返回,这样可以保证id的唯一性,又不插数据库,但是维护就比较麻烦了,程序一停,还要先看看跑到哪里了,然后继续从那开始。而且库里面的数据也会有断档。
我没弄明白什么样的需求要这么搞
[/Quote]
这个封装的upload.js完全可以重新写,可以不用在新建页面就传入id,但是其他什么时候才是正当的时机。
思维开阔点,目前就两个需求
1、新建和修改页面使用同一个jsp,可以判断是新建还是修改。
2、如何让文件上传绑定到具体实体中去,并且支持事务。
这个需求很合理吧(这句话让我想到了《少林足球》,踢足球那个扳手很合理吧,呵呵,玩笑。)
yami251139 2012-02-27
  • 打赏
  • 举报
回复
看set不set id啊,根据id判断,没有id,hibernate自动认为你是save,有id,就是update
yami251139 2012-02-27
  • 打赏
  • 举报
回复
我只想到一个方法,你上传的时候自己写个类似oracle sequence的自增小程序,位数自己定好,每拿一次自增,取消上传不返回,这样可以保证id的唯一性,又不插数据库,但是维护就比较麻烦了,程序一停,还要先看看跑到哪里了,然后继续从那开始。而且库里面的数据也会有断档。
我没弄明白什么样的需求要这么搞
xyz0101123132 2012-02-27
  • 打赏
  • 举报
回复
[Quote=引用 1 楼 yami251139 的回复:]
你都用了hibernate了。。。
自己操作一把不就ok了。。。。
你新建完一条数据,然后立去get这条数据的id,就可以得到他自动生成的id。
A a=new A();
a.setName("");//id别set
...
Template.save(a);
//这个时候新建了一条数据然后
a.getId();
能立刻拿到新建数据的id(具体什么id要看你生成方式)
[/Quote]
忘了说一句你如何判断是 save 还是 update?
xyz0101123132 2012-02-27
  • 打赏
  • 举报
回复
[Quote=引用 1 楼 yami251139 的回复:]
你都用了hibernate了。。。
自己操作一把不就ok了。。。。
你新建完一条数据,然后立去get这条数据的id,就可以得到他自动生成的id。
A a=new A();
a.setName("");//id别set
...
Template.save(a);
//这个时候新建了一条数据然后
a.getId();
能立刻拿到新建数据的id(具体什么id要看你生成方式)
[/Quote]
我是要去action->service->new xxx()然后返回个id
然后转向到jsp页面,就得到了我要的id,不管发不发sql,这样是比较麻烦的。
xyz0101123132 2012-02-27
  • 打赏
  • 举报
回复
目前只能发100分得帖子,如果回答的好,另开帖散分。
yami251139 2012-02-27
  • 打赏
  • 举报
回复
你都用了hibernate了。。。
自己操作一把不就ok了。。。。
你新建完一条数据,然后立去get这条数据的id,就可以得到他自动生成的id。
A a=new A();
a.setName("");//id别set
...
Template.save(a);
//这个时候新建了一条数据然后
a.getId();
能立刻拿到新建数据的id(具体什么id要看你生成方式)

67,513

社区成员

发帖
与我相关
我的任务
社区描述
J2EE只是Java企业应用。我们需要一个跨J2SE/WEB/EJB的微容器,保护我们的业务核心组件(中间件),以延续它的生命力,而不是依赖J2SE/J2EE版本。
社区管理员
  • Java EE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧