2

我将 Hibernate 与 Spring Boot 和 JPA 一起使用,并且有一个业务需求来检索并组合到存储在数据库中四个不同表中的单页响应数据中。

让我们称前两个表为“tblCredits”,包含 Credits,和“tblDebits”,包含 Debits。出于我们的目的,这两个表是相同的——相同的列名、相同的列类型、相同的 ID 字段,所有内容。我的端点应该能够返回贷方和借方的组合列表,能够通过返回的任何/所有字段进行搜索/排序,并具有分页功能。

如果我控制了该数据库,我只需将两个表合并到一个表中,或者创建一个视图或存储过程来为我执行此操作,但这是其他应用程序使用的遗留数据库,我无法在任何文件中修改方式,所以这不是一个选择。

如果我不需要排序和分页,我可以只创建两个完全独立的实体,为每个实体创建一个单独的 Spring Data JPA 存储库,分别查询这两个存储库,然后将结果组合到我自己的代码中。但是特别是对合并结果进行分页会变得非常麻烦,除非我绝对必须这样做,否则我不想自己实现合并的分页逻辑。理想情况下,我应该能够让 JPA 开箱即用地为我处理所有这些。

我已经能够使用声明为具有 InheritanceType.TABLE_PER_CLASS 的实体的抽象类来实现前两个表的第一步,如下所示:

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class AbstractCreditDebitEntity {
/* literally all my properties and ID and column mappings here 
...
*/
}

然后两个扩展该抽象实体并简单地指定两个不同表映射的具体类,根本没有特定于类的属性或列映射:

@Entity
@Table(name = "tblCredits")
public final class Credit extends AbstractCreditDebitEntity {
//Literally nothing inside this class
}

@Entity
@Table(name = "tblDebits")
public final class Debit extends AbstractCreditDebitEntity {
//Literally nothing inside this class
}

到目前为止一切都很好,这很好用,我能够在 AbstractCreditDebitEntity 实体上创建一个 Spring JPA 存储库,在这两个表上生成联合查询的引擎盖下,我能够从一个表中获取两个表的记录查询,具有适当的分页和排序。(关于联合查询的性能问题目前并不关心我。)

但是,当我合并另外两个表时,下一步我会被绊倒。tblCredits 与 tblCreditLineItems 具有一对多关系,而 tblDebits 与 tblDebitLineItems 具有一对多关系。同样,从我们的角度来看,tblCreditLineItems 和 tblDebitLineItems 是相同的表 - 相同的列名、相同的列类型、相同的 ID 字段,所有内容。

因此,对于这些子实体,我可以遵循与以前相同的模式:

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class AbstractCreditDebitLineItemEntity {
/* literally all my properties and ID and column mappings here 
...
*/
}
@Entity
@Table(name = "tblCreditLineItems")
public final class CreditLineItem extends AbstractCreditDebitLineItemEntity {
//Literally nothing inside this class
}

@Entity
@Table(name = "tblDebitLineItems")
public final class DebitLineItem extends AbstractCreditDebitLineItemEntity {
//Literally nothing inside this class
}

但现在我需要创建 Credit/Debit 实体和 CreditLineItem/DebitLineItem 实体之间的映射。这就是我挣扎的地方。因为我需要能够根据关联的 CreditLineItem/DebitLineItem 实体中的属性值过滤我返回的特定 Credit/Debit 实体,所以我需要两个实体之间的双向映射,但我一直无法让它工作成功地。

这是到目前为止我已经走了多远。首先是三个 Credit/Debit 实体,其 OneToMany 映射到其关联的 CreditLineItem/DebitLineItem 实体:

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class AbstractCreditDebitEntity {
/* literally all my properties and ID and column mappings here 
...
*/
  @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
  @JoinColumn(
      name = "MyIdColumnName",
      referencedColumnName = "MyIdColumnName"
  )
  public abstract List<AbstractCreditDebitLineItemEntity> getCreditDebitLineItems();

  public abstract void setCreditDebitLineItems(List<AbstractCreditDebitLineItemEntity> items);

}
@Entity
@Table(name = "tblCredits")
public final class Credit extends AbstractCreditDebitEntity {

  private List<CreditLineItem> creditDebitLineItems;

