Spring Data JPA Repository:如何有条件地获取子实体

Posted

技术标签:

【中文标题】Spring Data JPA Repository:如何有条件地获取子实体【英文标题】:Spring Data JPARepository: How to conditionally fetch children entites 【发布时间】: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,那么所有内容都将被获取,因为这是默认设置。 【参考方案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 嘿。我实现了这种方法,但我仍然得到了完整的对象水合。我发布了一个问题。如果你有机会看看它。谢谢***.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。

以上是关于Spring Data JPA Repository:如何有条件地获取子实体的主要内容,如果未能解决你的问题,请参考以下文章

spring-data-jpa 和 spring-boot-starter-data-jpa 的区别

spring-data详解之spring-data-jpa:简单三步快速上手spring-data-jpa开发

spring data jpa怎么和solr整合

spring data jpa问题

Spring Data 系列 Spring+JPA(spring-data-commons)

spring-data-jpa软删除方案