Criteria.DISTINCT_ROOT_ENTITY doesn't prevent duplicated objects
Asked Answered
S

2

9

I have following dao method:

@Override
public List<AdminRole> findAll() {
    Session session = sessionFactory.getCurrentSession();
    Criteria criteria = session.createCriteria(AdminRole.class);
    criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
    return criteria.list();
}

Actually I want to retrieve all entries from database.

Sometimes I see duplicates. This happens when I add user with AdminRole.

I have read that it is possible when I use EAGER fetch type and this should be fix adding following line:

criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

But this doesn't help me.

my mapping:

@Entity
@Table(name = "terminal_admin_role")
public class AdminRole {

    @Id
    @Column(name = "role_id", nullable = false, unique = true)
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_id")
    @SequenceGenerator(name = "user_id", sequenceName = "user_id")
    private Long adminId;

    @Column(name = "role")
    private String role;

    public AdminRole(String role) {
        this.role = role;
    }

    public AdminRole() {
    }

    // get set

    @Override
    public String toString(){
        return role;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof AdminRole)) {
            return false;
        }

        AdminRole adminRole = (AdminRole) o;

        if (!role.equals(adminRole.role)) {
            return false;
        }

        return true;
    }

    @Override
    public int hashCode() {
        return role.hashCode();
    }
}

and

@Entity
@Table(name = "terminal_admin")
public class TerminalAdmin {
    @ManyToMany(fetch=FetchType.EAGER,cascade=CascadeType.ALL)
    @JoinTable(name = "admin_role", joinColumns = { 
        @JoinColumn(name = "admin_id", nullable = false) }, 
        inverseJoinColumns = { @JoinColumn(name = "role_id", 
                nullable = false) })
    private Set<AdminRole> adminRoles;      
    //...
}

P.S.

I cannot switch fetch type.

I don't want to put this list into set.

Shawn answered 14/10, 2015 at 14:7 Comment(10)
I think thats distinct only because the user object value differs. AdminRole will point to many user object so AdminRole objects with same value comes as the result of the query because each has its own user object and the user object value differs.Siding
@Ry Kannan can you suggest way to fix issue?Shawn
I think you should add the field you want to select distinct. [link]:(#5196743)Critical
What do you mean by "sometimes"? You add a user with admin role and after that you always see duplicates when you execute the query?Herthahertz
@Dragan Bozanovic actually you rightShawn
And, there are no duplicates in the terminal_admin_role table in the database after you add the user?Herthahertz
@Dragan Bozanovic Yes I checked itShawn
How did you observe that you get duplicates? In the debugger?Herthahertz
@Dragan Bozanovic first of all I saw that behavour of some pages changed. In debug I have seen duplicates that absence in databaseShawn
Could you execute System.out.println(session.createCriteria(AdminRole.class).list().size()) in the application and select count(*) from terminal_admin_role in the SQL client you use and compare the results?Herthahertz
H
2

There is no reason to use DISTINCT_ROOT_ENTITY or anything similar, all you need is:

session.createCriteria(AdminRole.class).list();

If you get duplicates, then you really have them in the database. Check the code which saves AdminRoles either directly or by cascading from other entities.

When cascading PERSIST/MERGE operations from other entities, make sure that the operation is cascaded to a persistent/detached AdminRole instance, not to a transient (new) one.

Herthahertz answered 22/10, 2015 at 8:45 Comment(2)
I checked database and I didn't find duplicates thereShawn
Actually problem related with duplication roles when I insert TerminalAdminShawn
G
1

My money's on messing with hashCode/equals overrides and Hibernate proxies.

From EqualsandHashCode

However, once you close the Hibernate session, all bets are off. [...] Hence, if you keep collections of objects around between sessions, you will start to experience odd behavior (duplicate objects in collections, mainly).

First I'd use org.apache.commons.lang3 like so (it's obviously way too expensive for Hibernate entities but works ok w/ them and if it works that should validate my hunch):

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;


@Override
public String toString() {
    return ToStringBuilder.reflectionToString(this);
}

@Override
public int hashCode() {
    return HashCodeBuilder.reflectionHashCode(this);
}

@Override
public boolean equals(Object other) {
    return EqualsBuilder.reflectionEquals(this, other);
}

If this works you could go with a less expensive approach like so:

@Override
public int hashCode() {
    HashCodeBuilder hcb = new HashCodeBuilder();
    hcb.append(role);
    return hcb.toHashCode();
}

@Override
public boolean equals(Object obj) {
    if (this == obj) {
        return true;
    }
    if (!(obj instanceof AdminRole)) {
        return false;
    }
    AdminRole that = (AdminRole) obj;
    EqualsBuilder eb = new EqualsBuilder();
    eb.append(role, that.role);
    return eb.isEquals();
}
Gayle answered 22/10, 2015 at 21:38 Comment(3)
you can still use getters in hashCode/equals methods, no need of reflectionKellerman
@VasilyLiaskovsky you are right but I only mentioned the quick and dirty approach to using apache.common's reflection helpers for hashCode/equals overrides to confirm my hunch...Gayle
@Gayle first solution doesn't change somethingShawn

© 2022 - 2024 — McMap. All rights reserved.