Java Spring JPA/Hibernate复合密钥在PrePersist、定制生成器或服务层生成?

Java Spring JPA/Hibernate复合密钥在PrePersist、定制生成器或服务层生成?,java,spring,hibernate,spring-mvc,jpa,Java,Spring,Hibernate,Spring Mvc,Jpa,我有一个遗留数据库,我正在用JPA/Hibernate开发一个SpringMVC应用程序。我的问题来自复合主键的生成。主键示例如下所示: 序列号、年份、订单ID、行ID LineId将根据序列、年份和LineId的每个元组的最大值(LineId)生成 我考虑过以下方法: PrePersist Listener:这意味着Listener必须访问存储库,甚至可能引用其他实体才能获取下一个id。编辑:Hibernate文档说:回调方法不得调用EntityManager或查询方法 自定义生成器:我还

我有一个遗留数据库,我正在用JPA/Hibernate开发一个SpringMVC应用程序。我的问题来自复合主键的生成。主键示例如下所示:

序列号、年份、订单ID、行ID

LineId将根据序列、年份和LineId的每个元组的最大值(LineId)生成

我考虑过以下方法:


  • PrePersist Listener:这意味着Listener必须访问存储库,甚至可能引用其他实体才能获取下一个id。编辑:Hibernate文档说:
    回调方法不得调用EntityManager或查询方法
    
  • 自定义生成器:我还没有找到一个示例显示如何访问实体实例以检索进行正确选择所需的属性
  • 服务层:太冗长了
  • 覆盖Spring数据的JPA Repository save()方法实现:在这种情况下,我们可以访问实体的实例属性


实现这一目标的正确方法是什么?感谢

为了支持这一点,我经常使用一种领域驱动的设计技术,在我将
订单行
订单
关联时,我可以控制这一点

public class Order {
  private List<OrderLine> lines;

  // don't allow the external world to modify the lines collection.
  // forces them to use domain driven API exposed below.
  public List<OrderLine> getLines() {
    return Collections.unmodifiableList( lines );
  }

  // avoid allowing external sources to set the lines collection
  // Hibernate can set this despite the method being private.
  private void setLines(List<OrderLine> lines) {
    this.lines = lines;
  }

  public OrderLine addLine(String serial, Integer year) {
    final OrderLine line = new OrderLine( this, serial, year );
    lines.add( line );
    return line;
  }

  public void removeLine(Integer lineId) {
    lines.removeIf( l -> l.getId().getLineId().equals( lineId ) );
  }
}

public OrderLine {
  public OrderLine() {
  }

  OrderLine(Order order, String serial, Integer year) {
    this.id = new OrderLineId( order.getLines().size() + 1, serial, year, order.getId() );
  }
}
公共类秩序{
私有列表行;
//不允许外部世界修改lines集合。
//强制他们使用下面公开的域驱动API。
公共列表getLines(){
返回集合。不可修改列表(行);
}
//避免允许外部源设置行集合
//尽管方法是私有的,Hibernate仍可以设置此选项。
专用无效设置行(列表行){
这条线=线;
}
公共订单行addLine(字符串序列,整数年){
最终订单行=新订单行(本,系列,年份);
行。添加(行);
回流线;
}
公共void removeLine(整数lineId){
lines.removeIf(l->l.getId().getLineId().equals(lineId));
}
}
公共秩序线{
公共秩序线(){
}
订单行(订单、字符串序列、整数年){
this.id=neworderlineid(order.getLines().size()+1,serial,year,order.getId());
}
}
唯一调用特殊的
OrderLine
构造函数的代码是从
Order
调用的,您确保始终通过聚合根
Order
委托添加和删除
OrderLine
实体

public class Order {
  private List<OrderLine> lines;

  // don't allow the external world to modify the lines collection.
  // forces them to use domain driven API exposed below.
  public List<OrderLine> getLines() {
    return Collections.unmodifiableList( lines );
  }

  // avoid allowing external sources to set the lines collection
  // Hibernate can set this despite the method being private.
  private void setLines(List<OrderLine> lines) {
    this.lines = lines;
  }

  public OrderLine addLine(String serial, Integer year) {
    final OrderLine line = new OrderLine( this, serial, year );
    lines.add( line );
    return line;
  }

  public void removeLine(Integer lineId) {
    lines.removeIf( l -> l.getId().getLineId().equals( lineId ) );
  }
}

public OrderLine {
  public OrderLine() {
  }

  OrderLine(Order order, String serial, Integer year) {
    this.id = new OrderLineId( order.getLines().size() + 1, serial, year, order.getId() );
  }
}

这也意味着您只需要公开一个
订单
存储库,并且只通过
订单
操作与
订单
关联的行,而不直接操作。

如果每次保存都通过此方法,我建议使用存储库上的save方法。您只需要强制执行,在存储实体时,此方法在任何地方都可以使用。这就是DAO/repository模式的威力——它使您可以修改entityManger的标准行为以满足您的需要。使用复合主键是一个非常糟糕的主意,因此理想情况下,您可以返回并向遗留数据库添加一个ID列,并将当前复合键改为唯一索引。不幸的是,这个世界通常离理想还很远,但是您当前的用例不在JPA直接支持的JPA特性的范围之内。如果其他应用程序正在向表中插入数据,则没有好的解决方案。如果您的应用程序是数据库的唯一客户机,那么我将使用工厂来创建实体(带有id),并像表/序列生成器那样预分配id。