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

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

Related

Spring boot lazy collection not loaded properly

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)
},
]
}

JPA Repository - Getting duplicates in List caused by table joins

I am having a very difficult situation and yet the situation is very complex and hard to find similar case in stackoverflow.
I have the following entities
Store
#Data
#Entity
#Table(name = "store")
public class Store implements IModel {
#Id
#EqualsAndHashCode.Exclude
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#Enumerated(EnumType.STRING)
#Column(name = "storestatus", nullable = false)
private StoreStatus storeStatus = StoreStatus.UNKNOWN;
#OneToOne
#JoinColumn(name = "storetypecode_id")
private StoreTypeCode storeTypeCode;
#OneToOne(cascade = CascadeType.ALL, mappedBy = "store")
private Address address;
#Setter(AccessLevel.NONE)
#EqualsAndHashCode.Exclude
#OneToMany(fetch = FetchType.EAGER, mappedBy = "store")
private Set<StoreTranslation> storeTranslationList = new HashSet<>();
public Store() {
}
StoreTypeCode
#Data
#Entity
#Table(name = "storetypecode")
public class StoreTypeCode implements IModel {
#Id
#EqualsAndHashCode.Exclude
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#Column(name = "displaysort", nullable = false)
private Integer displaySort = 999;
#Setter(AccessLevel.NONE)
#EqualsAndHashCode.Exclude
#OneToMany(fetch = FetchType.EAGER, mappedBy = "storeTypeCode")
private Set<StoreTypeCodeTranslation> storeTypeCodeTranslationList = new HashSet<>();
public StoreTypeCode() {
}
}
And StoreCategory
#Data
#Entity
#Table(name = "storeitemcategory")
public class StoreItemCategory implements IModel {
#Id
#EqualsAndHashCode.Exclude
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#ManyToOne
#JoinColumn(name = "store_id")
private Store store;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
name = "storeitemcategory_storeitem",
joinColumns = {#JoinColumn(name = "storeitemcategory_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "storeitem_id", referencedColumnName = "id")})
private List<StoreItem> storeItems = new ArrayList<>();
#Setter(AccessLevel.NONE)
#EqualsAndHashCode.Exclude
#OneToMany(fetch = FetchType.EAGER, mappedBy = "storeItemCategory")
private Set<StoreItemCategoryTranslation> storeItemCategoryTranslationList = new HashSet<>();
public StoreItemCategory() {
}
public void addStoreItem(StoreItem storeItem) {
this.storeItems.add(storeItem);
}
}
With the above relationship, here is what I have.
Store A with storeTypeCode ("Cafe") and storeItemCategory ("Iced drinks")
StoreTypeCode has two translations 1) for English, 2) for Chinese.
Whenever I add an item to storeItems in StoreItemCategory, I get duplicates in the list. (And multiple duplicate records are inserted to 'storeitemcategory_storeitem' table.)
StoreItemCategory sic = storeItemCategoryRepository.findById(storeItemCategoryid).get();
sic.addStoreItem(new StoreItem(...));
sic = storeItemCategoryRepository.save(sic);
I suspect this has something to do with the way tables are joined for translations because when I run a query created from Spring for getting StoreItemCategory, I get multiple records of StoreItemCategory (one for English and one for Chinese from StoreTypeCode).
select
*
from
storeitemcategory storeitemc0_
left outer join
store store1_
on storeitemc0_.store_id=store1_.id
left outer join
storetranslation storetrans2_
on store1_.id=storetrans2_.store_id
left outer join
storetypecode storetypec3_
on store1_.storetypecode_id=storetypec3_.id
left outer join
storetypecodetranslation storetypec4_
on storetypec3_.id=storetypec4_.storetypecode_id
left outer join
address address5_
on store1_.id=address5_.store_id
left outer join
storeitemcategorytranslation storeitemc6_
on storeitemc0_.id=storeitemc6_.storeitemcategory_id
left outer join
storeitemcategory_storeitem storeitems7_
on storeitemc0_.id=storeitems7_.storeitemcategory_id
left outer join
storeitem storeitem8_
on storeitems7_.storeitem_id=storeitem8_.id
left outer join
store store9_
on storeitem8_.store_id=store9_.id
left outer join
storeitemtranslation storeitemt10_
on storeitem8_.id=storeitemt10_.storeitem_id
where
storeitemc0_.id=?
All my tables will have translations tables and I am not sure how to get-around with this without using set.
Does anyone have similar experience?

