JPQL query to fetch all roles of user based on user id - java

In my project I have many-to-many relationship between User and Role. For this reason I have also new entity UserRole which connects this two entities.
It looks like this:
User:
#Data
#Entity
#Table(NAME = "USERS")
public class User {
#Id
#Column(name = "USER_ID")
private String userId;
#Basic
#Column(name = "EMAIL")
private String email;
#OneToMany(fetch = LAZY, mappedBy = "user")
private Set<UserRole> userRoles;
}
Role:
#Data
#Entity
#Table(NAME = "ROLES")
public class Role {
#Id
#Column(name = "ROLE_ID")
private String roleId;
#Basic
#Column(name = "NAME")
private String name;
#OneToMany(fetch = LAZY, mappedBy = "role")
private Set<UserRole> userRoles;
}
UserRole:
#Data
#Entity
#IdClass(UserRolePK.class)
#Table(NAME = "USER_ROLES")
public class UserRole {
#Id
#Column(name = "USER_ID")
private String userId;
#Id
#Column(name = "ROLE_ID")
private String roleId;
#ManyToOne(fetch = LAZY)
#JoinColumn(name = "USER_ID", insertable = false, updatable = false)
private User user;
#ManyToOne(fetch = LAZY)
#JoinColumn(name = "ROLE_ID", insertable = false, updatable = false)
private Role role;
}
In this scenario User can have multiple roles.
Question: How to fetch User by his id (userId) with all assigned to him Roles using one query (JPQL)?
I know I can first fetch User by id, and than I can fetched separately Roles based on UserRole table.
But I want to do that in one query. I want to have User with List of Roles.

I would suggest you to correct your mapping in the following way:
#Data
#Entity
#Table(NAME = "USERS")
public class User {
#Id
#Column(name = "USER_ID")
private String userId;
#Column(name = "EMAIL")
private String email;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "USER_ROLES",
joinColumns = #JoinColumn(name = "USER_ID"),
inverseJoinColumns = #JoinColumn(name = "ROLE_ID"))
private Set<Role> roles;
}
#Data
#Entity
#Table(NAME = "ROLES")
public class Role {
#Id
#Column(name = "ROLE_ID")
private String roleId;
#Column(name = "NAME")
private String name;
#ManyToMany(fetch = FetchType.LAZY, mappedBy = "roles")
private Set<User> users;
}
This correction will not affect the database schema only hibernate mapping.
Then you will be able to do as suggested in the Andronicus answer:
#Query(
"select u " +
"from User u " +
"left join fetch u.roles "+
"where u.userId = :id "
)
List<User> getUsersWithFetchedRoles(#Param("id") String id)
If you stay with your current mapping you will not be able to fetch more than one association at a time as it is explained in this article.
Additional details related to the #ManyToMany association see in the documentation.

You can use the fetch keyword:
#Query(
"select u " +
"from User u " +
"left join fetch u.userRoles "+
"where u.userId = :id "
)
List<User> getUsersByIdAndRoles(#Param("id") String id)

Related

Spring Boot - combine nested resources for single API calls

Suppose you have two resources, User and Account. They are stored in separate tables but have a one-to-one relationship, and all API calls should work with them both together. For example a POST request to create a User with an Account would send this data:
{ "name" : "Joe Bloggs", "account" : { "title" : "My Account" }}
to /users rather than have multiple controllers with separate routes like users/1/account. This is because I need the User object to be just one, regardless of how it is stored internally.
Let's say I create these Entity classes
#Table(name = "user")
public class User {
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
#NotNull
Account account;
#Column(name = "name")
String name;
}
#Table(name = "account")
public class Account {
#OneToOne(cascade = CascadeType.ALL, optional = false, fetch = FetchType.LAZY)
#JoinColumn(name = "user_id", nullable = false)
#NotNull
User user;
#Column(name = "title")
String title;
}
The problem is when I make that POST request above, it throws an error because user_id is missing, since that's required for the join, but I cannot send the user_id because the User has not yet been created.
Is there a way to create both entities in a single API call?
Since it is a bi-directional relation, and one-to-one is a mandatory in this case, you should persist a user entity and only then persist an account. And one more thing isn't clear here is db schema. What are the pk's of entities? I coukd offer to use user.id as a single identity for both of tables. If so, entities would be as:
User(id, name), Account(user_id, title) and its entities are:
#Table(name = "account")
#Entity
public class Account {
#Id
#Column(name = "user_id", insertable = false, updatable = false)
private Long id;
#OneToOne(mappedBy = "account", fetch = FetchType.LAZY, optional = false)
#MapsId
private User user;
#Column(name = "title")
private String title;
}
#Table(name = "user")
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;;
#OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
#JoinColumn(name = "id", referencedColumnName = "user_id")
private Account account;
#Column(name = "name")
private String name;
}
at the service layer you must save them consistently:
#Transactional
public void save(User userModel) {
Account account = user.getAccount();
user.setAccount(null);
userRepository.save(user);
account.setUser(user);
accountRepository.save(account);
}
it will be done within a single transaction. But you must save the user first, coz the user_id is a PK of the account table. #MapsId shows that user's id is used as an account's identity
Another case is when account's id is stored in the user table:
User(id, name, account_id), Account(id, title) and entities are:
#Table(name = "account")
#Entity
public class Account {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToOne(cascade = CascadeType.ALL, mappedBy = "account")
private User user;
#Column(name = "title")
private String title;
}
#Table(name = "user")
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "account_id", insertable = false, updatable = false)
private Long accountId;
#OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
#JoinColumn(name = "account_id", referencedColumnName = "id", unique = true)
private Account account;
#Column(name = "name")
private String name;
}
in this case an Account entity will be implisitly persisted while User entity saving:
#Transactional
public void save(User userModel) {
userRepository.save(user);
}
will cause an insertion into the both of tables. Since cascade and orphane are declared, for deletion would be enough to set null for the account reference:
user.setAccount(null);
userRepository.save(user);

