Spring Data JPARepository:如何有条件地获取子进程

时间:2015-10-21 18:48:00

标签: java spring hibernate spring-mvc jpa

除非提供某个执行参数,否则如何配置其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无论如何急切地获取联系人。

帐户实体类如下所示:

@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);
}

5 个答案:

答案 0 :(得分:11)

如果没有调用getContacts()导致的对象方法,则lazy fetch应该正常工作。

如果您更喜欢手动工作,并且真的想要控制它(可能更多的上下文取决于用例)。我建议您从帐户实体中删除联系人,然后在联系人中映射帐户。告诉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;
}

希望这就是你要找的东西。顺便说一下,代码未经测试,所以你应该再次检查。

答案 1 :(得分:9)

您可以使用@Transactional

为此,您需要懒惰地获取帐户实体。

@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

答案 2 :(得分:6)

请查找使用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”属性。所有其他属性将在访问时延迟加载。

答案 3 :(得分:2)

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;
    }   
}

答案 4 :(得分:0)

如果您尝试将实体的结果集发送给客户端,则建议您使用数据传输对象(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"

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