Found shared references to a collection many to many relation

I have this method:
#Override
public Movie createMovie(Movie movie) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
JwtUser user = (JwtUser)authentication.getPrincipal();
User current_user = userRepository.findOne(user.getId());
movieRepository.save(movie);
userRepository.save(new HashSet<User>(){{
add(new User(current_user, new HashSet<Movie>(){{
add(movie);
}}));
}});
return movieRepository.save(movie);
}
When I run my application and call that function I get this error:
Found shared references to a collection: com.movieseat.model.security.User.movies
In my User model I have:
#ManyToMany
#JoinTable(name = "user_movie",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "movie_id", referencedColumnName = "id")
)
private Set<Movie> movies;
And in my Movie model I have:
#ManyToMany(mappedBy = "movies", cascade = CascadeType.ALL)
private Set<User> users = new HashSet<>();
public Set<User> getUsers() {
return users;
}
What produces the error?
As I understand your code, you're trying to create a Movie in database and bind it to the current User. Correct me if I'm wrong.
At first, as you may learn from Hibernate User Guide, bidirectional #ManyToMany association should be defined and used differently.
A bidirectional #ManyToMany association has an owning and a mappedBy side. To preserve synchronicity between both sides, it’s good practice to provide helper methods for adding or removing child entities.
Secondly, you should not use CascadeType.ALL on #ManyToMany associations:
For #ManyToMany associations, the REMOVE entity state transition
doesn’t make sense to be cascaded because it will propagate beyond the
link table. Since the other side might be referenced by other entities
on the parent-side, the automatic removal might end up in a
ConstraintViolationException.
For example, if #ManyToMany(cascade = CascadeType.ALL) was defined and
the first person would be deleted, Hibernate would throw an exception
because another person is still associated with the address that’s
being deleted.
So, we should move cascade to the owning side, change cascade type, provide helper methods to the User and update only the owning side (User) of the association in our business logic. Let's change the code.
User model:
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(name = "user_movie",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "movie_id", referencedColumnName = "id")
)
private Set<Movie> movies = new HashSet<>();
public void addMovie(Movie movie) {
movies.add(movie);
movie.getUsers().add(this);
}
public void removeMovie(Movie movie) {
movies.remove(movie);
movie.getUsers().remove(this);
}
Movie model:
#ManyToMany(mappedBy = "movies")
private Set<User> users = new HashSet<>();
And business logic:
#Override
public Movie createMovie(Movie movie) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
JwtUser user = (JwtUser)authentication.getPrincipal();
User current_user = userRepository.findOne(user.getId());
current_user.addMovie(movie);
userRepository.save(current_user);
return movie;
}

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')")

HQL query for Association

I am having below tables here but having some problem while fetching results.
#Entity
#Table(name = "USER_VW")
public class WorkspaceUserImpl
{
#JoinColumn(name = "USER_ID", insertable=false, updatable=false)
#OneToOne(targetEntity = UserImpl.class, fetch = FetchType.EAGER)
private User user;
}
#Table(name = "IK_USER")
#Inheritance(strategy = InheritanceType.JOINED)
#AttributeOverride(name = "id", column = #Column(name = "USER_ID") )
public class UserImpl extends BaseAuditable<UserIdentifier>implements User, UserAuthentication {
private static Logger log = LoggerFactory.getLogger(UserImpl.class);
#Id
#Type(type = "com.commons.UserIdentifierTypeMapper")
#Column(name = "USER_ID")
private UserIdentifier id;
}
and User
Public Inteface User
{
UserIdentifier getId();
}
Now i have written an HQL query to fetch all the data from WorkspaceUserImpl class with a given user ID for UserImpl class like below.
SELECT w from WorkspaceUserImpl w where w.user.id = : user_id;
and also tried
SELECT w from WorkspaceUserImpl as w INNER JOIN w.user as u where u.id = : user_id;
and even tried with JOIN FETCH also
and setting the parameter user_id with some say 1234.
but am getting List as emply for the partcular ID but in DB its having 5 records.
am i making any query mistake here? kindly advice..
Have you tried below query:
from WorkspaceUserImpl as w JOIN FETCH w.user as u where u.id = : user_id;

Resources