retrieving data from many to many extra column in spring jpa uisng an id

I have user entity and category entity and they are as followed.One user can subscribe to many categories and similarly same category can be subscribed by many users.
#Data
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(name="users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long userId;
#NotBlank(message = "username can't be blank")
private String username;
#Email
#NotEmpty(message = "Email is required")
#Column(unique=true)
private String email;
#NotBlank(message = "Password is required")
private String password;
// Each user is going to be mapped to a Location
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(
name = "location_id",
referencedColumnName = "locationId"
)
#NotNull
private Location location ;
#EqualsAndHashCode.Exclude
#ToString.Exclude
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST)
#JoinTable(name = "users_categories",
joinColumns = {
#JoinColumn(name = "user_id", referencedColumnName = "userId",
nullable = false, updatable = false)},
inverseJoinColumns = {
#JoinColumn(name = "category_id", referencedColumnName = "categoryId",
nullable = false, updatable = false)})
private Set <Categories> categories = new HashSet<>();
}
And categories entity
#Data
#AllArgsConstructor
#Entity
#Table(name="categories")
public class Categories {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long categoryId;
#NotBlank(message = "Please Add Category name ")
#Length(max =100 ,min =2)
private String categoryName ;
#Length(max=500,min=2)
private String categoryDescription;
#EqualsAndHashCode.Exclude
#ToString.Exclude
#ManyToMany(mappedBy = "categories", fetch = FetchType.EAGER)
private Set<User> users = new HashSet<>();
}
and I have this table where user and categories are mapped .
mysql> select * from users_categories where category_id=5;
+---------+-------------+
| user_id | category_id |
+---------+-------------+
| 3 | 5 |
| 7 | 5 |
+---------+-------------+
2 rows in set (0.00 sec)
I want to retrieve all userId with a specific category. How do we achieve this in spring boot and JPA?
Here is my user repository
import java.util.Optional;
#Repository
public interface UserRepository extends JpaRepository<User, Long>{
Optional<User> findByUsername(String username);
}
I didn't test it but try to add this to your UserRepository:
#Query("select u from User u inner join u.categories c where c.categoryId =:id")
List<User> findByCategory(#Param("id") Long id);

Spring boot #JoinCulumn ManyToOne relationship column does not exist

I currently have a problem with this Relationship, I have tried everything I saw on the internet. Still, I get this error: ERROR: column roles0_.user_id does not exist.
I have a boot app that has spring security, and I need to login using users from PostgreSQL database.
But I just can't get the relation between the user and the Role to work.
Here are Entity classes:
#Data
#Entity
#Table(name="user",schema = "public")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id")
private Integer id;
#Column(unique = true)
private String username;
private String password;
private boolean enabled;
#OneToMany(mappedBy = "user", fetch = FetchType.EAGER)
private List<Role> roles;
}
#Data
#Entity
#Table(name="role",schema = "public")
public class Role {
#Id
#Column(name="role_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
#ManyToOne()
#JoinColumn(name ="user_id")
private User user;
}
The database looks fine, I looked at the column names, etc. I don't know what to do to get rid of this error. I have the user table, and another table named roles, which include id and name, 2 inputs, USER and ADMIN...
It seems that the #JoinColumn annotation requires one additional column in the roles table the one with #ManytoOne relation, because when I add the column the error disappears, but when I'm trying to get the role from each user, I get an empty List. The foreign key is set as well, from the roles column to the role_id column from role table.
worked for me this way:
#Entity
#Data
#Table(name = "users")
public class User{
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "user_roles", joinColumns = {#JoinColumn(name = "user_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "role_id", referencedColumnName = "id")})
private List<Role> roles;
}
and then in roles just:
#Entity
#Table(name = "roles")
public class Role{
#ManyToMany(mappedBy = "roles", fetch = LAZY)
private List<User> users;
}
that's if you are ok with third table user_roles (user_id, role_id) which manages the many to many relation
User table :
#OneToMany(cascade = CascadeType.ALL, mappedBy = "user", fetch = FetchType.EAGER)
private List<Role> roles;
Role table :
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User user;

How to get rid of cyclic redundancy while having #ManyToMany relation JPA springboot

I am a newbie to the Spring boot (but worked in Laravel). I am facing a problem of cyclic redundancy in #ManyToMany relation. Let's go through the scenario -
What response I ma getting (fetching user's list which has many to many relationships with roles) -
Following is the ER-diagram of associated tables to manage many to many relationship between users and roles table.
User entity class has following code -
#Entity
#Where(clause = "deleted_at IS NULL")
#SQLDelete(sql = "UPDATE users SET deleted_at = CURRENT_TIMESTAMP WHERE id = ?", check = ResultCheckStyle.COUNT)
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Table(name = "users")
#JsonIgnoreProperties(
value = {"createdAt", "updatedAt", "deletedAt"}
)
public class User {
#Id
#Column(name = "id", updatable = false, nullable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "name", nullable = false)
#NotBlank(message = "Name field can not be empty")
private String name;
.....
.....
.....
#ManyToMany(targetEntity = Role.class, fetch = FetchType.EAGER)
#JoinTable(name = "user_roles",joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles;
}
And Role entity is as follows -
#Entity
#Table(name = "roles")
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#SQLDelete(sql = "UPDATE roles SET deleted_at = CURRENT_TIMESTAMP WHERE id = ?", check = ResultCheckStyle.COUNT)
#Where(clause = "deleted_at IS NULL")
#JsonIgnoreProperties(
value = {"createdAt", "updatedAt", "deletedAt"}
)
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", updatable = false, nullable = false)
private long id;
#Column(name = "title")
#NotBlank(message = "Title field must be not null")
private String title;
......
......
......
#OneToMany(targetEntity = User.class, fetch = FetchType.EAGER)
#JoinTable(name = "user_roles",joinColumns = #JoinColumn(name = "role_id"),
inverseJoinColumns = #JoinColumn(name = "user_id"))
private List<User> users;
}
How to solve this problem? What I am doing wrong here?
Since you are fetching the list directly. You will have to mention the annotation #JsonIgnore everywhere you have mapping specified. By everywhere I don't mean literally everywhere. Just use the annotation and see how it works.
Edit -> Just do it in roles table where you have mapped it to the user table. It will then skip the user mapping while fetching the data.
#JsonIgnore
private List<User> users;
You could annotate users within Role with #JsonBackReference.
Easiest would probably be to annotate the List<T>'s with a #JsonIgnoreProperties annotation to break the cyclic dependencies.
#JsonIgnoreProperties("users")
private List<Role> roles;
#JsonIgnoreProperties("roles")
private List<User> users;