  @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, targetEntity = CreditLineItem.class)
  @JoinColumn(
      name = "MyIdColumnName",
      referencedColumnName = "MyIdColumnName"
  )
  @Override
  public List<AbstractCreditDebitLineItemEntity> getCreditDebitLineItems() {
    return Optional.ofNullable(creditDebitLineItems).stream()
        .flatMap(List::stream)
        .filter(value -> AbstractCreditDebitLineItemEntity.class.isAssignableFrom(value.getClass()))
        .map(AbstractCreditDebitLineItemEntity.class::cast)
        .collect(Collectors.toList());
  }

  @Override
  public void setCreditDebitLineItems(List<AbstractCreditDebitLineItemEntity> items) {
    creditDebitLineItems =  Optional.ofNullable(items).stream()
      .flatMap(List::stream)
      .filter(value -> CreditLineItem.class.isAssignableFrom(value.getClass()))
      .map(CreditLineItem.class::cast)
      .collect(Collectors.toList());
  }

}

@Entity
@Table(name = "tblDebits")
public final class Debit extends AbstractCreditDebitEntity {
  private List<DebitLineItem> creditDebitLineItems;

  @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, targetEntity = DebitLineItem.class)
  @JoinColumn(
      name = "MyIdColumnName",
      referencedColumnName = "MyIdColumnName"
  )
  @Override
  public List<AbstractCreditDebitLineItemEntity> getCreditDebitLineItems() {
    return Optional.ofNullable(creditDebitLineItems).stream()
        .flatMap(List::stream)
        .filter(value -> AbstractCreditDebitLineItemEntity.class.isAssignableFrom(value.getClass()))
        .map(AbstractCreditDebitLineItemEntity.class::cast)
        .collect(Collectors.toList());
  }

  @Override
  public void setCreditDebitLineItems(List<AbstractCreditDebitLineItemEntity> items) {
    creditDebitLineItems =  Optional.ofNullable(items).stream()
      .flatMap(List::stream)
      .filter(value -> DebitLineItem.class.isAssignableFrom(value.getClass()))
      .map(DebitLineItem.class::cast)
      .collect(Collectors.toList());

  }
}

然后三个 CreditLineItem/DebitLineItem 实体及其多对一映射返回到 Credit/Debit 实体:

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class AbstractCreditDebitLineItemEntity {
/* literally all my properties and ID and column mappings here 
...
*/
  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(
      name = "MyIdColumnName",
      referencedColumnName = "MyIdColumnName",
      updatable = false,
      insertable = false)
  public abstract AbstractCreditDebitEntity getCreditDebit();

  public abstract void setCreditDebit(AbstractCreditDebitEntity creditDebitEntity);
}
@Entity
@Table(name = "tblCreditLineItems")
public final class CreditLineItem extends AbstractCreditDebitLineItemEntity {

    private Credit creditDebit;

  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(
      name = "MyIdColumnName",
      referencedColumnName = "MyIdColumnName",
      updatable = false,
      insertable = false)
 @Override
  public Credit getCreditDebit() {
    return creditDebit;
  }

  @Override
  public void setCreditDebit(AbstractCreditDebitEntity creditDebitEntity) {
    creditDebit =
        Optional.ofNullable(creditDebitEntity)
            .filter(value -> Credit.class.isAssignableFrom(value.getClass()))
            .map(Credit.class::cast)
            .orElse(throw new RuntimeException());
  }
}

@Entity
@Table(name = "tblDebitLineItems")
public final class DebitLineItem extends AbstractCreditDebitLineItemEntity {
    private Debit creditDebit;

  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(
      name = "MyIdColumnName",
      referencedColumnName = "MyIdColumnName",
      updatable = false,
      insertable = false)
 @Override
  public Debit getCreditDebit() {
    return creditDebit;
  }

  @Override
  public void setCreditDebit(AbstractCreditDebitEntity creditDebitEntity) {
    creditDebit =
        Optional.ofNullable(creditDebitEntity)
            .filter(value -> Debit.class.isAssignableFrom(value.getClass()))
            .map(Debit.class::cast)
            .orElse(throw new RuntimeException());
  }
}

但是,此代码可以编译...当在我的自动化测试中我尝试保留我的 Credit 实体之一(我使用简单的 H2 数据库进行自动化测试)时,我收到以下错误:

2021-04-02 13:53:52 [main] DEBUG org.hibernate.SQL T: S: - update AbstractCreditDebitLineItemEntity set MyIdColumnName=? where ID=?
2021-04-02 13:53:52 [main] DEBUG o.h.e.jdbc.spi.SqlExceptionHelper T: S: - could not prepare statement [update AbstractCreditDebitLineItemEntity set MyIdColumnName=? where ID=?]
org.h2.jdbc.JdbcSQLSyntaxErrorException: Table "ABSTRACTCREDITDEBITLINEITEMENTITY" does not exist

