33

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);
}
cosbor11
  • 14,709
  • 10
  • 54
  • 69
  • 1
    Collections in JPA are lazy by default, Spring Data JPA doesn't change anything on that. If there is somewhere a call to `getContacts` in your code then everything will be fetched as this is the default. – M. Deinum Oct 29 '15 at 09:58

5 Answers5

15

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.

Rowanto
  • 2,819
  • 3
  • 18
  • 26
11

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

Community
  • 1
  • 1
Harshal Patil
  • 6,659
  • 8
  • 41
  • 57
8

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.

buræquete
  • 14,226
  • 4
  • 44
  • 89
André Blaszczyk
  • 750
  • 4
  • 17
  • Thanks for taking the time to answer. In my example, I don't want any attributes populated on the contact. When I call 'accountRepository.findOne(5)', I want the repository to return the account entity with out any contacts. – cosbor11 Oct 24 '15 at 17:22
  • 1
    You're welcome. You don’t have many mechanisms to control what is loaded or not in a JPA Entity. You could use EAGER or LAZY fetching, but you already know that. Using a DTO should be a workaround (CustomAccount for instance which is a Account wrapper). – André Blaszczyk Oct 24 '15 at 17:56
  • Here the EntityGraph is statically bound. Is there any way to bind it at runtime so as that we can pass different EntityGraph as per the requirement? – Yogesh Jul 20 '18 at 13:13
  • Here is link for syntax for multiple NamedAttributeNode and/or multiple NamedEntityGraphs https://docs.oracle.com/javaee/7/tutorial/persistence-entitygraphs002.htm if link dies, search internet for this string : "Multiple (at)NamedEntityGraph definitions may be applied to a class by grouping them within a (at)NamedEntityGraphs annotation." replace (at) with the at sign (at sign now allowed in SOF comments) – granadaCoder Jun 02 '20 at 21:23
  • @AndréBlaszczyk Hey. I implemented this approach, but I still get full object hydration. I posted a question . if you get a chance to look at it. Thanks https://stackoverflow.com/questions/62162186/why-does-spring-jpa-ignore-javax-persistence-fetchtype-lazy-and-javax-persistenc – granadaCoder Jun 02 '20 at 22:16
2

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;
    }   
}
cosbor11
  • 14,709
  • 10
  • 54
  • 69
0

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.

leventgo
  • 109
  • 1
  • 1
  • 5
  • 1
    `Caused by: java.sql.SQLSyntaxErrorException: malformed numeric constant: . in statement` on passing my.child. – BALAJI RAJ Aug 28 '20 at 14:21