11 Replies Latest reply on Apr 11, 2011 5:22 PM by brandizzi

    Retrieving list of one-to-many related elements in a test

    brandizzi

      Hi, all!


      I have the test above. The Spectrum class has a one-to-many relationship with the Terminal class.


      @Test
      public void salvarFaixa2() throws Exception {
          new ComponentTest() {
              @Override
              protected void testComponents() throws Exception {
                  Terminal terminal = new Terminal();
                  terminal.setClient("Some client");
                  terminal.setNumber("123");
      
                  Spectrum spectrum = new Spectrum();
                  spectrum.setInitialSpectrum("1");
                  spectrum.setFinalSpectrum("2");
                  spectrum.setHashCode(1);
                  spectrum.setTerminal(terminal);
      
                  spectrum = geSpectrumService().save(spectrum);
      
                  terminal = getTerminalService().getTerminalById(terminal.getId());
                  assertNotNull(terminal.getSpectrums());
                  assertEquals(1, terminal.getSpectrums().size());
                  Spectrum retrievedSpectrum = terminal.getSpectrums().iterator().next();
                  assertEquals(spectrum.getInitialSpectrum(), retrievedSpectrum.getInitialSpectrum());
                  assertEquals(spectrum.getFinalSpectrum(), retrievedSpectrum.getFinalSpectrum());
                  assertEquals(spectrum.getHashCode(), retrievedSpectrum.getHashCode());
              }
          }.run();
      }



      If I execute the test, both the terminal and the spectrum are saved at the database, the relationship is actually registered but the test fails at

      assertNotNull(terminal.getSpectrums());

      - that is, no spectrum is found in the list of spectrums of the terminal.


      What is the issue? How could I retrieve a terminal with the spectrums?


      Thanks in advance!



        • 1. Re: Retrieving list of one-to-many related elements in a test
          kragoth

          Some things I'm not sure of.


          Do you have a cascading relationship from Spectrum to terminal? If yes then....ok...seems weird to me that the cascade is in that direction but...your schema not mine :P


          If no, then how is terminal getting saved to the DB? Does the SpectrumService detect that it also needs to save the terminal? Showing what happens behind that line of code might be helpful.


          If none of that is an issue then....without really knowing for sure, my guess is that the line


          getTerminalService().getTerminalById(terminal.getId());
          



          is returning the instance of terminal still contained in Hibernate's level 1 cache. So, looking at the rest of your test you never actually added the spectrum to the terminal's collection of spectrums and you never asked hibernate to initialize the collection of spectrums so... it will just return you the object it has already because by hibernate's reasoning it hasn't changed since you persisted it.


          Something else to note


          assertNotNull(terminal.getSpectrums());
          



          NOTE: This next paragraph is my opinion only. :)


          This is a bit of a red flag to me. The default constructor of Terminal should be setting the spectrums variable to an empty collection. If you don't do this it means that Anyone wanting to look at this collection has to first check it is null or not before trying to loop over it or perform a size() call or anything. Obviously this is only a problem before the entity has been persisted. Once it has been reread from the database hibernate will put a proxy object in that spot anyway.



          Back on topic :P I'm not sure if this would work (cause I'm not sure exactly where your transaction boundaries are) but, try this.


          //I'll assume you can somehow get your entitymanager
          entityManager.flush();
          entityManager.clear();
          terminal = getTerminalService().getTerminalById(terminal.getId());
          Hibernate.initialize(terminal.getSpectrums());
          assertNotNull(terminal.getSpectrums());
          

          • 2. Re: Retrieving list of one-to-many related elements in a test
            brandizzi

            Hi, Tim!



            Some things I'm not sure of.

            Let's see...



            Do you have a cascading relationship from Spectrum to terminal? If yes then....ok...seems weird to me that the cascade is in that direction but...your schema not mine :P

            Ok, I have to confess that spectrum is a bad translation for Faixa :/ (which would be better translated at frequency range. Unfortunately, it seems too late to alter it. It can be the reason you find this relationship a bit weird. Or it may be actually a strange choice, I do not know, I'm new here :)


            Back to the code, this is the relevant part my Spectrum class:


            @Entity
            public class Spectrum extends Entity {
                // ...
                @ManyToOne
                @JoinColumn(name = "ID_TERMINAL")
                private Terminal terminal;
                // ...
            }




            And this is the relevant part of Terminal:


            public class Terminal extends Entity {
                // ...
                @OneToMany(mappedBy="terminal", cascade=CascadeType.REMOVE, fetch=FetchType.EAGER)
                private Set<Spectrum> spectrum;
                // ...
            }




            If no, then how is terminal getting saved to the DB? Does the SpectrumService detect that it also needs to save the terminal? Showing what happens behind that line of code might be helpful.

            Let's seem them, then. This is the SpectrumService.save() method:


            public Spectrum save(Spectrum spectrum){
                return getSpectrumDAO().save(spectrum);
            }



            SpectrumDAOBean.save() just executes em.persist(spectrum);.



            If none of that is an issue then....without really knowing for sure, my guess is that the line


            getTerminalService().getTerminalById(terminal.getId());


            is returning the instance of terminal still contained in Hibernate's level 1 cache. So, looking at the rest of your test you never actually added the spectrum to the terminal's collection of spectrums and you never asked hibernate to initialize the collection of spectrums so... it will just return you the object it has already because by hibernate's reasoning it hasn't changed since you persisted it.

            That is exactly what is happening: I save the spectrum with the terminal field set and expect this will add the spectrum to the terminal list of spectrums. Apparently, this is not how it works, right? What should I do?



            Something else to note


            assertNotNull(terminal.getSpectrums());


            NOTE: This next paragraph is my opinion only. :)


            This is a bit of a red flag to me. The default constructor of Terminal should be setting the spectrums variable to an empty collection. If you don't do this it means that Anyone wanting to look at this collection has to first check it is null or not before trying to loop over it or perform a size() call or anything. Obviously this is only a problem before the entity has been persisted. Once it has been reread from the database hibernate will put a proxy object in that spot anyway.

            Did you mean it will put a proxy object when it is actually reread from the DB only? Because I'd bet Hibernate is getting it from Level 1 Cache and there is no proxy object in the spectrums field. In fact, there is no object at all.


            (I agree it is a red flag - in fact, the problem actually exists and the collection is null. However, I do not want to alter it right now - I would have to change a lot of entity classes. Also, this is a reasonable test because we use TDD and this assert would be a good way to assure we did what we should do :) )



            Back on topic :P I'm not sure if this would work (cause I'm not sure exactly where your transaction boundaries are) but, try this.

            //I'll assume you can somehow get your entitymanager
            entityManager.flush();
            entityManager.clear();
            terminal = getTerminalService().getTerminalById(terminal.getId());
            Hibernate.initialize(terminal.getSpectrums());
            assertNotNull(terminal.getSpectrums());



            Well, I tried to do it in the test and it did not work: Hibernate claims there is no transaction at the moment...


            Thanks for your help! If you ahve and other suggestions, I'd love to read it!

            • 3. Re: Retrieving list of one-to-many related elements in a test
              kragoth

              Oh dear, I think I'm more confused now then before! LOL


              I'm still confused about where you persist the Terminal.


              This is what you are doing to persist the spectrum, but.... the spectrum -> terminal relationship is not a cascade persist relationship so...how/where are you calling persist on the terminal?


              geSpectrumService().save(spectrum)
              



              I mean you must be persisting it somewhere otherwise hibernate would give you a Transient Object Passed to Persist exception when you try persist Spectrum.


              I need to do some more thinking about this. I'll get back to ya.

              • 4. Re: Retrieving list of one-to-many related elements in a test
                brandizzi

                Tim Evers wrote on Feb 28, 2011 18:26:


                Oh dear, I think I'm more confused now then before! LOL


                I understand, I am making the facts any clearer, sorry! :P



                I'm still confused about where you persist the Terminal.

                This is what you are doing to persist the spectrum, but.... the spectrum -> terminal relationship is not a cascade persist relationship so...how/where are you calling persist on the terminal?

                geSpectrumService().save(spectrum)
                



                I mean you must be persisting it somewhere otherwise hibernate would give you a Transient Object Passed to Persist exception when you try persist Spectrum.

                I need to do some more thinking about this. I'll get back to ya.


                You are right, I forgot to put here the terminal saving. I was trying to translate the code to English but cut the line out by mistake. I am really sorry... Well, the corrected code follows:


                @Test
                public void salvarFaixa2() throws Exception {
                    new ComponentTest() {
                        @Override
                        protected void testComponents() throws Exception {
                            Terminal terminal = new Terminal();
                            terminal.setClient("Some client");
                            terminal.setNumber("123");
                
                            Spectrum spectrum = new Spectrum();
                            spectrum.setInitialSpectrum("1");
                            spectrum.setFinalSpectrum("2");
                            spectrum.setHashCode(1);
                            spectrum.setTerminal(terminal);
                
                            spectrum = geSpectrumService().save(spectrum);
                
                            terminal = getTerminalService().getTerminalById(terminal.getId());
                            assertNotNull(terminal.getSpectrums());
                            assertEquals(1, terminal.getSpectrums().size());
                            Spectrum retrievedSpectrum = terminal.getSpectrums().iterator().next();
                            assertEquals(spectrum.getInitialSpectrum(), retrievedSpectrum.getInitialSpectrum());
                            assertEquals(spectrum.getFinalSpectrum(), retrievedSpectrum.getFinalSpectrum());
                            assertEquals(spectrum.getHashCode(), retrievedSpectrum.getHashCode());
                        }
                    }.run();
                }



                The problem occurs even when I save the terminal...


                Thanks again!

                • 5. Re: Retrieving list of one-to-many related elements in a test
                  kragoth

                  Umm....the 'correct' code still does not show you saving the Terminal. :P

                  • 6. Re: Retrieving list of one-to-many related elements in a test
                    brandizzi

                    Tim Evers wrote on Mar 01, 2011 17:49:


                    Umm....the 'correct' code still does not show you saving the Terminal. :P


                    How I am stupid!


                    There is the correct code:


                    @Test
                    public void salvarFaixa2() throws Exception {
                        new ComponentTest() {
                            @Override
                            protected void testComponents() throws Exception {
                                Terminal terminal = new Terminal();
                                terminal.setClient("Some client");
                                terminal.setNumber("123");
                                getTerminalService().save(terminal);
                    
                                Spectrum spectrum = new Spectrum();
                                spectrum.setInitialSpectrum("1");
                                spectrum.setFinalSpectrum("2");
                                spectrum.setHashCode(1);
                                spectrum.setTerminal(terminal);
                    
                                spectrum = geSpectrumService().save(spectrum);
                    
                                terminal = getTerminalService().getTerminalById(terminal.getId());
                                assertNotNull(terminal.getSpectrums());
                                assertEquals(1, terminal.getSpectrums().size());
                                Spectrum retrievedSpectrum = terminal.getSpectrums().iterator().next();
                                assertEquals(spectrum.getInitialSpectrum(), retrievedSpectrum.getInitialSpectrum());
                                assertEquals(spectrum.getFinalSpectrum(), retrievedSpectrum.getFinalSpectrum());
                                assertEquals(spectrum.getHashCode(), retrievedSpectrum.getHashCode());
                            }
                        }.run();
                    }



                    As usual, getTerminalService().save(terminal); just executes entityManager.persist(terminal).

                    • 7. Re: Retrieving list of one-to-many related elements in a test
                      kragoth

                      I should have asked for this earlier...but, what is done behind the line:


                      terminal = getTerminalService().getTerminalById(terminal.getId());
                      



                      If you run this test and take note of the id that the Terminal gets, then run a new test that all it does is run:


                      terminal = getTerminalService().getTerminalById(terminalIdFromLastTest);
                      



                      Does it load the Spectrum with it?

                      • 8. Re: Retrieving list of one-to-many related elements in a test
                        brandizzi

                        Tim Evers wrote on Mar 02, 2011 17:32:


                        I should have asked for this earlier...but, what is done behind the line:

                        terminal = getTerminalService().getTerminalById(terminal.getId());
                        




                        Here are the methods:


                        public Terminal getTerminalById(Long id) {
                            return getTerminalDAO().getTerminalById(id);
                        }



                        getTerminalDAO().getTerminalById(id); is


                        public Terminal getTerminalById(Long id) {
                            return em.find(Terminal.class, id);
                        }





                        If you run this test and take note of the id that the Terminal gets, then run a new test that all it does is run:

                        terminal = getTerminalService().getTerminalById(terminalIdFromLastTest);
                        



                        Does it load the Spectrum with it?


                        Yes, it does. The tests runned in two different processes, but I believe this is the intention anyway...


                        Thanks for your help, again and again :) Hope you have some idea about how to solve my problem...

                        • 9. Re: Retrieving list of one-to-many related elements in a test
                          themant

                          Hi!


                          I work with Adam and now i'm trying to solve this problem...


                          Does anyone knows what is happening here?


                          If we run the test and then a second test getting the entity saved on the test before, it comes with the spectrum list.



                          One thing really important is that we aren't using EJB. We use JPA (Hibernate).

                          • 10. Re: Retrieving list of one-to-many related elements in a test
                            kragoth

                            I don't really have time to prove this point but, here's what I think is happening.


                            @Test
                            public void salvarFaixa2() throws Exception {
                                new ComponentTest() {
                                    @Override
                                    protected void testComponents() throws Exception {
                                        Terminal terminal = new Terminal();
                                        terminal.setClient("Some client");
                                        terminal.setNumber("123");
                                        getTerminalService().save(terminal);
                            


                            terminal now saved to database and put into your hibernate session cache (id cache I think). This terminal has NOTHING in it's collection of spectrums.


                                        Spectrum spectrum = new Spectrum();
                                        spectrum.setInitialSpectrum("1");
                                        spectrum.setFinalSpectrum("2");
                                        spectrum.setHashCode(1);
                                        spectrum.setTerminal(terminal);
                            
                                        spectrum = geSpectrumService().save(spectrum);
                            



                            spectrum now saved to database and put into hibernate's level 1 cache. This DOES NOT flag the terminal as dirty, so hibernate assumes that the terminal it has in it's cache is still 100% correct.


                                        terminal = getTerminalService().getTerminalById(terminal.getId());
                            



                            No query happens here, hibernate just returns the instance it has in the level 1 cache.


                                        assertNotNull(terminal.getSpectrums());
                                        assertEquals(1, terminal.getSpectrums().size());
                                        Spectrum retrievedSpectrum = terminal.getSpectrums().iterator().next();
                                        assertEquals(spectrum.getInitialSpectrum(), retrievedSpectrum.getInitialSpectrum());
                                        assertEquals(spectrum.getFinalSpectrum(), retrievedSpectrum.getFinalSpectrum());
                                        assertEquals(spectrum.getHashCode(), retrievedSpectrum.getHashCode());
                                    }
                                }.run();
                            }
                            



                            Because the terminal was retrieved from the level 1 cache the spectrums collection is empty.


                            If you did this:


                            entityManager.clear();
                            terminal = getTerminalService().getTerminalById(terminal.getId());
                            



                            I'm willing to bet you would get the right result.


                            Ultimately however you need to get used to how hibernate works. It uses it's id cache whenever possible. It will NOT reload an entity that it believes has not changed if it is cached. This is partly due to a guarantee that hibernate makes. In a single session you will never have more then one instance of a single entity. So your whole test runs under a single session and thus once you have got the instance of Terminal it never changes even if you reread it.


                            So, now it comes down to making the right decisions in your code about how you use entities and their collections.


                            If the collection is cascading then maybe you should decide as a team to always use the cascading persist. Never save the cascaded object always rely on the cascade to save/persist it.


                            If it is a non cascading collection. Then if you persist an entity that belongs in that collection you will need to add it to that collection manually if you intend to use that collection in the same session.


                            So, you have 2 options. Use the entityManager.clear() after persisting both entities. OR write your test like this.


                            @Test
                            public void salvarFaixa2() throws Exception {
                                new ComponentTest() {
                                    @Override
                                    protected void testComponents() throws Exception {
                                        Terminal terminal = new Terminal();
                                        terminal.setClient("Some client");
                                        terminal.setNumber("123");
                            
                                        Spectrum spectrum = new Spectrum();
                                        spectrum.setInitialSpectrum("1");
                                        spectrum.setFinalSpectrum("2");
                                        spectrum.setHashCode(1);
                                        spectrum.setTerminal(terminal);
                                        
                                        terminal.getSpectrums().add(spectrum);
                                     
                                        getTerminalService().save(terminal); //I cant remember if this will work. Maybe you have to add the spectrum into the collection after the save but...you'll work that out.
                                        spectrum = geSpectrumService().save(spectrum);
                            
                                        terminal = getTerminalService().getTerminalById(terminal.getId());
                                        assertNotNull(terminal.getSpectrums());
                                        assertEquals(1, terminal.getSpectrums().size());
                                        Spectrum retrievedSpectrum = terminal.getSpectrums().iterator().next();
                                        assertEquals(spectrum.getInitialSpectrum(), retrievedSpectrum.getInitialSpectrum());
                                        assertEquals(spectrum.getFinalSpectrum(), retrievedSpectrum.getFinalSpectrum());
                                        assertEquals(spectrum.getHashCode(), retrievedSpectrum.getHashCode());
                                    }
                                }.run();
                            }
                            

                             

                            • 11. Re: Retrieving list of one-to-many related elements in a test
                              brandizzi

                              So, you have 2 options. Use the entityManager.clear() after persisting both entities. OR write your test like this.

                              We choose to adopt the second approach and it is working well. At first I thought it was somewhat a workaround/smell but after this discussionwWe thought it would be clearer and easier.


                              In fact, I really need to learn a lot more about Hibernate. I'm used only to JPA - and even JPA I do not know superbly. Anyway, your help was wonderful! Thank you again!