简体   繁体   中英

Spring Data JPARepository: How to conditionally fetch children entites

How can one configure their JPA Entities to not fetch related entities unless a certain execution parameter is provided.

According to Spring's documentation, 4.3.9. Configuring Fetch- and LoadGraphs , you need to use the @EntityGraph annotation to specify fetch policy for queries, however this doesn't let me decide at runtime whether I want to load those entities.

I'm okay with getting the child entities in a separate query, but in order to do that I would need to configure my repository or entities to not retrieve any children. Unfortunately, I cannot seem to find any strategies on how to do this. FetchPolicy is ignored, and EntityGraph is only helpful when specifying which entities I want to eagerly retrieve.

For example, assume Account is the parent and Contact is the child, and an Account can have many Contacts.

I want to be able to do this:

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

The problem is spring-data eagerly fetches the contacts anyways.

The Account Entity class looks like this:

@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

}

The AccountRepository class looks like this:

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

The lazy fetch should be working properly if no methods of object resulted from the getContacts() is called.

If you prefer more manual work, and really want to have control over this (maybe more contexts depending on the use case). I would suggest you to remove contacts from the account entity, and maps the account in the contacts instead. One way to tell hibernate to ignore that field is to map it using the @Transient annotation.

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

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

    //getters & setters

}

Then in your service class, you could do something like:

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

Hope this is what you are looking for. Btw, the code is untested, so you should probably check again.

You can use @Transactional for that.

For that you need to fetch you account entity Lazily.

@Transactional Annotations should be placed around all operations that are inseparable.

Write method in your service layer which is accepting one flag to fetch contacts eagerly.

@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 is a Service that can make multiple call in single transaction without closing connection with end point.

Hope you find this useful. :)

For more details refer this link

Please find an example which runs with JPA 2.1.

Set the attribute(s) you only want to load (with attributeNodes list) :

Your entity with Entity graph annotations :

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

Your custom interface :

public interface AccountRepository extends JpaRepository<Account, String> {

    @EntityGraph("accountGraph")
    Account findOne(String id);
}

Only the "accountId" property will be loaded eagerly. All others properties will be loaded lazily on access.

Spring data does not ignore fetch=FetchType.Lazy .

My problem was that I was using dozer-mapping to covert my entities to graphs. Evidently dozer calls the getters and setters to map two objects, so I needed to add a custom field mapper configuration to ignore 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;
    }   
}

If you are trying to send the resultset of your entities to a client, I recommend you use data transfer objects(DTO) instead of the entities. You can directly create a DTO within the HQL/JPQL. For example

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

and if you want to pass the child

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

That way you have a full control of what is being created and passed to client.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM