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() + "'");
        }
    }
}

Reply via email to