您的位置:首页 > 其它

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 里面有很多实用性很强的文档
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: