Search code examples
jpajpql

How to query one-to-many relationship in JPQL?


There is a user entity having one-to-many relationship with role entity. In other words, one user can have multiple roles. Anyhow when I query using JPQL, the Role entity is showing duplicate results, in this case, it shows ROLE_ADMIN twice. When I debug the code, I can see that the Role entity is having same memory id:

[org.huahsin.Role@1b332d22, org.huahsin.Role@1b332d22]

I wasn't sure whether I have did something wrong in JPQL. Please correct me if I'm wrong. The following are some hints on this problem.


This is the content available in Users table:

+----------+----------+---------+
| username | password | enabled |
+----------+----------+---------+
| user7    | user7    |       1 |
+----------+----------+---------+

This is the table description for Users table:

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

This is the content available in Authority table:

+----------+------------+
| username | authority  |
+----------+------------+
| user7    | ROLE_ADMIN |
| user7    | ROLE_USER  |
+----------+------------+

This is the table description for Authority table:

+-----------+-------------+------+-----+---------+-------+
| Field     | Type        | Null | Key | Default | Extra |
+-----------+-------------+------+-----+---------+-------+
| username  | varchar(10) | NO   | MUL | NULL    |       |
| authority | varchar(10) | NO   |     | NULL    |       |
+-----------+-------------+------+-----+---------+-------+

This is the query on User entity:

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

    @Id
    private String username;

    private String password;

    private Integer enabled;

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

This is the Role Entity:

@Entity
@Table(name="authority")
public class Role {

    @Id
    @Column(name="username")
    private String userId;

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

This is how I retrieve the results:

List<User> l = emf.createEntityManager().createNamedQuery("findByUser", User.class).setParameter("username", username).getResultList();
for( User u : l ) {
    System.out.println(u.getUsername());
    System.out.println(u.getRoleList());
}

Solution

    • It's not clear what you are trying to achieve by this query. If you want to load a user by its username, and username is a primary key, you can do it as follows:

      User u = em.find(User.class, username);
      
    • You get incorrect results because your Role entity is mapped incorrectly: userId is annotated as @Id, but it's not a primary key (i.e. it's not unique).

    I'd suggest you to change the database schema. A many-to-many relationship between User and Role (with join table) would be much easier to map:

    public class User {
        ...
        @OneToMany
        @JoinTable(name = "users_authority")
        private List<Role> roleList;
        ...
    }
    
    public class Role {
        @Id 
        private String authority;
    }
    

    If the current database schema is fixed, you'll need to:

    • Express the fact that authority has composite key (username, authority) using @EmbeddedId
    • Make your relationship bidirectional
    • Use @MapsId to indicate the fact that foreign key authority(username) is a part of the composite key of authority

    See also: