【问题标题】:JPA or Hibernate to generate a (non primary key) column value, not starting from 1JPA 或 Hibernate 生成(非主键)列值,不是从 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 会在插入后读取这个值,以便实体具有数据库生成的值。

标签: java mysql hibernate jpa auto-increment


【解决方案1】:

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

  1. 实体:
    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;
  1. 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,那么您有两个选择:

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

    2. 使用INSERT TRIGGER 来增加该列。

    【讨论】:

    • 感谢您的回复。触发器不是一个选项,因为我们想要一个自动生成的数据库模式,而不需要额外的代码。额外的表也不是一个选项,因为如果我们有回滚,增量将跳过一个数字,这是不允许的。
    • PostgreSQL 更加灵活。您可以在任何列上都有序列。
    • 好吧,不幸的是,这不是我在这个项目中的选择。很糟糕,hibernate 在这方面也失败了。
    猜你喜欢
    • 2019-10-15
    • 2015-11-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-27
    • 2019-11-06
    • 1970-01-01
    相关资源
    最近更新 更多