【问题标题】:Querying for @Embeddable objects stored in a separate collection查询存储在单独集合中的 @Embeddable 对象
【发布时间】:2019-12-02 06:13:50
【问题描述】:

我有一个简单的关系,其中一个实体有许多特定于它的地址,定义为:

@Entity
public class Corporation {

    @Id
    private Long id;

    @ElementCollection
    @CollectionTable(name = "addresses_table", joinColumns = @JoinColumn(name = "corporation_id"))
    private List<Address> addresses = new ArrayList<>();
}

Address 类使用@Embeddable 进行注释。这很有效,因为公司的每次更新都会删除其所有地址,然后插入新地址。这正是我正在寻找的行为。我尝试过的其他选项(OneToManyManyToMany)会导致性能下降,因为我需要跳过障碍,但仍然没有得到简单的 delete all + insert all 行为。

但是,有一个简单的要求,即我需要能够按某些条件查询地址。基本上这归结为一个简单的findAll(Pageable pageable, Specification spec) 方法。这对于当前和未来的用例来说已经足够了。

现在问题来了,可嵌入对象不是Entitys,因此我无法为它们创建 Spring 数据存储库。我能想到的唯一选择是:

  1. 使用本机实体管理器实现自定义存储库,但我不确定如何在代码方面以最佳方式执行此操作,以及它是否可以支持通用 Specification。如果没有,我仍然可以忍受,因为搜索地址的字段不会改变。

  2. select sth from Corporation c join c.addresses 那样做一些连接查询,然后根据地址属性限制结果。在这里,我再次不确定这是否可行并且与直接对地址表进行简单排队一样高效

任何建议都将不胜感激,无论是关于所描述的选项还是其他替代方案。

【问题讨论】:

    标签: hibernate jpa spring-data-jpa spring-data


    【解决方案1】:

    如果在您的项目中可以接受使用本机查询,IMO 对您来说这将是最简单和最高效的方式:

    public interface CorporationRepo extends JpaRepository<Corporation, Long> {
        @Query(value = "select a.city as city, a.street as street from addresses_table a where a.corporation_id = ?1", nativeQuery = true)
        List<AddressProjection> getAddressesByCorpId(Long corpId);
    }
    

    这里我假设Address 是这样的:

    @Data
    @Embeddable
    public class Address implements Serializable {
        private String city;
        private String street;
    
        @Tolerate
        public Address(String city, String street) {
            this.city = city;
            this.street = street;
        }
    }
    

    AddressProjectionProjection,如下所示:

    public interface AddressProjection {
        String getCity();
        String getStreet();
    
        default Address toAddress() {
            return new Address(getCity(), getStreet());
        }
    }
    

    (请注意,在带有投影的选择查询中使用别名(即a.city as city)是强制性的。)

    然后你就可以消费地址了:

    corporationRepo.getAddressesByCorpId(corpId)
        .stream()
        .map(AddressProjection::toAddress)
        .forEach(System.out::println);
    

    如果您不能使用本机查询(我不知道为什么),您可以使用第二个选项 - 带有连接的查询:

    public interface CorporationRepo extends JpaRepo<Corporation, Long> {
    
        @Query(value = "select a.city as city, a.street as street from addresses_table a where a.corporation_id = ?1", nativeQuery = true)
        List<AddressProjection> getAddressesByCorpId(Long corpId);
    
        @Query("select a from Corporation c join c.addresses a where c.id = ?1")
        List<Address> readAddressesByCorpId(Long corpId);
    }
    
    corporationRepo.readAddressesByCorpId(corpId).forEach(System.out::println);
    

    它会产生这样一个查询:

    select 
      a.city as col0, 
      a.street as col1 
    from 
      corporation c 
      inner join addresses_table a on c.id=a.corporation_id 
    where 
      c.id=1
    

    当然,它不像第一个那样是最优的,但也不是很糟糕,因为它只有一个索引字段的“连接”。

    您的第一个选项将与第二个选项相同,因为使用规范您将获得相同的“加入”查询。

    【讨论】:

      【解决方案2】:

      一个表可以映射到不同的类。那么为什么不创建另一个Address 类,它是一个通常的@Entity 类,这样你就可以为它创建一个存储库并使用你想要的Specification使用。

      @Embeddable Address 可以被认为是Corporation 的内部类,用于提供删除所有+插入所有行为。如果您希望域客户端只处理一个Address 类,您可以简单地在@Embeddable 地址和@Entity 地址之间进行转换。

      代码明智的看起来像:

      @Entity
      public class Corporation {
      
          @Id
          private Long id;
      
          @ElementCollection
          @CollectionTable(name = "addresses_table", joinColumns = @JoinColumn(name = "corporation_id"))
          private List<CorporationAddress> addresses = new ArrayList<>();
      
      
          public void addAddress(Address address){
             addresses.add(new CorporationAddress(address));
          }
      
          public List<Address> getAddresses(){
             return addresses.stream()
                  .map(CorporationAddress::toAddress).collect(toList());
          }
      
      }
      
      
      //Or you can put it as the internal static nested class inside Corporation if you like
      @Embeddable
      public class CorporationAddress {
      
          //Create from Address
          public CorporationAddress(Address){
          }
      
          //Convert to Address
          public Address toAddress(){
      
          }
      
      }
      
      @Entity
      public class Address {
      
      
      } 
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-11-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多