PostgreSQL串行类型的Hibernate注释

Adi*_*a R 7 java postgresql hibernate

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

我有一个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;
    }
  ....
....
Run Code Online (Sandbox Code Playgroud)

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

请帮忙.

Cra*_*ger 17

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

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

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

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

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

... 和更多.

您的应用程序将在发票编号中出现"漏洞",这些漏洞会发生.此外,没有顺序保证,所以这是完全有可能与后面的序列号的事务将更早(有时比具有后数更早).

分块:

对于某些应用程序(包括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在映射中定义单个s ,否则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;
Run Code Online (Sandbox Code Playgroud)

或者,您可以:

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

所有这些选项都将序列化您的事务 - 通过使用@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编号进行编号是不安全的除非您的JPA提供商提供比标准更多的保证,否则需要无间断或不在主键属性上的编号.

如果你坚持一个序列:

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

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

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

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

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

如果您很幸运,您的其他应用程序也将使用标准方法,即从INSERT列列表中省略序列列或明确指定关键字DEFAULT作为值,而不是调用nextval.这不会是很难找到了通过使log_statement = 'all'postgresql.conf和搜索日志.如果他们这样做,那么如果你决定需要通过替换你从计数器表设置DEFAULTBEFORE INSERT ... FOR EACH ROW触发器功能,你实际上可以将所有东西都切换到无间隙NEW.invoice_number.


Kei*_*thL 0

根据您的情况,这可能不起作用。有一个针对 Hibernate 的错误记录了这种行为。

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

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

<class name="Ticket" table="ticket_t">
    <id name="id" column="ticket_id">
        <generator class="identity"/>
    </id>
    <property name="customerName" column="customer_name"/>
    <property name="sequence" column="sequence" generated="always"/>
</class>
Run Code Online (Sandbox Code Playgroud)