Hibernate中如何启用新TableGenerator

本文从这个 Bug 开始,讲述了如何启用新 TableGenerator,并演示了新 TableGenerator 的完整用法。

问题的发现源自对 JPA 中 TableGenerator 的测试。测试的环境有这样几个条件:

为方便查询的测试,Employee 表格在初始化时会导入部分记录,这部分记录的主键在初始脚本中手动写好,比如 1、2、3、4。(参看文章所附示例代码中的 import_data.sql 文件)。 Employee 实体使用 TableGenerator 主键生成器,initialValue 的值设置为 10。 在单元测试中添加新的 Employee 记录。

Employee 实体类的代码参看清单 1:

清单 1. Employee 实体类

@Entity @Table(">name="emp3") public class Employee3 { @TableGenerator(name="id_gen",table="id_gen",initialValue=10) @Id @GeneratedValue(strategy=TABLE,generator="id_gen") private long id; private String firstName; private String lastName; ...... }

@TableGenerator 的配置参数 initialValue 指的是主键生成列的初始值,这在 @TableGenerator 的 API 文档中写得很清楚。现在 initialValue 值设置为 10, 那么在单元测试中用 JPA 添加新的 Employee 记录时,新记录的主键会从 11 开始,不会与已有的数据发生冲突(参看文章所附示例代码中 src/java/test/sample/case3/OldInitialValue.java)。执行的结果出乎意料,测试报错,说是主键重复。错误信息如清单 2 所示:

清单 2. 主键重复错误信息

11:23:40,220 ERROR SqlExceptionHelper:144 - Duplicate entry '1' for key 'PRIMARY'

这实在令人困惑。如果 initialValue 的含义不是初始值,那还能是什么呢?

问题其实出在程序所用的 JPA 提供者(Hibernate)上面。如果改用其他 JPA 提供者,估计不会出现上面的问题(未验证)。Hibernate 之所以会出现这种情况,并非无知,也不是不尊重标准,而有它自身的原因,这在文章后面会提到。现在,为了把问题讲清楚, 有必要先谈谈 JPA 主键生成器选型的问题,了解一下 @TableGenerator 在 JPA 中的特殊地位。

JPA 主键生成器选型

JPA 提供了四种主键生成器,参看表 1:

表 1. JPA 的四种主键生成器

生成器名称 描述 AUTO 由 JPA 提供者根据数据库自行决定生成算法。 IDENTITY 由数据库的自增列提供主键值。 SEQUENCE 由数据库 Sequence 对象提供主键值。 TABLE 由 JPA 提供者通过创建数据库表来记录生成的主键值。

一般来说,支持 IDENTITY 的数据库,如 MySQL、SQL Server、DB2 等,AUTO 的效果与 IDENTITY 相同。IDENTITY 主键生成器最大的特点是:在表中插入记录以后主键才会生成。这意味着,实体对象只有在保存到数据库以后,才能得到主键值。用 EntityManager 的 persist 方法来保存实体时必须在数据库中插入纪录,这种主键生成机制大大限制了 JPA 提供者优化性能的可能性。在 Hibernate 中通过设置 FlushMode 为 MANUAL,可以将记录的插入延迟到长事务提交时再执行,从而减少对数据库的访问频率。实施这种系统性能提升方案的前提就是不能使用 IDENTITY 主键生成器。

SEQUENCE 主键生成器主要用在 PostgreSQL、Oracle 等自带 Sequence 对象的数据库管理系统中,它每次从数据库 Sequence 对象中取出一段数值分配给新生成的实体对象,实体对象在写入数据库之前就会分配到相应的主键。

上面的分析中,我们把现实世界中的关系数据库分成了两大类:一是支持 IDENTITY 的数据库,二是支持 SEQUENCE 的数据库。对支持 IDENTITY 的数据库来说,使用 JPA 时变得有点麻烦:出于性能考虑,它们在选用主键生成策略时应当避免使用 IDENTITY 和 AUTO,同时,他们不支持 SEQUENCE。看起来,四个主键生成器里面排除了三个,剩下唯一的选择就是 TABLE。由此可见,TABLE 主键生成机制在 JPA 中地位特殊。它是在不影响性能情况下,通用性最强的 JPA 主键生成器。

TableGenerator 有新旧之分?

JPA 的 @TableGenerator 只是通用的注解,具体的功能要由 JPA 提供者来实现。Hibernate 中实现该注解的类有两个,一是原有的 TableGenerator,类名为 org.hibernate.id.TableGenerator,这是默认的 TableGenerator。二是新 TableGenerator,指的是 org.hibernate.id.enhanced.TableGenerator。当用 Hibernate 来提供 JPA 时,需要通过配置参数指定使用何种 TableGenerator 来提供相应功能。

在 4.1 版本的 Hibernate Reference Manual 关于配置参数的章节中(网址可从参考资源中找到)可以找到如下说明:

我们建议所有使用 @GeneratedValue 的新工程都配置 hibernate.id.new_generator_mappings=true 。因为新的生成器更加高效,也更符合 JPA2 的规范。不过,要是已经使用了 table 或 sequence 生成器,新生成器与之不相兼容。

还可以再参考一下 HHH-4884 和 HHH-4690 ,里面有 Hibernate 开发人员对这些问题的看法。

综合这些资源,可以得到如下结论:

如果不配置 hibernate.id.new_generator_mappings=true,使用 Hibernate 来提供 TableGenerator 时,JPA 中 @TableGenerator 注解的 initialValue 参数是无效的。 Hibernate 开发人员原本希望用新 TableGenerator 替换掉原有的 TableGenerator,但这么做会导致已经使用旧 TableGenerator 的 Hibernate 工程在升级 Hibernate 后,新生成的主键值可能会与原有的主键冲突,导致不可预料的结果。为保持兼容,Hibernate 默认情况下使用旧 TableGenerator 机制。 没有历史负担的新 Hibernate 工程都应该使用 hibernate.id.new_generator_mappings=true 配置选项。

现在回到清单 1 所示的问题,要解决这个问题只需在 persistence.xml 文件中添加如下一行配置即可:

清单 3. 添加新的配置行

<property name="hibernate.id.new_generator_mappings" value="true"/>

使用新 TableGenerator 后就可以放心地在 JPA 中使用 initialValue 参数了,不过,这只是新 TableGenerator 的一个好处,我们接下来还可以看看新 TableGenerator 带来的更多用法。

时间: 2024-12-21 00:06:08

Hibernate中如何启用新TableGenerator的相关文章

Hibernate中新的TableGenerator 机制

从 initialValue 说起 问题的发现源自对 JPA 中 TableGenerator 的测试.测试的环境有这样几个条件: 为方便查询的测试,Employee 表格在初始化时会导入部分记录,这部分记录的主键在初始脚本中手动写好,比如 1.2.3.4.(参看文章所附示例代码中的import_data.sql 文件). Employee 实体使用 TableGenerator 主键生成器,initialValue 的值设置为 10. 在单元测试中添加新的 Employee 记录. Emplo

ssh框架中如何为oracle中对应的表创建hibernate序列,以达到新加主键自增的功能

问题描述 ssh框架中如何为oracle中对应的表创建hibernate序列,以达到新加主键自增的功能 解决方案 .hbm 文件 <id name="id" type="java.lang.String"> <column name="ID" length="32" /> <generator class="uuid.hex" /> </id>策略不一样<

Hibernate中的Cache管理

Hibernate实现了良好的Cache机制,可以借助Hibernate内部的Cache迅速提高系统的数据读取性能. Hibernate中的Cache可分为两层:一级Cache和二级Cache. 一级Cache: Session实现了第一级Hibernate Cache,它属于事务级数据缓冲.一旦事务结束,这个Cache也随之失 效.一个Session的生命周期对应一个数据库事务或一个程序事务. Session-cache保证了一个Session中两次请求同一个对象时,取得的对象是同一个JAVA

Hibernate中Session的缓存及对象的状态

对于session这个接口的学习可以说是最痛苦也是最复杂的,因为它所涉及的方面太多了,一些隐藏的机制也很多,谁让它是Central API呢. 对于它的几个最基本的方法如save().delete().flush()等的学习都花了我一定的时间.在深入了解这些这些方法前,了解session的缓存机制以及Hibernate中Java对象的状态对我们是很有帮助的. 一.Session的缓存 Java是纯面向对象的语言,因此不可能像C语言那样直接操纵内存,例如声明一段可用的内存空间.在Java里面,缓存

在Hibernate中正确实现关联关系中的级联操作(cascading)

关系数据库系统本身就比较复杂,加上Hibernate的O/R映射层,复杂度加重了,很容易出现问题,本人将最近遇到的问题和解决方法做一个总结,整理在下面的一系列文章中 正确理解Hibernate的聚合类型(collection)的使用 在Hibernate中正确实现关联关系中的级联操作(cascading) 在Hibernate框架中编写持久对象类实现外键关联的几点注意事项 本文是第二篇,讲解在one-to-many(一对多)和many-to-one(多对一)关联关系中的cascade特性的声明方

mysql数据库表中插入一行新纪录时,调用一个java程序

问题描述 mysql数据库表中插入一行新纪录时,想要通知调用一个java程序,在网上查了下,说可以使用触发器,监听数据表中的变化,但是触发器器能直接调用外部的java程序吗,如果可以,该怎么做.或者说除了触发器以外,有好的其他方法,有想过去轮训数据库,但经常访问数据库效率太差了,所以想看看有没有其他的实现方法 解决方案 解决方案二:oracle的应该可以,你参考一下这两个链接:mysql的即使能实现,估计也很难:解决方案三:引用1楼u012724379的回复: oracle的应该可以,你参考一下

数据库-hibernate中怎么把字符串转成数字类型?

问题描述 hibernate中怎么把字符串转成数字类型? 以前用的都是mybatis,到了新的公司有项目用到了hibernate,问题是: 数据库表里的积分字段是varchar(2),对应的po类的积分字段是String类型的, 可是业务逻辑是要求按照积分来排序的,积分一般是数字,字符串的话,是不能显示正确结果的.想改表结构和po类是不行了,影响太大.怎么在查询的时候将字符串转成数字呢? 解决方案 String hql = "select CAST(字段 as integer) from 表&q

阿里巴巴中国站升级为在线采购批发大市场,启用新域名

昨日,阿里巴巴(01688.HK)宣布,原阿里巴巴中国站(alibaba.com.cn)即日起升级为在线采购批发大市场,启用新域名www.1688.com.据阿里巴巴披露,1688.com上线当天,首页批发客流量达639万,这也相当于全球最大小商品市场义乌小商品城1个月的人流量. 阿里巴巴CEO卫哲表示,1688平台不是新网站,而是阿里巴巴中国站的战略升级,真正实现从MeetAtAlibaba(相识在阿里巴巴)到WorkAtAlibaba(工作在阿里巴巴). 在此前中国乃至全球电子商务的布局里,

Hibernate中的session的save方法。

问题描述 Hibernate中的session的save方法. 我正在学习hibernate框架,然后在做一个插入的时候遇到了一个问题.通过跟踪我发现在执行session.save(obj);方法时,程序就不动了.并且在获取session时还打印出下面红色的信息. 请问一个是什么原因以及怎么解决呢? public int insertClient(Client client) { // TODO Auto-generated method stub //HibernateUtil.closeSe