• Home
  • RSS Feed
  • Log in

JPA implementation patterns: Bidirectional associations vs. lazy loading
Posted by Vincent Partington at around evening time: May 25th, 2009

Two weeks ago I blogged about the use of the Service Facade and Data Transfer Object pattern in JPA application architecture. This week I will move from the high level perspective and discuss an interesting interaction I discovered between the way bidirectional associations are managed and lazy loading. So let's roll up our sleeves and get dirty in this next installation of the JPA implementation patterns series. ;-)

This blog assumes that you are familiar with the Order/OrderLine example I introduced in the first two blogs of this series. If you are not, please review the example.

Consider the following code:

OrderLine orderLineToRemove = orderLineDao.findById(30);
orderLineToRemove.setOrder(null);

The intention of this code is to unassociate the OrderLine with the Order it was previously associated with. You might imagine doing this prior to removing the OrderLine object (although you can also use the @PreRemove annotation to have this done automatically) or when you want to attach the OrderLine to a different Order entity.

If you run this code you will find that the following entities will be loaded:

  1. The OrderLine with id 30.
  2. The Order associated with the OrderLine. This happens because the OrderLine.setOrder method invokes the Order.internalRemoveOrderLine method to remove the OrderLine from its parent Order object.
  3. All the other OrderLines that are associated with that Order! The Order.orderLines set is loaded when the OrderLine object with id 30 is removed from it.


Depending on the JPA provider, this might take two to three queries. Hibernate uses three queries; one for each of the lines mentioned here. OpenJPA only needs two queries because it loads the Order with the OrderLine using an outer join.

However the interesting thing here is that all OrderLine entities are loaded. If there are a lot of OrderLines this can be a very expensive operation. Because this happens even when you are not interested in the actual contents of that collection, you are be paying a high price for keeping the bidirectional associations intact.

So far I have discovered three different solutions to this problem:

  1. Don't make the association bidirectional; only keep the reference from the child object to the parent object. In this case that would mean removing the orderLines set in the Order object. To retrieve the OrderLines that go with an Order you would invoke the findOrderLinesByOrder method on the OrderLineDao. Since that would retrieve all the child objects and we got into this problem because there are a lot of those, you would need to write more specific queries to find the subset of child objects you need. A disadvantage of this approach is that it means an Order can't access its OrderLines without having to go through a service layer (a problem we will address in a later blog) or getting them passed in by a calling method.
  2. Use the Hibernate specific @LazyCollection annotation to cause a collection to be loaded "extra lazily" like so:
    @OneToMany(mappedBy = "order", cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
    @org.hibernate.annotations.LazyCollection(org.hibernate.annotations.LazyCollectionOption.EXTRA)
    public Set<OrderLine> orderLines = new HashSet<OrderLine>();

    This feature should cause Hibernate to be able to handle very large collections. For example, when you request the size of the set, Hibernate won't load all the elements of the collections. Instead it will execute a SELECT COUNT(*) FROM ... query. But even more interestingly: modifications to the collection are queued instead of being directly applied. If any modifications are pending when the collection is accessed, the session is flushed before further work is done.

    While this works fine for the size() method, it doesn't work when you try and iterate over the elements of the set (see JIRA issue HHH-2087 which has been open for two and a half years). The extra lazy loading of the size also has at least two open bugs: HHH-1491 and HHH-3319. All this leads me to believe the extra lazy loading feature of Hibernate is a nice idea but not fully mature (yet?).

  3. Inspired by the Hibernate mechanism of postponing operations on the collection until you really need them to be executed, I have modified the Order class to do something similar. First an operation queue has been added as a transient field to the Order class:
    private transient Queue<Runnable> queuedOperations = new LinkedList<Runnable>();

    Then the internalAddOrderLine and internalRemoveOrderLine methods have been changed so that they do not directly modify the orderLines set. Instead they create an instance of the appropriate subclass of the QueuedOperation class. That instance is initialized with the OrderLine object to add or remove and then placed on the queuesOperations queue:

    public void internalAddOrderLine(final OrderLine line) {
    	queuedOperations.offer(new Runnable() {
    		public void run() { orderLines.add(line); }
    	});
    }
     
    public void internalRemoveOrderLine(final OrderLine line) {
    	queuedOperations.offer(new Runnable() {
    		public void run() { orderLines.remove(line); }
    	});
    }

    Finally the getOrderLines method is changed so that it executes any queued operations before returning the set:

    public Set<? extends OrderLine> getOrderLines() {
    	executeQueuedOperations();
    	return Collections.unmodifiableSet(orderLines);
    }
     
    private void executeQueuedOperations() {
    	for (;;) {
    		Runnable op = queuedOperations.poll();
    		if (op == null)
    			break;
    		op.run();
    	}
    }

    If there were more methods that need the set to be fully up to date, they would invoke the executeQueuedOperations method in a similar manner.

    The downside here is that your domain objects get cluttered with even more "link management code" than we already had managing bidirectional associations. Abstracting out this logic to a separate class is left as an exercise for the reader. ;-)

