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 开始的主要内容,如果未能解决你的问题,请参考以下文章