JPA 或 Hibernate 生成(非主键)列值,不是从 1 开始

Posted

技术标签:

【中文标题】JPA 或 Hibernate 生成(非主键)列值,不是从 1 开始【英文标题】:JPA or Hibernate to generate a (non primary key) column value, not starting from 1 【发布时间】:2015-06-25 07:59:30 【问题描述】:

我想要一个 JPA/Hibernate(最好是 JPA)注释,它可以生成列的值,它不是主键,也不是从 1 开始。

据我所见,JPA 无法使用 @GeneratedValue 和 @SequenceGenerator 和 @TableGenerator 做到这一点。或者其他任何东西。

我看到solution 有一个额外的桌子,我觉得这不太优雅。

我可以接受 Hibernate 注释,因为我已经有了 Hibernate 注释。

我想使用@Generated,但我无法让它工作,人们claim 认为这是可能的。

@Generated(GenerationTime.INSERT)
private long invoiceNumber;//invoice number

更新:一个额外的要求,如果事务回滚,我们不能在编号上有间隙。 有人吗?

【问题讨论】:

说“我做不到”并不是很有帮助。测试用例是什么?当您执行测试用例代码时会发生什么,您期望会发生什么?您如何配置数据库以使其在插入时生成值? 首先,我找不到指定初始值的地方。第二个只有这个注释我得到一个错误:java.sql.SQLException: Field 'invoiceNumber' doesn't have a default value 你读过 Generated 的 javadoc 吗?它说此注释用于表示“注释的属性是由数据库生成的”。所以 Hibernate 不会生成。由你来配置一个数据库触发器,它会在表中插入一行时生成这个值,并且由于注解,Hibernate 会在插入后读取这个值,以便实体具有数据库生成的值。 【参考方案1】:

与@Vlad Mihalcea 相关,现在您可以使用@GeneratorType 为非id 列生成您自己的自定义值。例如:

    实体:
    import org.hibernate.annotations.GeneratorType

    @GeneratorType(type = CustomGenerator.class, when = GenerationTime.INSERT)
    @Column(name = "CUSTOM_COLUMN", unique = true, nullable = false, updatable = false, lenght = 64)
    private String custom;
    ValueGenerator 实现:
public class CustomGenerator extends ValueGenerator<String> 
        private static final String TODAY_EXAMPLE_QUERY = "from Example where createDate>:start and createDate<:end order by createDate desc";
        private static final String START_PARAMETER = "start";
        private static final String END_PARAMETER = "end";
        private static final String NEXTVAL_QUERY = "select EXAMPLE_SEQ.nextval from dual";
        private final SimpleDateFormat dataFormat = new SimpleDateFormat("yyyyMMdd");

        @Override
        public String generateValue(Session session, Object owner) 
            Date now = new Date();
            Query<Example> todayQuery = session.createQuery(TODAY_EXAMPLE_QUERY, Example.class);
            query.setParameter(START_PARAMETER, start(now));
            query.setParameter(END_PARAMETER, end(now));
            Example lastExample = todayQuery.setMaxResult(1).setHibernateFlushMode(COMMIT).uniqueResult();

            NativeQuery nextvalQuery = session.createSQLQuery(NEXTVAL_QUERY);
            Number nextvalValue = nextvalQuery.setFlushMode(COMMIT).uniqueResult();
            return dataFormat.format(now) + someParameter(lastExample) + nextvalValue.longValue();
        
    

【讨论】:

【参考方案2】:

这对我有用 - 我们在服务中编写了所有代码。 这是实体:

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class Registrant extends AbstractEntity 
    //....
    private long invoiceNumber;//invoice number

    @Entity
    public static class InvoiceNumberGenerator 
        @Id
        @GeneratedValue
        private int id;
        private long counter;

        public int getId() 
            return id;
        

        public void setId(int id) 
            this.id = id;
        

        public long getCounter() 
            return counter;
        

        public void setCounter(long counter) 
            this.counter = counter;
        
    

然后我们就有了一个可以施展魔法的服务(实际上并没有什么魔法,一切都是手动完成的):

public synchronized Registrant save(Registrant registrant) 
    long counter = getInvoiceNumber();
    registrant.setInvoiceNumber(counter);

    return registrantRepository.save(registrant);


private long getInvoiceNumber() 
    //mist: get the invoice number from the other table
    long count = registrantInvoiceNumberGeneratorRepository.count();
    if(count > 1) 
        throw new RuntimeException(": InvoiceNumberGenerator table has more than one row. Fix that");
    

    Registrant.InvoiceNumberGenerator generator;
    if(count == 0) 
        generator = new Registrant.InvoiceNumberGenerator();
        generator.setCounter(1000001);
        generator = registrantInvoiceNumberGeneratorRepository.save(generator);
     else 
        generator = registrantInvoiceNumberGeneratorRepository.findFirstByOrderByIdAsc();
    


    long counter = generator.getCounter();
    generator.setCounter(counter+1);
    registrantInvoiceNumberGeneratorRepository.save(generator);
    return counter;

注意synchronized 方法 - 这样没有人可以得到相同的号码。

我不敢相信没有什么自动的可以做到这一点。

【讨论】:

这只有在当前事务边界包含在同步块中时才能解决。如果synchronized 方法被TransactionInterceptor 包装,那么在您释放服务对象锁和事务提交之间有一个时间窗口,这意味着您可以让其他事务更新相同的计数器值。 没有TransactionInterceptors,我想不出比synchronized 块更好的方法 您需要确保此方法上或堆栈上没有事务注释,并以编程方式在同步块内移动事务边界。或者使用悲观锁定,因为同步只在一个 JVM 节点上工作。 如果有@Transaction会发生什么。我知道synchronized 仅适用于单个 jvm,但这是我们目前的情况。【参考方案3】:

@GeneratedValue 仅适用于标识符,因此您不能使用它。如果你使用 mysql,你会受到很大的限制,因为不支持数据库序列。

InnoDB doesn't support multiple AUTO_INCREMENT columns 如果您的表 PK 是 AUTO_INCREMENTED,那么您有两个选择:

    寻找一个单独的表,其行为类似于序列生成器,您已经说过您不满意的解决方案。

    使用INSERT TRIGGER 来增加该列。

【讨论】:

感谢您的回复。触发器不是一个选项,因为我们想要一个自动生成的数据库模式,而不需要额外的代码。额外的表也不是一个选项,因为如果我们有回滚,增量将跳过一个数字,这是不允许的。 PostgreSQL 更加灵活。您可以在任何列上都有序列。 好吧,不幸的是,这不是我在这个项目中的选择。很糟糕,hibernate 在这方面也失败了。

以上是关于JPA 或 Hibernate 生成(非主键)列值,不是从 1 开始的主要内容,如果未能解决你的问题,请参考以下文章

hibernate主键生成

@OneToMany 与非主键 Hibernate 的关系

如何设置在 Java JPA/Hibernate 中不是自动生成的 @Id 主键?

JPA中的主键生成策略

Hibernate一方对多方非主键的关联

使用JPA和Hibernate提供程序的ManyToMany关系不会创建主键