Of course this problem not only occurs when you have bidirectional associations. It surfaces any time you are manipulating large collections mapped with @OneToMany or @ManyToMany. Bidirectional associations just makes the cause less obvious because you think you are only manipulating a single entity.

Addendum d.d. May 31st, 2009: You should not use the method described above at bullet #3 if you are cascading any operations to the mapped collection. If you postpone modifications to the collections your JPA provider won't know about the added or removed elements and will cascade operations to the wrong entities. This means that any entities added to a collection on which you have set @CascadeType.PERSIST won't be persisted unless you explicitly invoke EntityManager.persist on them. On a similar note the Hibernate specific @org.hibernate.annotations.CascadeType.DELETE_ORPHAN annotation will only remove orphaned child entities when they are actually removed from Hibernate's PersistentCollection.

In any case, now you know what causes this performance hit and three possible ways to solve it. I am interested to hear whether you ran into this problem and how you solved it.

For a list of all the JPA implementation pattern blogs, please refer to the JPA implementation patterns wrap-up.

  • Share/Bookmark

Filed under Hibernate, JPA, JPA implementation patterns, Java | 7 Comments »



7 Responses to “JPA implementation patterns: Bidirectional associations vs. lazy loading”



    JPA Implementation Patterns « Fernando Franzini Java Blog Says:
    Posted at: July 16, 2009 at 1:14 pm

    [...] Bidirectional associations vs. lazy loading [...]



    Andrew Phillips Says:
    Posted at: July 24, 2009 at 9:33 am

    A thought on 3 (”postponing operations on the collection”): wouldn’t you want to move the “interception” into the collection itself? The suggestion is essentially proposal for how to process collection operations, so you’d presumably want that in a collection class, rather than in the DAO itself.

    But there are some gotchas, like ensuring any decorating collections are created only after the field injection of the ORM-managed fields has taken place. I guess @PostConstruct could be used for that, but that’s a bit ugly. Or you could use double checked locking.

    So something like

    class Entity {
      Collection persistedCollection;
      transient Collection lateLoadingPersistedCollection;
    
      @PostConstruct
      void createLateLoadingCollections() {
        lateLoadingPersistedCollection = new LateLoadingCollection(persistedCollection);
      }
    
      Collection getPersistedCollection() {
        return lateLoadingPersistedCollection;
      }
    
    }
    

    or

    class Entity {
      Collection persistedCollection;
    
      // let's pretend closures made it to Java 7
      transient LazyField lateLoadingPersistedCollection =
        new LazyField({ new LateLoadingCollection(persistedCollection) });
    
      Collection getPersistedCollection() {
        assert persistedCollection != null;
        return lateLoadingPersistedCollection.get();
      }
    
    }
    

    where LazyField is just some wrapper around some double-checked locking lazy instantiation code.

    Because both examples rely on returning the decorated collection from the getter, this presumably wouldn’t work with property access, by the way (but I guess that wouldn’t bother you).

    The LateLoadingCollection itself would just be a decorator around the real, ORM-managed collection that would intercept the add and remove methods (storing the values in a queue or whatever) and would simply merge and then delegate to the underlying set in all other cases, which would result in a load whenever e.g. the iterator was called.

    A key question here would be whether there is some guarantee that get (or any of the other methods that would result in a merge of any queued changes) would be called when an entity is persisted. Otherwise, changes to the underlying persistedCollection might not be saved, of course…



    Vincent Partington Says:
    Posted at: July 24, 2009 at 12:21 pm

    @Andrew: Interesting idea to abstract the queued operation into a separate set implementation.

    One remark about the last paragraph where you mention that the queued operations need to be propagated to the underlying set before the transaction is committed: this is not necessary when the set is not the owning side of a bidirectional relation (i.e.. when the set has the mappedBy field set on the @OneToMany annotation). And in most cases that is so.

    In any case, you’d rather not do this last-minute propagation because initializating the set is expensive (the reason we are bothering with this pattern anyway).



    M Says:
    Posted at: August 18, 2009 at 9:29 pm

    Hello,

    As I am new to jpa, please forgive the simplicity of the question. I have the following problem:

    How can I read a collection of strings from a child table that references the parent table by a FK ? The database is legacy and I can not change it. This seems likes such a basic operation and it frustrates me that I do not know how to do it in JPA.

    Thank You,
    MPK



    Vincent Partington Says:
    Posted at: August 24, 2009 at 8:09 pm

    @M: Are you saying that the child table only has two columns? The first a foreign key to the parent and the second column the string value? And the primary key is the combination of that foreign key and the string value?

    In that case one way to do it is to define the child as a separate entity with a composite primary key defined with the @IdClass annotation.

    Is that what you are looking for? If not, please post the definition of your schema.



    Bruno Says:
    Posted at: October 28, 2009 at 6:55 pm

    I was very excited to find this post because I have not found this issue discussed thoroughly elsewhere. The issue of large collections and inadvertent lazy-loading is one that has plagued me for years. In fact, we have been using TopLink and invented a framework to accomplish exactly the “exercise for the reader” you put forth. But this framework imposes a fair amount of boilerplate code into the entity classes and I’ve been fighting with bugs in the implementation for years (only some of which I introduced myself :-) ).

    After all of this time, I have finally come to the simple conclusion that using one-to-many relationships for large collections is a just a bad idea. Even if you are to get around the issues with lazy-loading, you still face the problem of the memory usage for the collection. We use EclipseLink’s “SoftWeak” caching strategy in general. But when one object holds a collection to hundreds or thousands of other entities that otherwise have only a weak reference, garbage collection is severely hampered. I basically draw the line at around 100 objects. Any relationship that would involve more than 100 objects is not a candidate for a collection mapping. This is not a hard and fast rule, but I have found that the cost of the extra DAO and service layer coding outweighs the hassles of dealing with these performance issues.



    Vincent Partington Says:
    Posted at: October 31, 2009 at 10:19 am

    @Bruno: I wholeheartedly agree with your rule of thumb. It is a lot harder to get the performance of large collections mapped as one-to-many relations right than if you were to retrieve them using a query (solution #1). So if the set becomes large, it is safer to go the explicit-query route.

    One problem exists though: because it is so easy to set up @OneToMany associations and it makes traversing the object graph easier, you are likely to end up with @OneToMany associations where you don’t want them later on. In fact, what might be small set of data when you start a project, might end up becoming a large set when actual data flows through the program. So the safest thing of all would be to avoid @OneToMany associations altogether. Hmm…



Leave a Reply

Click here to cancel reply.

Deployment automation for Java application running on Websphere, WebLogic and JBoss

Archives

  • January 2010
  • December 2009
  • November 2009
  • October 2009
  • September 2009
  • August 2009
  • July 2009
  • June 2009
  • May 2009
  • April 2009
  • March 2009
  • February 2009

Xebia Sites

  • Xebia Corporate
  • Xebia France
  • Xebia India

Categories

  • Java (279)
  • Agile (109)
  • General (50)
  • Testing (42)
  • Performance (42)
  • Hibernate (36)
  • Scrum (33)
  • Podcast (31)
  • Architecture (31)
  • Spring (28)
  • SOA (24)
  • Maven (22)
  • Project Management (22)
  • Flex (17)
  • JPA (17)
    • JPA implementation patterns (13)
  • Eclipse (15)
  • Quality Assurance (14)
  • Middleware (19)
  • Frameworks (13)

Tag Cloud

    Scala Agile Maven Java Introduction to Agile Functional Programming Spring Seam esb Lean IntelliJ Grails SOA XML Scrum Semantic Web Xebia Performance Testing Agile Awareness Workshop product owner fitnesse Hibernate Groovy JavaOne qcon Ajax Closures Architecture Poppendieck
medicin depression buy phentermine without a perscription aricept generic hair loss help how do you prevent bone loss urinary tract infection symptoms viagra sex domination cialis viagra cures for throat infection buy sumycin acne care new medication for cancer treatment help for sleeping problems on-line pharmacies cure snoring medications to help clot blood what is aspirin buy zestoretic bronchitis vs pneumonia back pain muscle acne face medication muscle women pain behind knee fat blocker man health arthritis natural cure woman health women insomnia cheap phentermine online cats and irritable bowel syndrome buy cialis generic online nutritional diet for osteoporosis abnormal blood clots treatments for hair loss what is zyprexa dental whitening products impotence herbs drugs for diabetes allergy prevention buy canada levitra Mentax adhd in children hair loss in woman medicines for blood clot online imitrex viagra buy free dog products clindamycin drug how to stop hair loss chloramphenicol discount drug viagra what valium does permanent hair loss heart failure medicine avapro 150mg ordering viagra online food allergies order viagra online online viagra prescription carisoprodol mg improve your skin discount erectile dysfunction medication buy xanax online buy order viagra scabies teatments information allegra vitamine b1 diazepam breast cancer support free stop smoking cipro side effects ultram cheapest treatment attention deficit disorder discount vitamins supplements how to get viagra online synthroid buy cheapest cialis zyrtec online how to clear acne preventive osteoporosis immune stimulants what is hoodia On Line Viagra getting over the pain diflucan dosage health asthma online stores hair loss products blood clot drugs colon parasites hair loss products discount medicine pravastatin buy griseofulvin tablets order indomethacin dog health products how to take a beta-blocker diazapan is valium treating cold sores chronic pain drug what is osteoporosis stress drug tooth whitening lowering cholesterol naturally legality of buying cialis online order levitra treatment for insomnia cheapest cialis index depakote overdose alprazolam condom sales treatment of yeast infection xanax sales taking viagra after cialis how to control pain new birth control chest pain health prozac prescription blood clots viagra in mexico chlamydia pill cancer drugs cold flu drugs how do i order viagra online super viagra acyclovir medicine benadryl dosage erythromycin pregnancy buy contoured condom chronic muscle pain pet health dogs treatment attention deficit disorder dental teeth whitening asthma medicine free prescription drugs herpes drug diabetes treatment buy tooth whitening gel cheap fast valium generic levitra buy cheapest viagra online lopressor drug pharmacy drug prices ultram dosing treatments for bipolar disorder neurontin withdrawal parasite medication chlamydia tips for increasing breast size ways to enhance breast what is valium used for metformin tablet order birth control hair loss for men how does xanax work treatment hepatitis c rythmol cheap acai antioxidants nexium generic blood pressure pills levitra online no prescription Levitra Online medications on line motion sickness drugs bactrim online order roxithromycin nicotine where can i order viagra immune supplements buy erexin v bph prostate allopurinol xanax for depression drug new smoking stop cheap impotence drug generic cialis delivery new treatment for depression antibiotics for cat viagra china alternative medicine cholesterol viagra dose anxiety disorder treatment severe muscle pain treatment of cancer calcium carbonate penis enlargement without pill valium maximum dosage reasons for high blood pressure energy product breast enlargement info cheap effexor building your body wrinkle cream aricept dosage alpha blocker increasing female sex drive valium depression new pain meds no rx xanax drug trileptal mg imitrex avapro 150mg medicine drugs contraception female claritin pill medication for acne med orders buy viagra internet levitra effect treatment for blood clots order sominex buy creatine buy precose cheap viagra overnight lopressor drug body building info health drugs general health and medical what is diazepam eye infections in dogs online prescription pills diclofenac tablet new medication anxiety buy citalopram medication male enhancement enhancement fat blocker medicine for throat infection order cardizem about soma health remedies for dogs generic xanax cheap zyrtec for depression medicine viagra sex domination buy acne skin care product hypnosis help study cure vaginal yeast infection weight loss supplement program muscle pain in leg how to increase erection buy viagra what is cla augmentin doses gaining muscle mass health med online heart rate treatments lopressor drug dog ear canal phentermine without prescription viagra order online weight loss glipizide diabetes astelin generic fat blocker buy gel tooth whitening cheap wellbutrin online weight loss program buy antiox anti-biotics acne skin treatmen tramadole vpxl pill drugs affecting levitra immune system support augmentin hypothyroidism medication buy erexin v uy prescription medication without a prescription buy discount order osteo arthritis online buy pilocarpine cheapest place to buy phentermine parasite treatment impotence help body fat loss viagra herb alternative constipation supplements treatment dementia adhd and medications muscle spasm relief viagra online cheap relieve upper back pain stop hair loss discount viagra online menstrual cycle problems antifungal shampoo side effects ativan gabapentin medication where can i buy viagra diazepam buy soma online clonidine dosage viagra gel top hair loss fast antibiotics cure chlamydia skin fungal infections drug zofran give up smoking alternative medicine cholesterol sleeping help best online viagra scams prednisone 10mg viagra sex domination lotensin easy weight loss pain meds without prescription over the counter drugs new high blood pressure medic generic compazine cetirizine drug order phentermine best fat blockers woman enhancement supplement drug zofran buy precose new drug treatment for cancer how to increase fertility viagra in australia benadryl dosing buy alcoholism medications order l arginine buy diazepam generic for ativan ativan prescription drugs weight loss treatment for chest pain woman health where can i buy phentermine online skin fungal infection give up smoking viagra on line hoodia information how does osteoporosis occur buy viagra online buy alcoholism medications depakote overdose klonopin pill tetracycline capsules what is high blood pressure bladder control for dogs generic for lipitor glucophage online pharmacy gabapentin dosage treating yeast infections dog health info cymbalta anxiety cheap tramadol without prescription hydrea drugs used for cancer cure for high blood pressure alcohol and valium relief from constipation liver infection treatment cialis soft zantac medication help sleep problems all natural antibiotics order medication without prescription sleep problems free hypnotherapy gaining muscle mass cheap viagra order online natural help for pain how to buy viagra drug price celebrex information otc diuretic levitra 10 mg buy medicine online pets products relief foot pain cialis without prescription med care cheapest generic cialis rapid hair loss pain medications generic side effects meds without prescriptions cat anxiety buy simplicef natural cure arthritis effects of high blood pressure lowest price generic viagra how to get birth control new breast cancer drug buy topamax blood pressure meds when are beta blockers prescribed how to get pain meds order fosamax online viagra name order viagra viagra cialis cat's eye health how to relieve lower back pain treating ear infections diazapan is valium online pain doctors high blood pressure in elderly medication to stop smoking wellbutrin dosages diabetes blood sugar levels weight loss diet pill side effects of prescribed pain pills drug list high blood pressure buy cialis online in usa ultram cost how to help osteoporosis how to use clomid discount brand viagra wellbutrin cymbalta buy pills without a prescription buy pain medicine online tab tramadol depression symptoms treatment how levitra work hypertension medications beta blockers prevent premature ejaculation xanax interactions with other medicines purchase medicine on line does alli work xenical mexico prescriptions buy sumycin uy prescription medication without a prescription ambien cost methocarbamol effects cheap beta blockers cats bladder reduce cholesterol naturally metformin tablet scabies medicine breast enhancer pills body building over 50 order viagra cheap zestril medication how to buy prescription medications online pharma kamagra drugs depression ear infection symptoms big muscle controlling blood pressure pain meds and pregnancy buy diazepam without prescription skin allergies antibiotic zoloft buy weight loss nutrition program Buy Cialis breast increase meds without prescriptions blood clots medical edema treatment for flu best hangover remedy diabetes drugs