PostgreSQL串行类型的Hibernate注释

我有一个PostgreSQL表,其中我有一个声明为serial的列inv_seq

我有一个Hibernate bean类来映射表。 除此列外,所有其他列都已正确读取。 这是Hibernate bean类中的声明:

 .... .... @GeneratedValue(strategy=javax.persistence.GenerationType.AUTO) @Column(name = "inv_seq") public Integer getInvoiceSeq() { return invoiceSeq; } public void setInvoiceSeq(Integer invoiceSeq) { this.invoiceSeq = invoiceSeq; } .... .... 

声明是否正确?
我能够看到数据库中列生成的序列号,但我无法在java类中访问它们。

请帮忙。

危险:您的问题意味着您可能会犯设计错误 – 您正在尝试使用数据库序列来呈现给用户的“业务”值,在这种情况下是发票编号。

如果您需要的不仅仅是测试值的相等性,请不要使用序列。 它没有订单。 它与另一个值没有“距离”。 它只是平等或不平等。

回滚:序列通常不适用于此类用途,因为序列的更改不会使用事务ROLLBACK 。 查看函数序列和CREATE SEQUENCE的页脚。

回滚是预期的和正常的。 他们发生的原因是:

  • 由更新顺序冲突或两个事务之间的其他锁定导致的死锁;
  • Hibernate中的乐观锁定回滚;
  • 暂时的客户错误;
  • DBA的服务器维护;
  • SERIALIZABLE或快照隔离事务中的SERIALIZABLE化冲突

… 和更多。

您的应用程序将在发票编号中出现“漏洞”,这些漏洞会发生。 此外,没有排序保证,因此具有较晚序列号的事务完全可能比具有较晚号码的事务更早(有时更早)提交。

分块:

对于某些应用程序(包括Hibernate)来说,一次从序列中获取多个值并将其交给内部事务也是正常的。 这是允许的,因为你不应该期望序列生成的值具有任何有意义的顺序或者除了相等之外可以以任何方式进行比较。 对于发票编号,您也需要订购,所以如果Hibernate获取值5900-5999并开始将其从5999倒计时或交替上下调,则您将不会感到高兴,因此您的发票编号为: n, n + 1,n + 49,n + 2,n + 48,… n + 50,n + 99,n + 51,n + 98,[n + 52丢失回滚],n + 97,.. …… 是的, Hibernate中存在高 – 低 – 低分配器 。

除非你在映射中定义单独的@SequenceGenerator ,否则Hibernate也喜欢为每个生成的ID共享一个序列,这没有任何帮助。 丑陋。

正确使用:

只有在您要求编号是唯一的时,序列才适用。 如果你还需要单调和序数,你应该考虑通过UPDATE ... RETURNINGSELECT ... FOR UPDATE (Hibernate中的“悲观锁定”)或通过Hibernate乐观锁定来使用带有计数器字段的普通表。 这样,您可以保证无间隙增量,无需空洞或无序输入。

该怎么做:

为计数器创建一个表。 在其中有一行,并在阅读时更新它。 这将锁定它,防止其他事务在您提交之前获取ID。

因为它会强制所有事务以串行方式运行,所以请尽量保留生成发票ID的事务,并避免在其中执行比您需要的更多工作。

 CREATE TABLE invoice_number ( last_invoice_number integer primary key ); -- PostgreSQL specific hack you can use to make -- really sure only one row ever exists CREATE UNIQUE INDEX there_can_be_only_one ON invoice_number( (1) ); -- Start the sequence so the first returned value is 1 INSERT INTO invoice_number(last_invoice_number) VALUES (0); -- To get a number; PostgreSQL specific but cleaner. -- Use as a native query from Hibernate. UPDATE invoice_number SET last_invoice_number = last_invoice_number + 1 RETURNING last_invoice_number; 

或者,您可以:

  • 为invoice_number定义一个实体,添加一个@Version列,让乐观锁定处理冲突;
  • 为invoice_number定义一个实体,并在Hibernate中使用显式悲观锁定来执行select …更新然后更新。

