【问题标题】:Spring Data JPARepository: How to conditionally fetch children entitesSpring Data JPA Repository:如何有条件地获取子实体
【发布时间】:2016-01-20 21:37:43
【问题描述】:

除非提供了某个执行参数,否则如何配置他们的 JPA 实体以不获取相关实体。

根据 Spring 的文档 4.3.9. Configuring Fetch- and LoadGraphs,您需要使用 @EntityGraph 注释来指定查询的获取策略,但这并不能让我在运行时决定是否要加载这些实体。

我可以在单独的查询中获取子实体,但为了做到这一点,我需要将我的存储库或实体配置为不检索任何子实体。不幸的是,我似乎找不到任何关于如何做到这一点的策略。 FetchPolicy 被忽略,EntityGraph 仅在指定我想要急切检索的实体时才有用。

例如,假设Account 是父级,Contact 是子级,并且一个帐户可以有多个联系人。

我希望能够做到这一点:

if(fetchPolicy.contains("contacts")){
  account.setContacts(contactRepository.findByAccountId(account.getAccountId());
}

问题是 spring-data 无论如何都急切地获取联系人。

Account Entity 类如下所示:

@Entity
@Table(name = "accounts")
public class Account
{
    protected String accountId;
    protected Collection<Contact> contacts;

    @OneToMany
    //@OneToMany(fetch=FetchType.LAZY) --> doesn't work, Spring Repositories ignore this
    @JoinColumn(name="account_id", referencedColumnName="account_id")
    public Collection<Contact> getContacts()
    {
        return contacts;
    }

    //getters & setters

}

AccountRepository 类如下所示:

public interface AccountRepository extends JpaRepository<Account, String>
{
    //@EntityGraph ... <-- has type= LOAD or FETCH, but neither can help me prevent retrieval
    Account findOne(String id);
}

【问题讨论】:

  • JPA 中的集合默认是惰性的,Spring Data JPA 对此没有任何改变。如果在您的代码中某处调用了getContacts,那么所有内容都将被获取,因为这是默认设置。

标签: java spring hibernate spring-mvc jpa


【解决方案1】:

如果没有调用 getContacts() 产生的对象方法,则延迟获取应该正常工作。

如果您喜欢更多的手动工作,并且真的希望对此进行控制(可能更多的上下文取决于用例)。我建议您从帐户实体中删除联系人,然后将帐户映射到联系人中。告诉 hibernate 忽略该字段的一种方法是使用 @Transient 注释对其进行映射。

@Entity
@Table(name = "accounts")
public class Account
{
    protected String accountId;
    protected Collection<Contact> contacts;

    @Transient
    public Collection<Contact> getContacts()
    {
        return contacts;
    }

    //getters & setters

}

然后在您的服务类中,您可以执行以下操作:

public Account getAccountById(int accountId, Set<String> fetchPolicy) {
    Account account = accountRepository.findOne(accountId);
    if(fetchPolicy.contains("contacts")){
        account.setContacts(contactRepository.findByAccountId(account.getAccountId());
    }
    return account;
}

希望这是您正在寻找的。顺便说一句,代码未经测试,所以您可能应该再次检查。

【讨论】:

    【解决方案2】:

    您可以为此使用@Transactional

    为此,您需要获取您的帐户实体 Lazily。

    @Transactional注解应该放在所有不可分割的操作周围。

    在您的服务层中写入方法,该方法接受一个标志以急切地获取联系人。

    @Transactional
    public Account getAccount(String id, boolean fetchEagerly){
        Account account = accountRepository.findOne(id);
    
        //If you want to fetch contact then send fetchEagerly as true
        if(fetchEagerly){
            //Here fetching contacts eagerly
            Object object = account.getContacts().size();   
        }
    }
    

    @Transactional 是一个可以在单个事务中进行多次调用的服务 不关闭与端点的连接。

    希望你觉得这很有用。 :)

    更多详情refer this link

    【讨论】:

      【解决方案3】:

      请找到一个使用 JPA 2.1 运行的示例。

      设置你只想加载的属性(带有attributeNodes列表):

      带有实体图注释的实体:

      @Entity
      @NamedEntityGraph(name = "accountGraph", attributeNodes = { 
        @NamedAttributeNode("accountId")})
      @Table(name = "accounts")
      public class Account {
      
          protected String accountId;
          protected Collection<Contact> contacts;
      
          @OneToMany(fetch=FetchType.LAZY)
          @JoinColumn(name="account_id", referencedColumnName="account_id")
          public Collection<Contact> getContacts()
          {
              return contacts;
          }
      }
      

      您的自定义界面:

      public interface AccountRepository extends JpaRepository<Account, String> {
      
          @EntityGraph("accountGraph")
          Account findOne(String id);
      }
      

      只有“accountId”属性会被急切加载。所有其他属性将在访问时延迟加载。

      【讨论】:

      • 感谢您花时间回答。在我的示例中,我不希望在联系人上填充任何属性。当我调用“accountRepository.findOne(5)”时,我希望存储库返回没有任何联系人的帐户实体。
      • 不客气。您没有很多机制来控制 JPA 实体中加载或不加载的内容。你可以使用 EAGER 或 LAZY fetching,但你已经知道了。使用 DTO 应该是一种解决方法(例如,CustomAccount 是一个 Account 包装器)。
      • 这里 EntityGraph 是静态绑定的。有没有办法在运行时绑定它,以便我们可以根据需要传递不同的EntityGraph?
      • 这里是多个 NamedAttributeNode 和/或多个 NamedEntityGraphs docs.oracle.com/javaee/7/tutorial/… 的语法链接,如果链接失效,请在互联网上搜索此字符串:“多个 (at)NamedEntityGraph 定义可以通过对它们进行分组来应用于一个类在(at)NamedEntityGraphs 注释中。”将 (at) 替换为 at 符号(现在 SOF cmets 中允许使用 at 符号)
      • @AndréBlaszczyk 嘿。我实现了这种方法,但我仍然得到了完整的对象水合。我发布了一个问题。如果你有机会看看它。谢谢stackoverflow.com/questions/62162186/…
      【解决方案4】:

      Spring 数据不会忽略fetch=FetchType.Lazy

      我的问题是我使用dozer-mapping 将我的实体转换为图表。显然dozer 调用 getter 和 setter 来映射两个对象,所以我需要添加一个自定义字段映射器配置来忽略 PersistentCollections...

      GlobalCustomFieldMapper.java:

      public class GlobalCustomFieldMapper implements CustomFieldMapper 
      {
          public boolean mapField(Object source, Object destination, Object sourceFieldValue, ClassMap classMap, FieldMap fieldMapping) 
          {
             if (!(sourceFieldValue instanceof PersistentCollection)) {
                  // Allow dozer to map as normal
                  return;
              }
              if (((PersistentCollectiosourceFieldValue).wasInitialized()) {
                  // Allow dozer to map as normal
                  return false;
              }
      
              // Set destination to null, and tell dozer that the field is mapped
              destination = null;
              return true;
          }   
      }
      

      【讨论】:

        【解决方案5】:

        如果您尝试将实体的结果集发送给客户端,我建议您使用数据传输对象 (DTO) 而不是实体。您可以直接在 HQL/JPQL 中创建 DTO。 例如

        "select new com.test.MyTableDto(my.id, my.name) from MyTable my"
        

        如果你想通过孩子

        "select new com.test.MyTableDto(my.id, my.name, my.child) from MyTable my"
        

        这样您就可以完全控制正在创建的内容并将其传递给客户端。

        【讨论】:

        • Caused by: java.sql.SQLSyntaxErrorException: malformed numeric constant: . in statement 传递 my.child。
        猜你喜欢
        • 2017-07-25
        • 2020-05-16
        • 1970-01-01
        • 2019-09-30
        • 2022-01-07
        • 1970-01-01
        • 1970-01-01
        • 2018-12-03
        • 2020-08-19
        相关资源
        最近更新 更多