Announcement Announcement Module
Collapse
No announcement yet.
"failed to lazily initialize a collection" behaviour Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • "failed to lazily initialize a collection" behaviour

    I am trying to understand the difference in behaviour between two setups, one that works and the other that doesn't.

    Using: Spring 2.5, Hibernate 3.2, Hibernate-annotations 3.3.1.

    I have a Region persistent object containing a Map collection:
    Code:
    public class Region {
    	@Id
    	private Integer id;
    
    	// various fields omitted
    	
    	@CollectionOfElements
    	@JoinTable(name = "locale.regions_localized", joinColumns = { @JoinColumn(name = "region") })
    	@org.hibernate.annotations.MapKey(columns = { @Column(name = "lang") })
    	@Column(name = "name")
    	private Map<Language, String> regionLocalized = new HashMap<Language, String>();
    
    	// constructors and getters and setters omitted
    }
    The RegionDao needs a method to ensure that the collection is fully initialized. Compare the two methods fullyLoadA() and fullyLoadB():

    Code:
    public class RegionDao extends HibernateDaoSupport {
    
    	public void fullyLoadA(final Region region, final Language lang) {
    		getHibernateTemplate().execute(new HibernateCallback() {
    			public Object doInHibernate(Session session) throws HibernateException, SQLException {
    				session.refresh(region);
    				Hibernate.initialize(region.getLocalized().get(lang));
    				return null;
    			}
    		});
    	}
    
    	public void fullyLoadB(Region region, Language lang) {
    		region = (Region) getHibernateTemplate().load(Region.class, region.getId());
    		getHibernateTemplate().initialize(region.getLocalized().get(lang));
    	}
    
    }
    Here is the (transaction-enabled) service, where one bit of code succeeds, while the other fails:

    Code:
    @Transactional
    public class MyService {
    
    	private final Logger log = Logger.getLogger(MyService.class);
    	private final Language myLanguage = Language.EN;
    
    	public void testGetRegion() {
    		Region regionA = regionDao.load(25);
    		regionDao.fullyLoadA(regionA, myLanguage);
    		log.info(regionA.getLocalized().get(myLanguage)); // successfull!
    
    		Region regionB = regionDao.load(26);
    		regionDao.fullyLoadB(regionB, myLanguage);
    		log.info(regionB.getLocalized().get(myLanguage)); // exception: "failed to lazily initialize a collection"
    	}
    }
    Could anyone explain to me the fundamental difference between the two approaches: why one succeeds and the other fails.

    The only technical distinction I can think of between methods fullyLoadA() and fullyLoadB() is the use of Hibernate sessions. In the first case, both operations happen within the same session, while in the second case, the two operations may happen in separate sessions (is that correct?). Does that justify the behaviour?

  • #2
    First time i think that i see a collection with Map type

    i am not an expert with Hibernate but
    fullyLoadA() and fullyLoadB() are void
    so

    Code:
    regionDao.fullyLoadX(regionX, myLanguage);
    return nothing

    and in fullyLoadB
    Code:
    region = (Region) getHibernateTemplate().load(Region.class, region.getId());
    region is never returned

    try this

    Code:
    public Region fullyLoadB(Region region, Language lang) {
    		region = (Region) getHibernateTemplate().load(Region.class, region.getId());
    		getHibernateTemplate().initialize(region.getLocalized().get(lang));
                    return region;
    }
    and
    Code:
                    Region regionB = regionDao.load(26);
    		regionB = regionDao.fullyLoadB(regionB, myLanguage);
    		log.info(regionB.getLocalized().get(myLanguage));
    and you have session.refresh(region); in fullyLoadA

    HTH

    regards

    Comment


    • #3
      Thank you for taking the time to reply. Unfortunately, IMHO, I don't think your suggestions address the issue...

      Originally posted by dr_pompeii View Post
      i am not an expert with Hibernate but
      fullyLoadA() and fullyLoadB() are void
      Indeed. Exactly as Hibernate.initialize(object.getCollection()) is also void. The point of Hibernate's static initialize(object) method is to populate properly the proxied object. Likewise with my fullyLoad() method. The object should not be reassigned, since it does not change (i.e. the "object pointer" still points to the same object in memory); it's simply fully initialized.

      and in fullyLoadB
      Code:
      region = (Region) getHibernateTemplate().load(Region.class, region.getId());
      region is never returned
      Well, you see, here in this case, I had to break my previous rule of not re-assigning region. The reason I had to do that is that the nicer alternative threw an exception (something like "no attached session"; I forget the wording):
      Code:
      getHibernateTemplate().refresh(region);

      Comment


      • #4
        Hello

        Unfortunately, IMHO, I don't think your suggestions address the issue...
        thats the reason because i said i am not an expert , but..

        Code:
        Exactly as Hibernate.initialize(object.getCollection()) is also void
        mmmm, why you said that?, normally i use this getHibernateTemplate().initialize(object.getCollec tion())
        to initialize my collections and works normal,
        of course that the method that i call to initialize my object must return an object

        The point of Hibernate's static initialize(object) method is to populate properly the proxied object.
        the object itself
        and you can initialize some associated objects (and collections too) if are need it

        Well, you see, here in this case, I had to break my previous rule of not re-assigning region
        and what is the problem with the rule if is broken?

        regards
        Last edited by dr_pompeii; Aug 25th, 2008, 07:21 AM.

        Comment


        • #5
          Well, let me put my question in perspective.

          I do have a solution that works (fullyLoadA()).
          What I want is to get an understanding of why the alternative does not. (And it's not down to Java grammar.)

          There is something mysterious, cabbalistic about Hibernate's handling of proxied entities: I am never entirely certain ahead of time how much or how little I am likely to get away with when I am about to read the properties off such an object (how deep can I go, etc.), and I would like it were less mysterious.

          As Dr_Pompeii pointed out, I have never seen an example of a Map-Collection used in such context---whereas I have to use them all the time, because of localization. (For instance, if I were to support 200 languages, I wouldn't want to read 200 lines from the database when I want a property name only in Urdu.) Frankly, I suspect the Map-Collection is hands down Hibernate's weakest area of support for collections (I won't expound on this in here), which is why it's critical to get a grasp of what's happening.

          I suspect that the current question revolves entirely around session and transaction management. Since my solution involves Spring's HibernateCallback(), I assume that this forum is the most appropriate place to ask about it. But if it's too specific to Hibernate, I'd be happy to turn to Hibernate forums (though the level of feedback there is a fraction of what it is here...).

          Can anyone enlighten me as to why fullyLoadB() doesn't do its job?
          Last edited by pierdeux; Aug 25th, 2008, 11:44 AM.

          Comment


          • #6
            Code:
            public Region fullyLoadB(Region region, Language lang) {
            	region = (Region) getHibernateTemplate().load(Region.class, region.getId());
            	getHibernateTemplate().initialize(region.getLocalized().get(lang));
                    return region;
            }
            some critical problem in work with the code above?
            and i forgot this, normally i must write in this way

            Code:
            getHibernateTemplate().initialize(region.getLocalized());
            first test and share if works, and why you choiced Map instead of Set?

            regards

            Comment


            • #7
              Originally posted by dr_pompeii View Post
              Code:
              public Region fullyLoadB(Region region, Language lang) {
              	region = (Region) getHibernateTemplate().load(Region.class, region.getId());
              	getHibernateTemplate().initialize(region.getLocalized().get(lang));
                      return region;
              }
              some critical problem in work with the code above?
              It yielded exactly the same exception as before (and that was to be expected since it is functionally equivalent to my version of fullyLoadB()):
              Code:
              org.hibernate.LazyInitializationException: could not initialize proxy - no Session
              * * *

              Incidental to this topic:
              and why you choiced Map instead of Set?
              Suppose Region 1 denotes Germany, then the associated table of Regions' names will contain the following entries among others:

              Code:
               region | lang | name
              --------+------+-------------
               1      |  de  | Deutschland
               1      |  fr  | Allemagne
               1      |  en  | Germany
               1      |  es  | Alemania
               1      |  it  | Germania
               1      |  ru  | Германия
               1      |  pl  | Niemcy
              The "region" column is a foreign key to the main Regions table, while the "lang" column is a foreign key to the Languages table.

              The natural object structure for encompassing a region's name (localized) would be exactly what I have: a Map<Language, String>.

              On the other hand, a Set, a Bag or a List would not account for the functional relationship between the language and the name (in particular, it would fail to reflect the unicity constraint of (region, lang) pair), and a List would introduce a notion of ordering which is alien to my structure.
              Last edited by pierdeux; Aug 25th, 2008, 03:28 PM.

              Comment


              • #8
                Reading the HibernateTemplate docs:


                "The central method is execute, supporting Hibernate access code implementing the HibernateCallback interface. It provides Hibernate Session handling such that neither the HibernateCallback implementation nor the calling code needs to explicitly care about retrieving/closing Hibernate Sessions, or handling Session lifecycle exceptions. For typical single step actions, there are various convenience methods (find, load, saveOrUpdate, delete)."


                I am not sure if I am reading this right but it looks like if you use execute you are basically guaranteeing that you will have a session available. I think your right in you said "I suspect that the current question revolves entirely around session and transaction management.".

                You may want to take a closer look at those configs.

                Comment


                • #9
                  Your B method doesn't work because it uses 2 sessions instead of one. Transactionality should fix the problem, as it should make sure every hibernateTemplate method is executed using the same session. The problem is that your transaction setup is not working - for @transactional support you need java proxies and for proxies you need an interface. No interface -> no proxy -> no transaction -> 2 sessions -> lazyloadingexception.

                  Comment


                  • #10
                    Thank you, TerpInMD. Your comment strengthens my intuition. However it raises a more specific question...

                    Notice that, in my method fullyLoadB(), no exception is thrown by the line getHibernateTemplate().initialize(...);. The exception is thrown only when the Service (that called the Dao) attempts to read from the un-initialized collection... I would have thought that, if the initialize() method failed to do its job (because, say, the session user therein is different from the session that re-attached the dirty object), it shouldn't keep quiet about it!!!

                    * * *

                    Originally posted by dejanp View Post
                    Your B method doesn't work because it uses 2 sessions instead of one. Transactionality should fix the problem, as it should make sure every hibernateTemplate method is executed using the same session. The problem is that your transaction setup is not working - for @transactional support you need java proxies and for proxies you need an interface. No interface -> no proxy -> no transaction -> 2 sessions -> lazyloadingexception.
                    Dejanp, I actually cheated in the quoted bit of code (I didn't want to go into such details): in fact, I don't use @Transactional. My service truly is transactional (Interface and all) through the following lines in my applicationContext.xml file:

                    Code:
                    	<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
                    		<property name="sessionFactory" ref="sessionFactory" />
                    	</bean>
                    	<tx:advice id="txAdvice" transaction-manager="transactionManager">
                    		<tx:attributes>
                    			<tx:method name="get*" read-only="true" />
                    			<tx:method name="load*" read-only="true" />
                    			<tx:method name="find*" read-only="true" />
                    			<tx:method name="*" />
                    		</tx:attributes>
                    	</tx:advice>
                    	<aop:config>
                    		<aop:advisor pointcut="com.myapp.SystemArchitecture.inServiceLayer()" advice-ref="txAdvice" />
                    	</aop:config>
                    The regionDao object of the earlier code is in fact of class RegionDaoImpl which implements the RegionDao interface. Similarly with the MyService class.

                    In short, my transaction setup has been working fine (rollback and all) over the last two years. I doubt the transaction is part of the current problem: more likely the session.

                    * * *

                    Can I deduce from that whole discussion that the static method Hibernate.initialize(collection) can only be used either:
                    a) inside the execute() method of a HibernateCallback, or
                    b) in between factory.openSession() and session.close().

                    We've seen (through my own example) that a) works.

                    But how could b) work too?
                    In other words, how is Hibernate.initialize() "aware" of the surrounding session?
                    Or even: how is it aware of that session inside the execute() method of a HibernateCallback???
                    Last edited by pierdeux; Aug 26th, 2008, 11:34 AM.

                    Comment


                    • #11
                      Your transaction setup is not working. When HibernateTemplate methods are invoked inside of a transaction they all use the same session.

                      Comment


                      • #12
                        Originally posted by dejanp View Post
                        Your transaction setup is not working.
                        That's a pretty bold statement...

                        For the sake of argument and for my own learning, what kind of test would be acceptable as a proof that it does work?
                        Last edited by pierdeux; Aug 27th, 2008, 12:31 PM.

                        Comment


                        • #13
                          Your transaction setup is not working
                          i am agree

                          Code:
                          <aop:config>
                              <aop:advisor pointcut="com.myapp.SystemArchitecture.inServiceLayer()" advice-ref="txAdvice" />
                          </aop:config>
                          is right your pointcut?

                          in all the code that you posted before , well "inServiceLayer" not appear
                          i only see RegionDao and MyService, so what is "inServiceLayer"??

                          regards

                          Comment


                          • #14
                            Originally posted by dr_pompeii View Post
                            Code:
                            <aop:config>
                                <aop:advisor pointcut="com.myapp.SystemArchitecture.inServiceLayer()" advice-ref="txAdvice" />
                            </aop:config>
                            is right your pointcut?

                            in all the code that you posted before , well "inServiceLayer" not appear
                            i only see RegionDao and MyService, so what is "inServiceLayer"??
                            Now come on, guys. We're not in kindergarten. I am not posting my entire application. You can assume that inServiceLayer() is defined inside the SystemArchitecture class (which I did not provide either). Otherwise, Spring wouldn't even let me start. (It's pretty obvious that, even had I provided that SystemArchitecture class, you still wouldn't be able to judge whether it does its job.)

                            What I can say is that I have used that transaction setup for about 12 months, all the while reading, writing, updating and deleting from the database, committing and rolling back transactions, and running tests upon tests. I have got 12 months of practical success with that transaction setup. It is not impossible that there is a problem in there, but the likelihood is that the problem is elsewhere.

                            Now, let me ask again the question I put earlier (and this is not rhetoric or sarcarsm): what would be universally considered as a good test of such a transaction setup? I would truly appreciate suggestions on that front: either I will be proved wrong (my transaction setup was not working in some cases), or else we'll know the problem is elsewhere.

                            Comment


                            • #15
                              my brain is foggy from many hours of research on a similar but different problem, so take what i say cautiously.
                              i think in loadedB you are reassign the local variable region - it is being set with a new instance from the database (retrieved with the id from the reference in the calling method).

                              thus your calling method's region - regionB - is not being updated.

                              try accessing the map within loadedB (for testing purposes).

                              Comment

                              Working...
                              X