JPA join in spring boot application - java

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"
]
]

Related

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

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)

Not able to map fields of different entities with many to many relationship

DB Schema - 2 tables (user and role) have many to many relationship and are bridged by an intermediate table (user_role) in postgres. I want to fetch all the roles and the name of a person who created it. Name is available in the users table but all the other details are in roles table. Roles table has a field created_by (User_id of the person who created the role).
I am trying to build a GET Request to view all the roles of a given id with the name of the person who created it
Entity class Users1.java
#Data
#Entity
#Table(name = "user")
public class Users1 {
#Id
#Column(name = "user_id")
private Long user_id;
#Column(name = "org_id")
private Long org_id;
#Column(name = "boss")
private Boolean boss;
#Column(name = "firstname")
private String firstname;
#Column(name = "created_by")
private Long created_by;
#CreationTimestamp
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "created_on")
private Date created_on;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {CascadeType.ALL})
#JoinTable(name = "user_role",
joinColumns = {#JoinColumn(name = "user_id")},
inverseJoinColumns = {#JoinColumn(name = "created_by")})
private List<Role> roles = new ArrayList<Role>();
// Getters and Setters
Entity class Role.java
#Data
#Entity
#Table(name = "role")
public class Role implements Serializable {
private static final long serialVersionUID = -2343243243242432341L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "role_id")
private Long role_id;
#Column(name = "org_id")
private Long org_id;
#Column(name = "role_name")
private String role_name;
#Column(name = "created_by")
private Long created_by;
#CreationTimestamp
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "created_on")
private Date created_on;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {CascadeType.ALL},
mappedBy = "roles")
private List<Users1> users = new ArrayList<>();
// Getters and Setters
roles.repository class
#Repository
#Transactional
public interface RolesRepository extends CrudRepository<Role,Long> {
#Query(value ="select r.*,u.firstname as firstname
from user u
join user_role ur on u.user_id = ur.user_id
join role r on ur.role_id = r.role_id
where(true = (select u.boss from user u where u.user_id = ?2) and r.org_id = ?1)
or
(false = (select u.boss from user u where u.user_id = ?2) and r.created_by =?2)",
nativeQuery=true)
public Iterable<Role> findAllById(Long org_id,Long created_by );
#Query("from Users1 u where u.user_id=?2 and u.org_id = ?1")
public Users1 findUserbyOrgId(Long org_id, Long created_by);
}
roles.Controller class :
public ResponseEntity<Iterable<Role>> getAllRoles(#RequestParam("org_id") Long org_id,
#RequestParam("created_by") Long created_by ) throws Exception {
if( null != rolesRepository.findUserbyOrg(org_id, created_by)) {
Iterable<Role> Roles = rolesRepository.findAllById(org_id, created_by); }
GET Response from postman:
[
{
"roleId": 3,
"org_id": 2,
"roleName": "manager",
"createdBy": 5,
"createdOn": 1591716178419,
}
]
I'm getting everything except the firstname. I'm not sure how to fetch that in my GET API. Any help would be really appreciated.
Not a direct answer to your question, but #ManyToMany is generally not encouraged in the real field due to the following reasons.
The joining table may need to have more information (but can't)
Since the joining table is hidden, query result is hard to anticipate.
The recommended approach is to
decompose two #ManyToMany classes to two #OneToMany classes +
one #ManyToOne class (joining table)
elevate the joining table to an entity class.
There are many practices of such cases in stackoverflow and youtube. I think it will ultimately save you more time to switch to this approach.

How to access data in bidirectional many to many mapping

UserDetail.java (model class)
#Entity
public class UserDetail {
#Id
private String email;
private String name;
private String password;
#ManyToMany(cascade = CascadeType.ALL)
private List<Role> role;
#ManyToMany(cascade = CascadeType.ALL,mappedBy = "user")
private List<GroupDetail> group;
}
GroupDetail.java ( model class)
#Entity
public class GroupDetail {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#Column(unique = true)
private String groupName;
#ManyToMany(cascade = CascadeType.ALL)
private List<UserDetail> user;
#ManyToMany(cascade = CascadeType.ALL)
private List<Role> role;
}
Here as you can see GroupDetail is the owner of the association and Hibernate will only check that side when maintaining the association.
So, how do i get List of GroupDetail using the user email?
Following should work for you:
select userDtl.group from UserDetail userDtl where userDtl.email = 'Your Email'
If this does not work, then you could try to use join explicitly like this:
select g from UserDetail u join u.group g where u.email = 'Your Email'
Do this, add getter and setter in both entities.
UserDetails user = em.find(UserDetails.class, emailId);
List<GroupDetail> group = user.getGroup();

JPA hibernate one to many relationship creates extra column name

I have two entities. I want to cascade the insertion of the child entity when the owner entity is persisted and set the SSO_ID of the child entity to the one that was generated for the owner by the generator.
#Entity(name = "USERS")
#Table(name = "USERS")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ID_GENERATOR")
#SequenceGenerator(name = "ID_GENERATOR", sequenceName = "ID_SEQUENCE")
#Column(name = "SSO_ID")
private Long ssoId;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
private List<UserEmail> userEmails = new ArrayList<>();
// getters, setters etc.
}
#Entity(name = "USER_EMAILS")
#Table(name = "USER_EMAILS")
#IdClass(UserEmailId.class)
public class UserEmail {
#Id
#Column(name = "SSO_ID")
private Long ssoId;
#Id
#Column(name = "USER_MAIL")
private String userMail;
#Id
#Column(name = "START_DATE")
private Date startDate;
#Id
#Column(name = "EMAIL_TYPE")
private String emailType;
#ManyToOne(fetch = FetchType.LAZY)
private User user;
// getters, setters etc.
}
The UserEmail ID class is:
public class UserEmailId implements Serializable {
private Long ssoId;
private String userMail;
private Date startDate;
private String emailType;
// getters, setters etc.
}
Instead, I get an error:
insert into hub_users_emails (user_sso_id, email_type, sso_id, start_date, user_mail) values (?, ?, ?, ?, ?)
(etc.)
binding parameter [1] as [BIGINT] - [1234837655] => this is user_sso_id
(etc.)
binding parameter [3] as [BIGINT] - [null] => this is the original sso_id
SQL Error: 904, SQLState: 42000
o.h.engine.jdbc.spi.SqlExceptionHelper : ORA-00904: "USER_SSO_ID": invalid identifier
I've tried some other setups of one to many (bidirectional, unidirectional, etc.) but It seems that this problem persists between all implementations.
Help is appreciated.
As you use #ManyToOne and #OneToMany, hibernate will create user_sso_id on your USER_EMAILS table. I am not sure why do you want another ssoId on USER_EMAILS.
I have removed sso_id from USER_EMAILS and now it's working fine. I know this is not the exact answer of your question. Following code may help you.
#Entity(name = "USERS")
#Table(name = "USERS")
#Setter
#Getter
public class User {
#Id
#SequenceGenerator(name = "ID_GENERATOR", sequenceName = "ID_SEQUENCE")
#GeneratedValue(generator = "ID_GENERATOR" )
#Column(name = "SSO_ID")
private Long ssoId;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "user")
private List<UserEmail> userEmails = new ArrayList<>();
}
#Setter
#Getter
#Entity(name = "USER_EMAILS")
#Table(name = "USER_EMAILS")
#IdClass(UserEmailId.class)
public class UserEmail {
#Id
#Column(name = "USER_MAIL")
private String userMail;
#Id
#Column(name = "START_DATE")
private Date startDate;
#Id
#Column(name = "EMAIL_TYPE")
private String emailType;
#ManyToOne(fetch = FetchType.LAZY)
private User user;
}
#Setter
#Getter
public class UserEmailId implements Serializable {
private String userMail;
private Date startDate;
private String emailType;
}
public class SomeClass{
public User saveUser(){
User user = new User();
UserEmail userEmail = new UserEmail();
userEmail.setUser(user);
userEmail.setEmailType("type");
userEmail.setStartDate(new Date());
userEmail.setUserMail("someEmail#gmail.com");
user.setUserEmails(Arrays.asList(userEmail));
userRepo.save(user);
}
}

JPA - Can an #JoinColumn be an #Id as well? SerializationException occurs

I am trying to use an #JoinColumn as an #Id using JPA and I am getting SerializationExceptions, "Could not serialize."
UserRole.java:
#Entity
#Table(name = "authorities")
public class UserRole implements Serializable {
#Column(name = "authority")
private String role;
#Id
#ManyToOne
#JoinColumn(name = "username")
private User owner;
...
}
User.java:
#Entity
#Table(name = "users")
public class User implements Serializable {
#Id
#GeneratedValue
protected Long id;
#Column(name = "username")
protected String email;
#OneToMany(mappedBy = "owner", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
protected Set<UserRole> roles = new HashSet<UserRole>();
....
}
"username" is set up as a unique index in my Users table but not as the primary key.
Is there any way to make "username" act as the ID for UserRole? I don't want to introduce a numeric key in UserRole. Have I totally lost the plot here?
I am using MySQL and Hibernate under the hood.
That mapping doesn't really make sense. ID has to be unique, but ManyToOne says 'lots of these have the same User.'

Categories

Resources