JPA join in spring boot application

I've read examples but have my personal question to you.
I have 2 tables:
Role:
id, name
User:
id, login, name, role_id
Role entity
#Entity
#Table(name = "role")
public class Role {
#Id
#Column(name = "id")
private long id;
#Column(name = "name", length = 45)
private String name;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "role")
private Set<User> user = new HashSet<>();
//getters and setters
User entity
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id",insertable = false, updatable = false)
private long id;
#Column(name = "login")
private String login;
#Column(name = "user_name")
private String userName;
#ManyToOne(fetch = FetchType.LAZY)
private Role role;
//getters and setters
And repository:
public interface UserRepository extends JpaRepository<User, Long> {
String Q_GET_ALL_USERS = "from User u left join Role r on u.role_id=r.id";
#Query(Q_GET_ALL_USERS)
Collection<User> getAllUsers();
This code is showing: Caused by: java.lang.IllegalArgumentException: org.hibernate.hql.internal.ast.QuerySyntaxException: Path expected for join! [from com.example.jpa.model.User u left join Role r on u.role_id=r.id]
How I understand entity can't contains 'id' (in my case in Role) for references and I should remove this field. But entity should have '#Id'.
In this case I should create new column in 'Role'? or I can use more beautiful decision?
I put all project to bb
To use join in HQL (JPQL) you don't need on clause
String Q_GET_ALL_USERS = "select u from User u left join u.role";
This query doesn't have any sence because of you don't use role in the where clause.
If you want to get users with a fetched role you can use join fetch
String Q_GET_ALL_USERS = "select u from User u left join fetch u.role";
Update
Your schema for User and Role is not commonly used. I advice to you make #ManyToMany association from user to roles and remove any user association from the Role
#Entity
#Table(name = "user")
public class User {
#ManyToMany(fetch = FetchType.LAZY)
private Set<Role> roles;
}
#Entity
#Table(name = "role")
public class Role {
#Id
#Column(name = "id")
private long id;
#Column(name = "name", length = 45)
private String name;
}
No, you should create a new column in User.
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "role_id")
private Role role;
Thank you all for answers. Right entities and query below (plus tables schema).
Tables (queries)
CREATE TABLE role (
id INT NOT NULL PRIMARY KEY,
name VARCHAR(45) NOT NULL
);
CREATE TABLE user (
id INT NOT NULL PRIMARY KEY IDENTITY,
login VARCHAR(45) NOT NULL,
user_name VARCHAR(45) NOT NULL,
role_id INT NOT NULL,
FOREIGN KEY (role_id) REFERENCES role (id)
);
Entities:
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id",insertable = false, updatable = false)
private long id;
#Column(name = "login")
private String login;
#Column(name = "user_name")
private String userName;
#ManyToOne(fetch = FetchType.LAZY)
private Role role;
//getters and setters
}
and
#Entity
#Table(name = "role")
public class Role {
#Id
#Column(name = "id")
private long id;
#Column(name = "name", length = 45)
private String name;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "role")
private Set<User> user = new HashSet<>();
//getters and setters
}
Repository
public interface UserRepository extends JpaRepository<User, Long> {
String Q_GET_ALL_USERS = "select u from User u left join u.role";
#Query(Q_GET_ALL_USERS)
Collection<User> getAllUsers();
}
#v-ladynev proposed alternative decision(use only #ManyToMany in User). More details you can find in comments under this answer.
When I check this decision I will update this answer (I hope I don't forget it :-))
Models
#Entity
#Table(name = "sys_std_user")
public class StdUser {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "class_id")
public int classId;
#Column(name = "user_name")
public String userName;
}
#Entity
#Table(name = "sys_std_profile")
public class StdProfile {
#Id
#Column(name = "pro_id")
public int proId;
#Column(name = "full_name")
public String fullName;
}
Controllers
#PersistenceUnit
private EntityManagerFactory emf;
#GetMapping("/join")
public List actionJoinTable() {
EntityManager em = emf.createEntityManager();
List arr_cust = em
.createQuery("SELECT u.classId, u.userName, p.fullName FROM StdUser u, StdProfile p WHERE u.classId=p.proId")
.getResultList();
return arr_cust;
}
Result:
[
[
1,
"Ram",
"Ram Pukar Chaudhary"
],
[
2,
"Raja",
"Raja Kishor Shah"
]
]

Categories

Resources