Spring boot lazy collection not loaded properly - spring-boot

I have 3 entities:
User, Shelter and ShelterUsers
ShelterUsers is a join table for many to many relationship between Shelter and User with additional column. (design based on this suggestion)
I also have a #OneToMany relationship from User to Role which also uses a join table but without an additional field so there is no entity for that table
When I get the User from the database Roles are also attached with a join if I set the fetch to EAGER and they're loaded with additional query if I set fetch to LAZY (even without calling the user.getRoles() explicitly, which I also don't understand).
Problem is that ShelterUsers are not attached to user, no matter if I set it to EAGER or LAZY. After inspecting the sql that hibernate generates it seems like it generates the wrong sql:
First query:
SELECT user0_.id AS id1_7_0_,
user0_.created_at AS created_2_7_0_,
user0_.email AS email3_7_0_,
user0_.first_name AS first_na4_7_0_,
user0_.last_name AS last_nam5_7_0_,
user0_.password AS password6_7_0_,
user0_.updated_at AS updated_7_7_0_,
user0_.username AS username8_7_0_,
roles1_.user_id AS user_id1_8_1_,
role2_.id AS role_id2_8_1_,
role2_.id AS id1_4_2_,
role2_.name AS name2_4_2_
FROM users user0_
LEFT OUTER JOIN users_roles roles1_ ON user0_.id=roles1_.user_id
LEFT OUTER JOIN ROLES role2_ ON roles1_.role_id=role2_.id
WHERE user0_.id=?
Second query:
SELECT shelters0_.shelter_id AS shelter_3_6_0_,
shelters0_.id AS id1_6_0_,
shelters0_.id AS id1_6_1_,
shelters0_.shelter_id AS shelter_3_6_1_,
shelters0_.user_id AS user_id4_6_1_,
shelters0_.user_role AS user_rol2_6_1_,
user1_.id AS id1_7_2_,
user1_.created_at AS created_2_7_2_,
user1_.email AS email3_7_2_,
user1_.first_name AS first_na4_7_2_,
user1_.last_name AS last_nam5_7_2_,
user1_.password AS password6_7_2_,
user1_.updated_at AS updated_7_7_2_,
user1_.username AS username8_7_2_
FROM shelter_users shelters0_
LEFT OUTER JOIN users user1_ ON shelters0_.user_id=user1_.id
WHERE shelters0_.shelter_id=?
Where clause should be WHERE user1_.id = ?
Here is all relevant code:
User:
#Entity
#Data
#Table(name = "users")
public class User {
...
#OneToMany(fetch = FetchType.EAGER)
#JoinTable(name = "users_roles",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "role_id", referencedColumnName = "id")
)
private Collection<Role> roles = new ArrayList<>();
#OneToMany(mappedBy = "shelter")
private Collection<ShelterUsers> shelters = new ArrayList<>();
...
}
Shelter:
#Entity
...
#Table(name = "shelters")
public class Shelter {
...
#OneToMany(mappedBy = "user")
private List<ShelterUsers> users = new ArrayList<>();
}
ShelterUsers:
#Entity
public class ShelterUsers {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
#JoinColumn(name = "user_id", referencedColumnName = "id")
private User user;
#ManyToOne
#JoinColumn(name = "shelter_id", referencedColumnName = "id")
private Shelter shelter;
#Enumerated(EnumType.STRING)
#Column(name = "user_role")
private ShelterUserRole userRole;
}
Recap:
Roles get loaded with user no matter the fetch type.
ShelterUsers are not loaded
JSON example:
{
...
"roles": [
{
"id": 1,
"name": "ROLE_ADMIN"
}
],
"shelters": [],
...
}
What I would ideally want to achieve is this:
{
"roles": [
{
"id": 1,
"name": "ROLE_ADMIN"
}
],
"shelters": [
{
//Shelter entity (not ShelterUser)
},
]
}

Related

how can i make query with jpa many to many and list of condition must be all true

