【问题标题】:How do you map two abstract InheritanceType.TABLE_PER_CLASS entities with a OneToMany relationship?如何将两个抽象 InheritanceType.TABLE_PER_CLASS 实体与 OneToMany 关系映射?
【发布时间】:2021-06-29 14:13:17
【问题描述】:

我将 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 实体及其 ManyToOne 映射回 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实体的知识也是维护?

【问题讨论】:

    标签: java hibernate orm spring-data-jpa table-per-class


    【解决方案1】:

    经过大量实验,我找到了适合我的东西。

    基本上,与其尝试用具体实体中的 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. persist,使用从具体 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 simultaneously fetch multiple bags)

    【讨论】:

      猜你喜欢
      • 2018-10-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-02-08
      • 2019-09-23
      • 1970-01-01
      相关资源
      最近更新 更多