4、JPA table主键生成策略(在JPA中table策略是首推!!!)
2015-11-29 15:46
519 查看
用 table 来生成主键详解
它是在不影响性能情况下,通用性最强的 JPA 主键生成器。这种方法生成主键的策略可以适用于任何数据库,不必担心不同数据库不兼容造成的问题。initialValue不起作用?
Hibernate 从 3.2.3 之后引入了两个新的主键生成器 TableGenerator 和 SequenceStyleGenerator。为了保持与旧版本的兼容,这两个新主键生成器在默认情况下不会被启用,而不启用新 TableGenerator 的 Hibernate 在提供 JPA 的 @TableGenerator 注解时会有 Bug。这个bug是什么呢?我们将上一节中的Customer.java的getId方法做如下下 List_1 的修改:
List_1. Id的生成策略为TABLE
@TableGenerator(name="ID_GENERATOR", table="t_id_generator", pkColumnName="PK_NAME", pkColumnValue="seed_t_customer_id", valueColumnName="PK_VALUE", allocationSize=20, initialValue=10 ) @GeneratedValue(strategy=GenerationType.TABLE, generator="ID_GENERATOR") @Id public Integer getId() { return id; }
上面的@TableGenerator配置指定了initialValue=10,指定了主键生成列的初始值为10,这在 @TableGenerator 的 API 文档中写得很清楚。现在 initialValue 值设置为 10, 那么在单元测试中用 JPA 添加新的 Customer 记录时,新记录的主键会从 11 开始。但是,实际上保存到数据库中的主键值确实1 !!!
也就是说,在@TableGenerator中配置的initialValue根本不起作用!!!
这实在令人困惑。其实问题出在程序所用的 JPA 提供者(Hibernate)上面。如果改用其他 JPA 提供者,估计不会出现上面的问题(未验证)。Hibernate 之所以会出现这种情况,并非是不尊重标准,而有它自身的原因。现在,为了把问题讲清楚, 有必要先谈谈 JPA 主键生成器选型的问题,了解一下 @TableGenerator 在 JPA 中的特殊地位。
JPA 主键生成器选型
JPA 提供了四种主键生成器,参看表 1:package com.magicode.jpa.helloworld; import java.util.Date; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.EntityTransaction; import javax.persistence.Persistence; public class Main { /** * 测试: * 3次EntityManagerFactory生命周期,3次EntityManager * 生命周期。id分配上面会出现空洞。 */ public static void main(String[] args) { // 注意for的位置 for(int i = 0; i < 3; i++){ /* * 1、获取EntityManagerFactory实例 * 利用Persistence类的静态方法,结合persistence.xml中 * persistence-unit标签的name属性值得到 */ EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpa-1"); // 2、获取EntityManager实例 EntityManager em = emf.createEntityManager(); // 3、开启事物 EntityTransaction transaction = em.getTransaction(); transaction.begin(); // 4、调用EntityManager的persist方法完成持久化过程 //保存第1条记录 Customer customer = new Customer(); customer.setAge(9); customer.setEmail("Tom@163.com"); customer.setLastName("Tom"); customer.setBirthday(new Date()); customer.setCreatedTime(new Date()); em.persist(customer); //保存第2条记录 customer = new Customer(); customer.setAge(10); customer.setEmail("Jerry@163.com"); customer.setLastName("Jerry"); customer.setBirthday(new Date()); customer.setCreatedTime(new Date()); em.persist(customer); // 5、提交事物 transaction.commit(); // 6、关闭EntityManager em.close(); // 7、关闭EntityManagerFactory emf.close(); } } }
3次EntityManagerFactory,3次EntityManager:会出现id空洞
删除上次运行以后的数据表。执行List_7,得到运行后的状态如下:
Figure_8. 出现了id空洞,id号分为三段
Figure_8. 辅助表的状态,说明辅助表生成之后更新了两次
结果讨论如下:
①、从List_6和List_7的运行状态可以印证id空洞是出现在不同EntityManagerFactory生命周期中的,而不是出现在EntityManager中的。也就是说,辅助表的读取优化是在EntityManagerFactory这个层面上完成的。
②、同时也印证了上面阐述的理论,第一个id分配是从 initialValue + 1 开始的;辅助表记录了下下一个段的first_id(依据不同的策略也可能是下一个段的first_id);
③、每一次EntityManagerFactory生命周期中,当第一次用到某个辅助表的时候,首先会检测指定的辅助表是否存在。如果存在,则读取first_id,同时更新辅助表的数据;如果不存在,则会创建一个辅助表,同时在辅助表中存放数值 initialValue + 1 + locationSize * 2 。
综合上面的介绍,理一下TABLE id生成的过程(重要,上面这么多东西就为了理解这个结论):
1、JPA有4中id的生成策略。TABLE策略只是其中的一种,由于其通用性和对算法的优化,这种策略成为JPA id生成策略中的最优选择。2、TABLE策略的思想是这样的:
①、TABLE将整数分成若干个段segment;
②、专门用一张数据表(称为“辅助表”)来存放“下一个segment,或者是下下一segment”起始编号,我把它称为first_id;
③、initialValue设置一个初始值,实际上也就是指定了第一个segment的first_id为 initialValue + 1 ;
④、allocationSize设置了segment的长度。
⑤、假如initialValue=10,allocationSize=20,那么会有两个结论, a. 最小的一个id号就是11; b. 分段情况为 11~30,31~50,51~70 ...等;
⑥、如果当前正在使用的是11~30这个id段,那么辅助表中存放的数值会是12、31或51。
保存12的情况:在allocationSize<=1的情况下JPA的实现Hibernate不使用任何的id生成优化策略,辅助表中记录的就是下一个要生成的id号。这样,每次生成id都会访问辅助表,极大的降低了效率;
保存31的情况:设置了hibernate.id.optimizer.pooled.prefer_lo为true,hibernate使用pooled-lo优化器,辅助表中存放的数值是下一个id段的起始值;
保存51的情况:没有设置hibernate.id.optimizer.pooled.prefer_lo为true的时候,hibernate使用pooled优化器,辅助表中保存的数值是下下一个id段的起始值;
⑦、生成id号的时候,每个segment长度仅仅需要访问一次辅助表,极大的降低了访问辅助表的次数:每次生成id号的时候都是在first_id(如,11,31,51...)的基础之上递增得到的。只有当前段内的id号分配完了,才会再一次访问辅助表得到新的first_id,开始新的一轮分配。
⑧、要想分得上述优化红利,则必须在persistence.xml中配置<property name="hibernate.id.new_generator_mappings" value="true"/>使用新的TableGenerator类来实现@TableGenerator注解。也只有使用了该配置,initialValue属性也才会发挥作用。
⑨、所以,allocationSize实际上是一个容量参数,是优化器的优化参数。另外,在不同的EntityManagerFactory生命周期中,持久化对象的id会出现空洞现象。但是,没有关系,我们应该接受这种空洞现象;
注:主要参考IBM文档库中的一篇博文 “探索 Hibernate 新 TableGenerator 机制”
博文地址为:http://www.ibm.com/developerworks/cn/java/j-lo-tablegenerator/
另外:IBM文档库 http://www.ibm.com/developerworks/cn/views/java/libraryview.jsp 里面有很多实用性很强的文档
相关文章推荐
- 经典的机器学习方面源代码库
- 打印100~200 之间的素数
- BLE4.0数据传输过程跟踪
- Python读写Excel自动填表
- vim背景色molokai配置
- HTTP压缩
- plist读写,NSArray,NSData,NSnumber,字典等简使用
- 读《50 Android Hacks》笔记整理Hack 18~Hack 23
- js 中变量是可以保存数据,也可以保存地址
- pat 1024. Palindromic Number (25)
- SJTU-ACM-1530
- Android 各种按钮点击效果以及兼容性问题总结
- 为您的创业公司推荐5款类Slack开源协作工具
- 80x86浮点数和整数的内部结构
- 奇怪的高精度
- 【PA2014】【BZOJ3712】Fiolki
- 什么时候需要使用display:block;属性?
- Jedis下的ShardedJedis(分布式)使用方法(一)
- android框架搭建——二次封装Volley框架
- Struts2基础复习系列(3)