I have 2 entities User and Prestation with many to many relationship. I want to get all users having specific Prestation like a list of ID Prestation
my query :
#Query("SELECT distinct u FROM User u JOIN u.prestation p where p.id In (:prestationsList)")
public List<User> findAllUser(#Param("prestationsList") List<Integer> prestationsList);
But this did not give me the correct data, because (In) return user if one element of my prestationList is true
my User :
#Entity
public class User {
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "users_prestations",
joinColumns = {#JoinColumn(name = "user_id")},
inverseJoinColumns = {#JoinColumn(name = "prestation_id")})
private Set<Prestations> prestation = new HashSet<>();
}
my Prestations :
#Entity
public class Prestations {
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "prestation")
private Set<User> user = new HashSet<>();
}
How should I write this query in JPA?
try using this JPQL query
#Query("SELECT * FROM User u left join u.prestation p where p.id In (:prestationsList)")
public List<User> findAllUser(#Param("prestationsList") List<Integer> prestationsList);
if this doesn't work try a native query

How to store userid, roleid and privilegeid in single table using #JoinTable?

Requirement : Create table using #JoinColumn annotation in User entity class, which contain columns like, user_id, authority_id(means role), privilege_id.
Condition: Authority and Privileges also have one to many relationship.
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "pm_authority_privilege",
joinColumns = #JoinColumn(name = "authority_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "privilege_id", referencedColumnName = "id"))
private Set<Privilege> privileges = new HashSet<>();
As per above configuration i got privilege as part of authority json.
Issue : I'm confused what should be variable type if i take two value in 'inverseJoinColumns'.
When i try to do same thing using following way it gives error of transactional object because privilege is exist on two place.
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "pm_user_authority_privilege",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = {
#JoinColumn(name = "authority_id", referencedColumnName = "id")
// #JoinColumn(name = "privilege_id", referencedColumnName = "id")
})
private Set<Authority> authorities = new HashSet<>();
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "pm_user_authority_privilege",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "privilege_id", referencedColumnName = "id"))
private Set<Privilege> privileges = new HashSet<>();
Note : There is page which have no. of roles and all relevant privilege of those role.(handle using code define in issue)
And from that i select some of role and save them when user created.
{
"authorities": [
{
"authorityId": "ec071816-31e6-11ea-bae1-0242ac110005",
"authorityName": "MANAGER",
"description": null,
"privileges": [
{
"description": "",
"privilege": "CREATE_USER",
"privilegeId": "ba9a4952-4d53-42e9-94fe-8373d334819e"
},
{
"description": null,
"privilege": "SHOW_DATA",
"privilegeId": "ec06de1b-31e6-11ea-bae1-0242ac110005"
}
]
}
],
"email": "test#gmail.com",
"password": "test#123",
"phoneNo": "8575456595",
"status": true,
"userId": null,
"username": "test"
}
i found something. It is not up to the mark but it help solve your problem.
As per i understand you need two kind of relationship,
Authority & Privileges
User & Authority & Privileges (Privileges also exist in Authority object)
In such case you have to follow this,
Create one UserDTO which access user info and authority JSON (So privilege not show two times).
Based on that JSON create two entity and their object. One for User and another for UserAuthorityPrivilege.
UserAuthorityPrivilege is the table which contain your three required data and as obvious all three are required so define them as embedded key.
Give the relation between user and UserAuthorityPrivilege table
Embaded Class :
#Data
#Embeddable
#NoArgsConstructor
#AllArgsConstructor
public class EmbeddedUserAccess implements Serializable {
#Column(name = "user_id") private UUID userId;
#Column(name = "authority") private String authority;
#Column(name = "privilege") private String privilege; }
Entity Class
#Entity
#Table(name = "pm_user_authority_privilege")
#Data
#NoArgsConstructor
#AllArgsConstructor
public class UserAuthorityPrivilege {
#EmbeddedId
private EmbeddedUserAccess id;
#Null
#Column(name = "store_id", nullable = true)
private UUID storeId;
}
DTO:
#Data
#NoArgsConstructor
#AllArgsConstructor
public class UserDTO {
private UUID userId;
private String username;
private String email;
private String phoneNumber;
private String password;
private Boolean status;
private Authority authority;
}
Fetch:
UserAuthorityPrivilege access = null;
EmbeddedUserAccess embedded = null;
Set<UserAuthorityPrivilege> accessibilities = new HashSet<>();
Authority authority = userDTO.getAuthority();
for (Privilege privilege : authority.getPrivileges()) {
access = new UserAuthorityPrivilege();
embedded = new EmbeddedUserAccess();
embedded.setUserId(user.getUserId());
embedded.setAuthority(authority.getAuthority());
embedded.setPrivilege(privilege.getPrivilege());
access.setId(embedded);
accessibilities.add(access);
}
You have to update code similerly.
I have one query, your user relate with single authority or multiple.
For more brief description add comment.

Inner join in spring boot data jpa

I am using spring boot data jpa 1.4 and I'm fairly new to it.
My table definition is here. Its fairly simple, there are 2 tables (Groups and Users).
The group table contains group_id(primary key), group_name, group_active(values=Y/N).
The group table can ideally have only one row which is has group_active to 'Y', the rest should have 'N'
The user table contains user_id(primary key), user_name, group_id(foreign key from group).
Following are my entity classes
Group:
#Entity
#Table(schema = "HR", name = "GROUPS")
public class Group {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "GROUP_ID")
private Long id;
#Column(name = "GROUP_NAME")
private String name;
#Column(name = "GROUP_ACTIVE")
private String active;
User:
#Entity
#Table(schema = "HR", name = "USERS")
public class User {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "USER_ID")
private Long id;
#Column(name = "USER_NAME")
private String name;
#Column(name = "GROUP_ID")
private Long groupId;
#ManyToMany
#JoinTable(
schema = "HR",
name = "GROUPS",
joinColumns = {#JoinColumn(table = "GROUPS", name = "GROUP_ID", insertable = false, updatable = false)},
inverseJoinColumns = {#JoinColumn(table = "USERS", name = "GROUP_ID", insertable = false, updatable = false)}
)
#WhereJoinTable(clause = "GROUP_ACTIVE='Y'")
private List<Group> group;
Repository class:
public interface UserRepository extends CrudRepository<User, Long>{
List<User> findByName (String name);
}
Query: This is the query I want to execute, which is a simple inner join.
SELECT U.*
FROM HR.USER U, HR.GROUP G
WHERE U.GROUP_ID=G.GROUP_ID
AND G.GROUP_ACTIVE='Y'
AND U.USER_NAME=?
What would be the correct way to write the #JoinTable or #JoinColumn such that I always get back one user that belongs to the active group with the name ?
I have done some tests based on your set-up and the solution would need to use filters (assuming there is only one Group with Group_Activity = 'Y'):
Group Entity
#Entity
#Table(schema = "HR", name = "GROUPS")
public class Group {
#OneToMany(mappedBy = "group")
#Filter(name = "activityFilter")
private Set<User> users;
User Entity
#Entity
#Table(schema = "HR", name = "USERS")
#FilterDef(name="activityFilter"
, defaultCondition="group_id =
(select g.id from groups g where g.GROUP_ACTIVE='Y')")
public class User {
#ManyToOne
#JoinColumn(name = "group_id")
private Group group;
When making a query
session.enableFilter("activityFilter");
session.createQuery("select u from Group g inner join g.users u where u.user_name = :userName");
Additionally if there are many groups with activity = 'Y' then try this:
#FilterDef(name="activityFilter"
, defaultCondition="group_id in
(select g.id from group g where g.GROUP_ACTIVE='Y')")

How do I write this JPA query that requires a JOIN?

I'm using JPA 2.0, Hibernate 4.1.0.Final, and Spring 3.1.1.RELEASE. I have two entities:
#Entity
#Table(name = "user",
uniqueConstraints = { #UniqueConstraint(columnNames = { "USER_NAME" }) }
)
public class User implements Comparable<User>, Serializable
{
...
#Column(name = "first_name")
#NotNull
/* the first name of the User */
private String firstName;
and
#Entity
#Table(name="code_user",
uniqueConstraints = {
#UniqueConstraint(columnNames = { "SAMPLE_WORD_ID", "USER_ID" }) }
)
public class CodeUser
{
#Id
#NotNull
#GeneratedValue(generator = "uuid-strategy")
#Column(name = "ID")
private String id;
#ManyToOne
#JoinColumn(name = "CODE_ID", nullable = false, updatable = true)
private Code code;
#ManyToOne
#JoinColumn(name = "USER_ID", nullable = false, updatable = true)
private User user;
How do I write a JPA/CriteriaBuilder query to find all User objects who's first name matches "Dave" and who are tied to a Code record in which the code is "abode"?
I might have missed an HQL syntax element
SELECT user FROM CodeUser codeUser
JOIN FETCH codeUser.user user
JOIN FETCH codeUser.code code
WHERE user.firstName = 'Dave' AND code.value = 'abode'
assuming Code has a field value holding the value "abode". You might not need the FETCH.
You can always replace the literal values with a placeholder like ? or a named placeholder like :name and set their values from the Query object.

JPA many to many - do i have to remove\add from both collection sets?

I have a User entity,
And a Department Entity
i want to have #manyToMany relationship between them :
many users can have many departments.
in my User entity:
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "UserDepartments", joinColumns = { #JoinColumn(name = "user_id", referencedColumnName = "id") }, inverseJoinColumns = { #JoinColumn(name = "department_id", referencedColumnName = "id") })
private Set<Department> departments;
and my Department entity has a SET as well of users..
my question is:
if i need to implement the method :
public void removeUserFromDepartment(User user, Department department) {
//bla bla
}
do i have to call
department.getUserCollection.remove(user);
AND
user.getDepartmentCollection.remove(department);
Or is there a way to maintain this logic by only removing one of them ?
If i have to save both its pretty hard to maintain especially for someone who doesn't know about the many to many relation of the two entities..
When a OneToMany or ManyToMany relationship exists in JPA the client code is responsible for managing the relationship. This means that you must explicitly remove the object from both sides of the relationship.
So lets say you have a User instance and need to remove a department.
User user = dao.findUser(1L); //Find a user, psuedo code
Department dept = user.getDepartments().get(0);
dept.getUsers().remove(user);
user.getDepartments().remove(user);
dao.update(user); //once again psuedo code
To use the code above you should add a cascade type to the relationship in the user entity:
#ManyToMany(fetch = FetchType.LAZY, cascade=CascadeType.ALL)
#JoinTable(name = "UserDepartments", joinColumns = { #JoinColumn(name = "user_id", referencedColumnName = "id") }, inverseJoinColumns = { #JoinColumn(name = "department_id", referencedColumnName = "id") })
private Set<Department> departments;
This will cause saves on the User entity to be cascaded to the Departments entity. Just a reminder save is psuedo code, it will boil down to a call on the EntityManager such as persist or merge.

Resources