它似乎试图基于从我的 AbstractCreditDebitEntity 类到我的 AbstractCreditDebitLineItemEntity 的@OneToMany 映射来坚持。其中,由于它是具有 InheritanceType.TABLE_PER_CLASS 的抽象类,因此没有为它指定表,因此它假定它需要持久保存到的表与该类具有相同的名称。

我想在这里发生的是 Credit 子类中具体 getter 上的 @OneToMany 映射,它将其 targetEntity 指定为具体 CreditLineItem.class,以从本质上覆盖/替换其父抽象类上的 @OneToMany 映射。但似乎具体类的映射被完全忽略了?

我可以从 AbstractCreditDebitEntity 类中完全删除 @OneToMany 映射,并且只在扩展它的两个具体 Credit/Debit 实体中定义该映射。这使得持久性错误消失,并且 90% 的测试用例通过了......但在这种情况下,当我尝试根据以下字段之一过滤或排序从组合 AbstractCreditDebitEntity Spring Data JPA 存储库返回的结果时CreditLineItem/DebitLineItem 子实体中存在,由于 AbstractCreditDebitEntity 不再具有到 AbstractCreditDebitLineItemEntity 的任何映射,查询失败。

有没有什么好的方法可以解决这个问题,使得从 AbstractCreditDebitEntity 到 AbstractCreditDebitLineItemEntity 的 OneToMany 映射仍然存在,但是 Credit 实体专门映射到 CreditLineItem 实体和 Debit 实体专门映射到 DebitLineItem 实体的知识也保持不变?

4

1 回答 1

0

经过大量的实验,我发现了一些对我有用的东西。

基本上,与其尝试用具体实体中的 OneToMany 映射来覆盖抽象实体类中的 OneToMany 映射,不如让它们完全独立地映射到完全不同的属性。这意味着我的具体实体有两个不同的 AbstractCreditDebitLineItemEntity 集合,并且一些 AbstractCreditDebitLineItemEntity 对象将在两个集合中出现两次。在内存/计算方面有点浪费,但我可以接受,它有效!

所以这就是我最终的结果:

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class AbstractCreditDebitEntity {
/* literally all my properties and ID and column mappings here 
...
*/

  private List<AbstractCreditDebitLineItemEntity> creditDebitLineItems;

  @OneToMany(fetch = FetchType.LAZY, targetEntity = AbstractCreditDebitLineItemEntity.class)
  @JoinColumn(
      name = "MyIdColumnName",
      referencedColumnName = "MyIdColumnName",
      updatable = false,
      insertable = false
  )
  public List<AbstractCreditDebitLineItemEntity> getCreditDebitLineItems() {
    return creditDebitLineItems;
  }

  public void setCreditDebitLineItems(List<AbstractCreditDebitLineItemEntity> items) {
    creditDebitLineItems = items;
  }

}
@Entity
@Table(name = "tblCredits")
public final class Credit extends AbstractCreditDebitEntity {

  private List<CreditLineItem> creditLineItems;

  @OneToMany(cascade = CascadeType.ALL, targetEntity = CreditLineItem.class)
  @LazyCollection(LazyCollectionOption.FALSE)
  @JoinColumn(
      name = "MyIdColumnName",
      referencedColumnName = "MyIdColumnName"
  )
  public List<CreditLineItem> getCreditLineItems() {
    return creditLineItems;
  }

  @Override
  public void setCreditDebitLineItems(List<CreditLineItem> items) {
    creditLineItems =  items;
  }

}

对借方实体重复完全相同的模式。

这使我能够:

  1. 持久化,使用从具体 Credit 和 Debit 实体到具体 CreditLineItem 和 DebitLineItem 实体的 OneToMany 映射;和

  2. 使用从该抽象实体到 AbstractCreditDebitLineItemEntity 的完全独立的 OneToMany 映射在 AbstractCreditDebitEntity 的 Spring Data JPA 存储库中查找。

不像我能够用具体子类中更具体的 OneToMany 映射覆盖抽象父类中的 OneToMany 映射那样干净......但正如我所说,它有效!

(关于这个问题的答案帮助我知道我需要用 @LazyCollection(LazyCollectionOption.FALSE) 替换具体 OneToMany 映射上的 fetchType=FetchType.EAGER: Hibernate throws MultipleBagFetchException - cannot 同时获取多个包

于 2021-04-05T19:02:33.550 回答