Thanks for tip, Bryan. The idea of dao.getAll in pojo makes service and presentation level dependant on dao interfaces definition. That's not good when applications have to share their data models.
They mention "You can filter these duplicates simply through a Set.", do you know how to make hibernate to return result as HashSet? As I understand if two objects have identical hash codes only one will be added to HashSet. ros Bryan Noll wrote: > > Nevermind. According to this... > > http://www.hibernate.org/hib_docs/v3/reference/en/html_single/ > Section 10.4.1 says: > > *"Note that queries that make use of eager fetching of collections > usually return duplicates of the root objects (but with their > collections initialized). You can filter these duplicates simply through > a Set.*" > > > This seems wrong to me, but at least it's not just us, it's actually > defined behavior. I guess you'll have to use the 'Set users = ...' > approach as I mentioned earlier. > > --Bryan > > Bryan Noll wrote: >> OK. Would one of you other appfuse committer folks like to verify >> this for me? I want to make sure I'm not crazy before I approach >> somebody in the Hibernate camp about this. It seems like such an >> obvious bug, I want to be sure I'm not screwing something up. The >> only thing you have to do to basically prove that it's not behaving as >> expected is run the test with Nicknames mapped as EAGER on the User >> class, watch it fail, then run the test with the the default FetchType >> (just don't enter one) and watch it pass. >> >> >> >> Bryan Noll wrote: >>> This seems buggy to me. I believe I remember having this exact same >>> issue with an older version of hibernate (3.x beta). The one I >>> remember manifested itself when I used 'JOIN FETCH' syntax to eagerly >>> fetch stuff, which is basically the same thing as mapping it as EAGER >>> as far as the sql that's generated goes. That being said, the >>> generated SQL itself is not the problem. That is correct because it >>> brings back all the information necessary to fully materialize the >>> eagerly mapped child objects. The problem seems to occur after that, >>> in the bowels of Hibernate where it ought to translate those 4 rows >>> into, in my case, 2 User objects, with one of those user objects >>> having fully materialized references to 3 Nickname objects. >>> >>> I'll keep messing with it and see if I can find out if I'm doing >>> something wrong. I guess the worst case scenario would leave us >>> doing something like this if necessary: >>> >>> Set users = new HashSet(dao.getAll()); >>> >>> That would guarantee only 2 User objects. >>> >>> ros wrote: >>>> That's rigth Bryan, i've got the same effect - sql generated in >>>> hibernate >>>> returns duplicate rows with identical ids. >>>> >>>> Do you know how to run in debug mode appfuse 2 from intellij? >>>> >>>> >>>> Bryan Noll wrote: >>>> >>>>> OK... so I accidentally stopped short of actually addressing your >>>>> issue. So, I added another test as follows: >>>>> >>>>> public void testGetAllUsers() throws Exception { >>>>> System.out.println("##### testGetAllUsers #####"); >>>>> super.assertEquals(2, dao.getAll().size()); >>>>> } >>>>> >>>>> ...and I'm having the same issue. I'll have to look some more to >>>>> figure >>>>> out what's going on. >>>>> >>>>> Hibernate generates this SQL for the example I gave, which returns 4 >>>>> rows, and I only have to app_user's in the table. >>>>> >>>>> SELECT THIS_.ID AS ID0_2_, >>>>> THIS_.ADDRESS AS ADDRESS0_2_, >>>>> THIS_.COUNTRY AS COUNTRY0_2_, >>>>> THIS_.CITY AS CITY0_2_, >>>>> THIS_.PROVINCE AS PROVINCE0_2_, >>>>> THIS_.POSTAL_CODE AS POSTAL6_0_2_, >>>>> THIS_.VERSION AS VERSION0_2_, >>>>> THIS_.ACCOUNT_ENABLED AS ACCOUNT8_0_2_, >>>>> THIS_.USERNAME AS USERNAME0_2_, >>>>> THIS_.PASSWORD AS PASSWORD0_2_, >>>>> THIS_.PASSWORD_HINT AS PASSWORD11_0_2_, >>>>> THIS_.FIRST_NAME AS FIRST12_0_2_, >>>>> THIS_.LAST_NAME AS LAST13_0_2_, >>>>> THIS_.EMAIL AS EMAIL0_2_, >>>>> THIS_.PHONE_NUMBER AS PHONE15_0_2_, >>>>> THIS_.WEBSITE AS WEBSITE0_2_, >>>>> THIS_.ACCOUNT_EXPIRED AS ACCOUNT17_0_2_, >>>>> THIS_.ACCOUNT_LOCKED AS ACCOUNT18_0_2_, >>>>> THIS_.CREDENTIALS_EXPIRED AS CREDENT19_0_2_, >>>>> ROLES2_.USER_ID AS USER1_4_, >>>>> ROLE3_.ID AS ROLE2_4_, >>>>> ROLE3_.ID AS ID1_0_, >>>>> ROLE3_.NAME AS NAME1_0_, >>>>> ROLE3_.DESCRIPTION AS DESCRIPT3_1_0_, >>>>> NICKNAMES4_.USER_ID_REF AS USER4_5_, >>>>> NICKNAMES4_.ID AS ID5_, >>>>> NICKNAMES4_.ID AS ID2_1_, >>>>> NICKNAMES4_.NAME AS NAME2_1_, >>>>> NICKNAMES4_.STORYBEHINDNAME AS STORYBEH3_2_1_, >>>>> NICKNAMES4_.USER_ID_REF AS USER4_2_1_ >>>>> FROM APP_USER THIS_ >>>>> LEFT OUTER JOIN USER_ROLE ROLES2_ >>>>> ON THIS_.ID = ROLES2_.USER_ID >>>>> LEFT OUTER JOIN ROLE ROLE3_ >>>>> ON ROLES2_.ROLE_ID = ROLE3_.ID >>>>> LEFT OUTER JOIN NICKNAMES NICKNAMES4_ >>>>> ON THIS_.ID = NICKNAMES4_.USER_ID_REF >>>>> >>>>> >>>>> >>>>> Bryan Noll wrote: >>>>> >>>>>> OK... so it might help to see the code from your >>>>>> GenericSearchDaoHibernate class, but I it works just fine for me with >>>>>> the following: >>>>>> >>>>>> Assuming you checked out from the following (which by the way, the >>>>>> suggested way for using appfuse 2 is not to use the code directly as >>>>>> you did with appfuse 1, but do use an archetype, and depend on >>>>>> appfuse >>>>>> in your pom.xml) : >>>>>> >>>>>> https://appfuse.dev.java.net/svn/appfuse/trunk/ >>>>>> >>>>>> 1) >>>>>> >>>>>> Add this to your User class in /data/common >>>>>> >>>>>> protected Set<Nickname> nickNames = null; >>>>>> @OneToMany(mappedBy="user", cascade=CascadeType.ALL, >>>>>> fetch=FetchType.EAGER) >>>>>> public Set<Nickname> getNickNames() { >>>>>> return nickNames; >>>>>> } >>>>>> >>>>>> public void setNickNames(Set<Nickname> nickNames) { >>>>>> this.nickNames = nickNames; >>>>>> } >>>>>> >>>>>> >>>>>> 2) Create the Nickname class in /data/common (file attached) >>>>>> >>>>>> 3) Add the following to >>>>>> /data/hibernate/src/test/resources/hibernate.cfg.xml >>>>>> >>>>>> <mapping class="org.appfuse.model.Nickname"/> >>>>>> >>>>>> 4) Add the test >>>>>> src/test/java/org/appfuse/dao/UserNicknameDaoTest.java >>>>>> (file attached) to /data/hibernate >>>>>> >>>>>> Notice here that I didn't create a new dao or method or anything. >>>>>> Simply used the GenericDao that is already configured for the User >>>>>> class, and by virtue of the fact that we mapped nicknames with an >>>>>> EAGER fetch type, the 'get' works for us. >>>>>> >>>>>> 5) Run mvn:install from /data/common >>>>>> >>>>>> 6) Run mvn:test from /data/hibernate - this should create your >>>>>> nicknames table >>>>>> >>>>>> 7) Insert 3 rows into the nicknames table that refer to user with id >>>>>> of 1 (more or less than 3 and the test will not pass) >>>>>> >>>>>> 8) Run 'mvn -Dtest=UserNicknameDaoTest' from /data/hibernate - the >>>>>> test should pass and you should see something like this from the >>>>>> println's >>>>>> >>>>>> The story behind 'Country Bry' is : 'Sean and Raible put their heads >>>>>> together for this one.' >>>>>> The story behind 'Country' is : 'Hef shortened Country Bry to this.' >>>>>> The story behind 'Meat' is : 'Filios gave me this one.' >>>>>> >>>>>> >>>>>> >>>>>> Let me know if something doesn't work. >>>>>> >>>>>> >>>>>> >>>>>> ros wrote: >>>>>> >>>>>>> (Appguse2 Hibernate) >>>>>>> I've added OneToMany relation with FetchType.EAGER from User pojo to >>>>>>> Test >>>>>>> pojo. >>>>>>> Now GenericDaoHibernate getAll returns 5 users, but in database >>>>>>> there >>>>>>> are 2 >>>>>>> users - one amin user and one tomcat user, tomcat user has relation >>>>>>> to 4 >>>>>>> Test pojos. >>>>>>> Can getAll return distinct selection of users? >>>>>>> >>>>>>> >>>>>>> >>>>>>> >>>>>>> @Entity >>>>>>> @Table(name="app_user") >>>>>>> public class User extends BaseObject implements Serializable, >>>>>>> UserDetails { >>>>>>> ... >>>>>>> protected Set<Test> tests = new HashSet<Test>(); >>>>>>> ... >>>>>>> @OneToMany(targetEntity=Test.class, cascade=CascadeType.ALL, >>>>>>> mappedBy="user", fetch = FetchType.EAGER) >>>>>>> public Set<Test> getTests() { >>>>>>> return testss; >>>>>>> } >>>>>>> ... >>>>>>> } >>>>>>> >>>>>>> test: >>>>>>> >>>>>>> GenericSearchDaoHibernate<User, Long> searchDao = new >>>>>>> GenericSearchDaoHibernate(User.class); >>>>>>> searchDao.setSessionFactory((SessionFactory) >>>>>>> applicationContext.getBean("sessionFactory")); >>>>>>> List<User> users = searchDao.getAll(); >>>>>>> System.out.println(users.toString()); >>>>>>> >>>>>>> output: >>>>>>> [EMAIL >>>>>>> PROTECTED],enabled=true,accountExpired=false,credentialsExpired=false,accountLocked=false,Granted >>>>>>> >>>>>>> >>>>>>> Authorities: ,user], >>>>>>> [EMAIL >>>>>>> PROTECTED],enabled=true,accountExpired=false,credentialsExpired=false,accountLocked=false,Granted >>>>>>> >>>>>>> >>>>>>> Authorities: ,user], >>>>>>> [EMAIL >>>>>>> PROTECTED],enabled=true,accountExpired=false,credentialsExpired=false,accountLocked=false,Granted >>>>>>> >>>>>>> >>>>>>> Authorities: ,user], >>>>>>> [EMAIL >>>>>>> PROTECTED],enabled=true,accountExpired=false,credentialsExpired=false,accountLocked=false,Granted >>>>>>> >>>>>>> >>>>>>> Authorities: ,user], >>>>>>> [EMAIL >>>>>>> PROTECTED],enabled=true,accountExpired=false,credentialsExpired=false,accountLocked=false,Granted >>>>>>> >>>>>>> >>>>>>> Authorities: ,admin]] >>>>>>> >>>>>>> >>>>>>> If relation is FetchType.LAZY then no duplicate users, but I need to >>>>>>> load >>>>>>> Test pojos with user. >>>>>>> >>>>>>> >>>>>> ------------------------------------------------------------------------ >>>>>> >>>>>> package org.appfuse.model; >>>>>> >>>>>> import javax.persistence.CascadeType; >>>>>> import javax.persistence.Column; >>>>>> import javax.persistence.Entity; >>>>>> import javax.persistence.GeneratedValue; >>>>>> import javax.persistence.GenerationType; >>>>>> import javax.persistence.Id; >>>>>> import javax.persistence.JoinColumn; >>>>>> import javax.persistence.ManyToOne; >>>>>> import javax.persistence.Table; >>>>>> >>>>>> import org.apache.commons.lang.builder.EqualsBuilder; >>>>>> import org.apache.commons.lang.builder.HashCodeBuilder; >>>>>> import org.apache.commons.lang.builder.ToStringBuilder; >>>>>> >>>>>> @Entity >>>>>> @Table(name="nicknames") >>>>>> public class Nickname extends BaseObject { >>>>>> >>>>>> protected Long id; >>>>>> protected String name; >>>>>> protected String storyBehindName; >>>>>> protected User user; >>>>>> >>>>>> @Id @GeneratedValue(strategy=GenerationType.AUTO) >>>>>> public Long getId() { >>>>>> return id; >>>>>> } >>>>>> >>>>>> @Column(length=30) >>>>>> public String getName() { >>>>>> return name; >>>>>> } >>>>>> >>>>>> @Column(length=400) >>>>>> public String getStoryBehindName() { >>>>>> return storyBehindName; >>>>>> } >>>>>> >>>>>> @ManyToOne(cascade=CascadeType.ALL) >>>>>> @JoinColumn(name="user_id_ref") >>>>>> public User getUser() { >>>>>> return user; >>>>>> } >>>>>> public void setId(Long id) { >>>>>> this.id = id; >>>>>> } >>>>>> public void setName(String name) { >>>>>> this.name = name; >>>>>> } >>>>>> public void setStoryBehindName(String storyBehindName) { >>>>>> this.storyBehindName = storyBehindName; >>>>>> } >>>>>> public void setUser(User user) { >>>>>> this.user = user; >>>>>> } >>>>>> >>>>>> /** >>>>>> * @see java.lang.Object#equals(Object) >>>>>> */ >>>>>> public boolean equals(Object object) { >>>>>> if (!(object instanceof Nickname)) { >>>>>> return false; >>>>>> } >>>>>> Nickname rhs = (Nickname) object; >>>>>> return new EqualsBuilder().append( >>>>>> this.user, rhs.user).append(this.storyBehindName, >>>>>> rhs.storyBehindName).append(this.name, rhs.name).append( >>>>>> this.id, rhs.id).isEquals(); >>>>>> } >>>>>> /** >>>>>> * @see java.lang.Object#hashCode() >>>>>> */ >>>>>> public int hashCode() { >>>>>> return new HashCodeBuilder(-288934289, >>>>>> 311543415).append(this.user) >>>>>> >>>>>> .append(this.storyBehindName).append(this.name).append(this.id) >>>>>> .toHashCode(); >>>>>> } >>>>>> /** >>>>>> * @see java.lang.Object#toString() >>>>>> */ >>>>>> public String toString() { >>>>>> return new ToStringBuilder(this).append("name", >>>>>> this.name).append( >>>>>> "storyBehindName", this.storyBehindName).append("id", >>>>>> this.id) >>>>>> .append("user", this.user).toString(); >>>>>> } >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> } >>>>>> >>>>>> ------------------------------------------------------------------------ >>>>>> >>>>>> package org.appfuse.dao; >>>>>> >>>>>> import org.appfuse.Constants; >>>>>> import org.appfuse.model.Address; >>>>>> import org.appfuse.model.Nickname; >>>>>> import org.appfuse.model.Role; >>>>>> import org.appfuse.model.User; >>>>>> import org.springframework.dao.DataAccessException; >>>>>> import org.springframework.dao.DataIntegrityViolationException; >>>>>> >>>>>> public class UserNicknameDaoTest extends BaseDaoTestCase { >>>>>> private UserDao dao = null; >>>>>> >>>>>> public void setUserDao(UserDao dao) { >>>>>> this.dao = dao; >>>>>> } >>>>>> >>>>>> public void testGetUserInvalid() throws Exception { >>>>>> try { >>>>>> dao.get(1000L); >>>>>> fail("'badusername' found in database, failing test..."); >>>>>> } catch (DataAccessException d) { >>>>>> assertTrue(d != null); >>>>>> } >>>>>> } >>>>>> >>>>>> public void testGetUser() throws Exception { >>>>>> User user = dao.get(1L); >>>>>> >>>>>> assertNotNull(user); >>>>>> assertEquals(1, user.getRoles().size()); >>>>>> assertTrue(user.isEnabled()); >>>>>> >>>>>> super.assertEquals(3, user.getNickNames().size()); >>>>>> for (Nickname n : user.getNickNames()) { >>>>>> System.out.println("The story behind '" + n.getName() + >>>>>> "' is >>>>>> : '" + n.getStoryBehindName() + "'"); >>>>>> } >>>>>> } >>>>>> } >>>>>> >>>>>> >>>>> >>>> >>>> > > -- View this message in context: http://www.nabble.com/apfuse2%2Bhibernate---GenericDaoHibernate-getAll-returns-duplicate-entities-%28users%29-tf3227657s2369.html#a9002267 Sent from the AppFuse - User mailing list archive at Nabble.com. --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]
