Thursday, December 26, 2013

Resolving duplicate collection reference with @NamedQuery

Considering a scenario where I have 2 tables, one table called user, the other table call authority. There is one-to-many relationship, which means each user could have multiple roles. Table below illustrate how this relationship is constructed in database.

Users table

+----------+-------------+------+-----+---------+-------+
| Field    | Type        | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+-------+
| username | varchar(10) | NO   | PRI |         |       |
| password | varchar(32) | YES  |     | NULL    |       |
| enabled  | int(11)     | YES  |     | NULL    |       |
+----------+-------------+------+-----+---------+-------+

Authority table

+-----------+-------------+------+-----+---------+-------+
| Field     | Type        | Null | Key | Default | Extra |
+-----------+-------------+------+-----+---------+-------+
| username  | varchar(10) | NO   | MUL | NULL    |       |
| authority | varchar(10) | NO   |     | NULL    |       |
+-----------+-------------+------+-----+---------+-------+ 
On Java site, each table is being mapped to the corresponding Java entity as illustrate below:

User entity

@NamedQuery(
 name="findByUser",
 query="select u from User u join u.roleList r where r.userId = :username ")
@Entity
@Table(name="users")
public class User {
 ...

 @OneToMany(targetEntity = Role.class, cascade = {CascadeType.ALL})
 @JoinColumn(name="username")
 private List roleList;

 ...

Role entity

@Entity
@Table(name="authority")
public class Role {
 ...
With this setup, I don't see any defect in the first run because the test data are a combination of one user pair with one role. The problem comes only when there is one user pair with two roles, I notice that the roleList will contain duplicate data. To prove my statement, I did some scanning on the data retrieve from the query.
  ...

  User theUser = emf.createEntityManager().createNamedQuery("findByUser", User.class).setParameter("username", username).getSingleResult();
  System.out.println(theUser.getRoleList());
  ...
Code snippet above shows that theUser.getRoleList() is having duplicate data as it is showing the same memory location [org.huahsin.Role@1b332d22, org.huahsin.Role@1b332d22] in the console. This problem is due to the Authority table is missing primary key. Add a primary key into Authority table will fix this problem. A lesson learn, whenever working with JPA, it is best to have the primary key declare in database to allow JPA to have a clear view on the relationship.

No comments: