I have a User entity which has one many to many relationship with Authority table and many to one with UserGroup and another many to one with company.
I am using a Projection Interface named UserDto and trying to fetch all users with the help of entity graph with attributePaths as authorities, company and userGroup but before spring 3 it always gave me distinct parents but now it is doing cartesian product.
I read somewhere that Hibernate 6 automatically de-duplicates the result set. So i tried writing a jpa query by myself using join fetch and adding distinct key word and used User entity instead of project but same result.
@Entity
@Table(name = "USERS")
public class User extends Cacheable<Integer, User> implements AuditableEntity {
private static final long serialVersionUID = -4759265801462008942L;
@Id
@Column(name = "USER_ID", nullable = false)
@TableGenerator(name = "USER_ID", table = "ID_GENERATOR", pkColumnName = "GEN_KEY", valueColumnName = "GEN_VALUE",
pkColumnValue = "USER_ID", allocationSize = 10)
@GeneratedValue(strategy = GenerationType.TABLE, generator = "USER_ID")
private Integer id;
@Column(name = "EMAIL_ID", unique = true, nullable = false, length = 254)
private String emailId;
@JsonIgnore
@Column(name = "PASSWORD", nullable = false, length = 60)
private String password;
@Column(name = "ENABLED", nullable = false)
private boolean enabled = false;
@Column(name = "LOCKED", nullable = false)
private boolean locked = false;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
name = "USER_AUTHORITY",
joinColumns = {@JoinColumn(name = "USER_ID", referencedColumnName = "USER_ID")},
inverseJoinColumns = {@JoinColumn(name = "AUTHORITY_NAME", referencedColumnName = "NAME")})
@NotNull
private Set<Authority> authorities = new HashSet<>();
@NotEmpty
@Size(max = 60)
@Column(name = "FIRST_NAME", length = 60)
private String firstName;
@Column(name = "MIDDLE_NAME", length = 60)
private String middleName;
@NotEmpty
@Size(max = 60)
@Column(name = "LAST_NAME", length = 60)
@FieldDescription(name = "Last Name", order = 1, type = ExcelColumnType.STRING, required = true)
private String lastName;
@NotNull
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "COMPANY_ID")
@FieldDescription(name = "Company", order = 4, type = ExcelColumnType.COMPANY, required = true)
private Company company;
@ManyToOne
@JoinColumn(name = "USER_GROUP_ID")
private UserGroup userGroup;
//hashcode and equals based on id
}
Here is projection interface
public interface UserDto {
Integer getId();
String getEmailId();
String getFirstName();
String getLastName();
@Value("#{target.firstName + ' ' + target.lastName}")
String getFullName();
Company getCompany();
@Value("#{target.userGroup.id}")
Integer getUserGroupId();
@Value("#{target.userGroup.name}")
String getUserGroupName();
}
This is the repository
@Repository
public interface UserRepository extends JpaRepository<User, Integer>, CacheableRepository<User, Integer> {
//other methods..
@EntityGraph(attributePaths = { "authorities", "company", "userGroup" })
<E> List<E> findBy(Class<E> type);
//another way
//@Query("Select u from User u left join fetch u.authorities a left join fetch u.company c left join fetch u.userGroup")
// @QueryHints(value = { @QueryHint(name = org.hibernate.jpa.QueryHints.HINT_PASS_DISTINCT_THROUGH, value = "false")})
//List<User> findBy();
}
Service
@Transactional
public List<UserDto> getAll() {
return userRepository.findBy(UserDto.class);
}
Note(solution): so de-duplication now only works with Set, earlier it worked with list also. Changed method's return type to set in repository.