所有这些选项都将序列化您的事务 – 通过使用@Version回滚冲突,或阻止它们(锁定)直到锁定持有者提交。 无论哪种方式,无间隙序列都会真正减慢应用程序的这个区域,所以只有在必要时才使用无间隙序列。

@GenerationType.TABLE :使用@GenerationType.TABLE@TableGenerator(initialValue=1, ...)很有诱惑力。 不幸的是,虽然GenerationType.TABLE允许您通过@TableGenerator指定分配大小,但它不提供有关排序或回滚行为的任何保证。 请参阅JPA 2.0规范,第11.1.46节和第11.1.17节。 特别是“此规范没有定义这些策略的确切行为。脚注102 ”便携式应用程序不应在其他持久字段或属性上使用GeneratedValue注释[比@Id主键]“ 。因此使用@GenerationType.TABLE是不安全的@GenerationType.TABLE用于编号,您需要无间隙或编号不在主键属性上,除非您的JPA提供商提供比标准更多的保证。

如果你坚持一个序列

海报说明他们已经使用已经使用序列的数据库的现有应用程序,所以他们坚持使用它。

JPA标准不保证您可以使用生成的列,除了@Id之外,您可以(a)忽略它并继续,只要您的提供商允许您,或者(b)使用默认值执行插入并重新开始 – 从数据库中读取。 后者更安全:

  @Column(name = "inv_seq", insertable=false, updatable=false) public Integer getInvoiceSeq() { return invoiceSeq; } 

由于insertable=false ,提供程序不会尝试为列指定值。 您现在可以在数据库中设置合适的DEFAULT ,例如nextval('some_sequence') ,它将被尊重。 您可能必须在持久化之后使用EntityManager.refresh()从数据库中重新读取实体 – 我不确定持久性提供程序是否会为您执行此操作并且我没有检查规范或编写演示程序。

唯一的缺点是似乎无法使列成为@NotNull或nullable=false ,因为提供程序不理解数据库具有列的默认值。 它在数据库中仍然可以是NOT NULL

如果你很幸运,你的其他应用程序也将使用标准方法,或者省略INSERT列列表中的序列列,或者显式指定关键字DEFAULT作为值,而不是调用nextval 。 通过在postgresql.conf启用log_statement = 'all'并搜索日志,不难发现它。 如果他们这样做,那么你实际上可以将所有内容切换到无间隙,如果你决定需要将DEFAULT替换为BEFORE INSERT ... FOR EACH ROW触发器函数,该函数从计数器表设置NEW.invoice_number

我发现当你将它设置为AUTO时,hibernate 3.6会尝试对所有实体使用单个序列,所以在我的应用程序中我使用IDENTITY作为生成策略。

 @Id @Column(name="Id") @GeneratedValue(strategy=GenerationType.IDENTITY) private Integer id; 

@Craig对于需要递增的发票号码有一些非常好的观点,如果你将它们呈现给用户,他建议使用表格。 如果您最终使用表来存储下一个ID,则可以使用与此类似的映射。

 @Column(name="Id") @GeneratedValue(strategy=GenerationType.TABLE,generator="user_table_generator") @TableGenerator( name="user_table_generator", table="keys", schema="primarykeys", pkColumnName="key_name", pkColumnValue="xxx", valueColumnName="key_value", initialValue=1, allocationSize=1) private Integer id; 

根据您的具体情况,这可能无效。 针对Hibernate打开了一个记录此行为的错误。

http://opensource.atlassian.com/projects/hibernate/browse/HHH-4159

如果您打开使用映射文件而不是注释,我就能够重新创建问题(SERIAL列中的NULL不属于主键)。 使用property元素的“generated”属性会导致Hibernate在插入后重新读取行以获取生成的列值:

        

我在我的项目中使用Postgres + Hibernate,这就是我所做的:

 @Id @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "hibernate_sequence") @SequenceGenerator(name = "hibernate_sequence", sequenceName = "hibernate_sequence") @Column(name = "id", unique = true, nullable = false) protected Long id; public Long getId() { return id; } public void setId(Long id) { this.id = id; } 

它对我来说很好。