hi I have a #ManyToMany relationship (Users and Groups)
in the Group entity I have :
#ManyToMany(fetch=FetchType.EAGER , cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(name = "group_user",
joinColumns = #JoinColumn(name = "gid"),
inverseJoinColumns = #JoinColumn(name = "uid"))
#JsonIgnore
private List<User> users;
in the User entity I have :
#ManyToMany(mappedBy = "users",fetch=FetchType.LAZY , cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JsonIgnore
private List<Group> groups;
the table "group_user" has been created . I can get the list of users in a group and the list of groups to which a user belongs using these queries :
#Query("select g From Group g join g.users u where u.id=?1")
Iterable<Group> getAllGroupsofUser(long idUser);
#Query("select u From User u join u.groups g where g.id=?1")
Iterable<User> getAllUsersingroup(long idGroup);
I want to add a user to a group so I did this
User user=userRepository.findOne(1);
Group group=groupRepository.findOne(3);
group.getUsers().add(user);
groupRepository.save(group);
this works . it adds a new line in my table group_user.
Actually it insert all the new list of users ( it's a problem ) How to fix that ?
Also I need to do the same when I change fetch=FetchType.EAGER to fetch=FetchType.LAZY in my Group entity .
I'm still waiting for an answer ! I need a better solution for adding a user to a group without having to insert all the list again
Related
I can represent this query:
SELECT * FROM group g JOIN user u ON user.group_id = group.id
via the following in JPA:
#EntityGraph(attributePaths = {"users.posts.comments"})
Optional<Group> findEagerlyFetchedById(UUID id);
But how do I filter out some users based on a field? I want to get the group with the given groupId, with user rows (and children of those) but only for users that are authenticated. As in, how do I represent the following SQL query in JPA?
SELECT * FROM group g JOIN user u ON user.group_id = group.id WHERE user.isAuthenticated = true
I currently have the query below but it takes an all-or-nothing approach. If a single user has matching isAuthenticated field then it returns the group along with all users regardless of whether that field is true for that user. Also, if no users are authenticated, then the group isn't returned at all.
#EntityGraph(attributePaths = {"users.posts.comments"})
#Query("SELECT g FROM Group g JOIN g.users gu WHERE gu.isAuthenticated = :isAuthenticated AND g.id = :groupId")
Optional<Group> findEagerlyFetchedByUserAuthed(UUID groupId, boolean isAuthenticated);
For reference these are the entity definitions:
Group:
#Entity
public class Group {
private UUID id;
#OneToMany(
mappedBy = "groups",
fetch = FetchType.LAZY,
cascade = CascadeType.ALL,
orphanRemoval = true
)
private Set<User> users = Sets.newHashSet();
}
User:
#Entity
public class User {
private UUID id;
private Boolean isAuthenticated;
#ManyToOne( fetch = FetchType.LAZY )
private Group group;
}
From what i am understanding is that you want to select all groups that have authenticated users.
As I understand your problem with "then it returns the group along with all users regardless of whether" is that the database loads all the users, into the java/context even if this is not needed. The Problem for this is probably that even though the users are fetched lazy
#OneToMany(
mappedBy = "groups",
fetch = FetchType.LAZY,
cascade = CascadeType.ALL,
orphanRemoval = true
)
private Set<User> users = Sets.newHashSet();
java has to evaluate weather or not or not the entry in the set is unique. Depending on your implementation of equals for Group or User (not shown in your example) it might be possible that the value of all fields is called, therefore requiring the Set to be fully loaded. A solution for this could be replacing the Set with a List.
private Set<User> users = new ArrayList<>();
Depending on your toString() implementation of the classes it could also just be a problem with debugging since most debuggers call the toString() implementation when trying to display an Object inside the debugger.
The second problem I understand you are approaching is "Also, if no users are authenticated, then the group isn't returned at all." I dont know how to help with that since your SQL clearly states
" ....g.users gu WHERE gu.isAuthenticated = :isAuthenticated ..."
this will always just return groups with authenticated users. Here i cant understand what your problem is. That is what i thought was your goal.
A practical approach that might help you could be selecting the Users and then accessing the groups (in Java via streams).
#Query("SELECT u FROM Users u WHERE u.isAuthenticated = :isAuthenticated)
List<Users> findEagerlyFetchedByUserAuthed(boolean isAuthenticated);
or trying to do a sub select of users first and then joining with something like this:
#Query(
"SELECT group
FROM from group
where groupid IN (SELECT u.groupId
FROM Users u
WHERE u.isAuthenticated = :isAuthenticated))
Optional<Group> findEagerlyFetchedByUserAuthed(UUID groupId, boolean isAuthenticated);
My syntax here is probably not 100% correct but i hope you got the idea.
Lastly it might be better to use
List<Group> findEagerlyFetc...
instead of
Optional<Group> findEagerlyFetc....
#EntityGraph with #Query not working properly.
Use JPA method naming query with #EntityGraph
#EntityGraph(attributePaths = {"users.posts.comments"})
Optional<Group> findByIdAndUsers_IsAuthenticated(UUID groupId, boolean isAuthenticated);
Note: To resolve ambiguity we can use _ inside your method name to manually define traversal points.
Entities as below:
class A {
Long id;
#ManyToMany
#JoinTable(name = "rel_a_b", joinColumns = #JoinColumn(name = "a_id"), inverseJoinColumns = #JoinColumn(name = "b_id"))
Set<B> bSet;
}
class B {
Long id;
#ManyToMany(mappedBy = "bSet")
Set<A> aSet;
#ManyToMany
#JoinTable(name = "rel_b_c", joinColumns = #JoinColumn(name = "b_id"), inverseJoinColumns = #JoinColumn(name = "c_id"))
Set<C> cSet;
}
class C {
Long id;
#ManyToMany(mappedBy = "cSet")
Set<B> bSet;
}
I need to select A entities and join fetch bSet and cSet in B entity. Using JPA Criteria, codes as below:
final Fetch<A, B> bSetFetch = rootA.fetch("bSet", JoinType.LEFT);
bSetFetch.fetch("cSet", JoinType.LEFT);
are working perfectly, but I can't achieve this with QueryDSL. I tried
final QA a = QA.a;
jpaQuery
.from(a)
.leftJoin(a.bSet, QB.b).fetchJoin()
.leftJoin(QB.b.cSet).fetchJoin()
.select(a)
but it throws exception that
query specified join fetching, but the owner of the fetched association was not present in the select list [FromElement{explicit,not a collection join,fetch join,fetch non-lazy properties,classAlias=b,role=A.b,tableName=`b`,tableAlias=b4_,origin=a a2_,columns={a2_.id ,className=B}}] [select a
from A a
left join fetch a.bSet as b
left join fetch b.cSet]]
, if without fetchJoin(), the results don't include bSet and cSet. Could anyone solve this?
The fetch joins are applied correctly from a QueryDSL point of view. We can also observe this from the fact that the produced JPQL query looks correct.
The limitation here is that Hibernate only allows FETCH JOINS if the owner of the fetch association is projected in the select clause. cSet is an association on B, so you would need to project your b's or omit the fetch join for cSet. For example:
jpaQuery
.from(a)
.leftJoin(a.bSet, QB.b).fetchJoin()
.leftJoin(QB.b.cSet).fetchJoin()
.select(a, b)
Now this will result in duplicate results for a due to the cardinality of bSet. That is just a limitation of fetch joins in Hibernate.
Alternatively, you could consider specifying a fetch graph for the query:
EntityGraph postGraph = em.getEntityGraph("post");
query.setHint("javax.persistence.fetchgraph", postGraph);
For more information on using EntityGraphs see https://www.baeldung.com/jpa-entity-graph
I have a User entity generated in Netbeans from an existing database table. The table has a column lastUpdatedByUser that is a User entity. Most of the tables in this database have a lastUpdatedByUser column and queries against those entities correctly return a user object as part of the result.
Ex. Retrieve FROM ProductionTable WHERE date = 'someDate' has a lastUpdatedByUser object that shows who last updated the table row and the rest of their user attributes.
If the productionTable data is edited in the web-app and submitted I need to update the lastUpdatedByUser column.
Users userUpdating = usersService.selectUserEntityByUserId(userId);
Users userEntity = usersFacade.findSingleWithNamedQuery("Users.findByUserId", parameters);
SELECT u FROM Users u WHERE u.userId = :userId
returns a User object that contains a lastUpdatedByUser that is a User object that contains a lastUpdatedByUser that is a User object that contains a lastUpdatedByUser object.... (I have no clue how many there are, and twenty rows of these adds up)
After I persist this
productionEntity.setLastUpdatedByUser(userUpdating);
I get Json StackOverflowError in the next request for the updated entity
gson.toJson(updatedProductionEntity)
The Users entity definition:
#OneToMany(mappedBy = "lastUpdatedByUser")
private Collection<Users> usersCollection;
#JoinColumn(name = "LastUpdatedByUser", referencedColumnName = "UserId")
#ManyToOne
private Users lastUpdatedByUser;
#OneToMany(mappedBy = "lastUpdatedByUser")
private Collection<Production> productionCollection;
How can edit that such that I continue to get a user object as part of other entities like Production, but only a single lastUpdatedByUser object for a User entity?
Thanks for any insight.
I'm guessing this is my issue:
#JoinColumn(name = "LastUpdatedByUser", referencedColumnName = "UserId")
as I found a FK in the Users table to its own UserId
Love refactoring
================================
Drop that FK from the Users table and regenerate the entity in Netbeans and I get
private Integer lastUpdatedByUser;
like it should be
instead of
private Users lastUpdatedByUser;
Now I get to edit all the entities that have valid FKs into the Users table and code and...
Thanks for listening.
I'm having a Product object with a list of related products (which are also product objects). The field of related products is annotated like this:
public class Product {
#JoinTable(name = "RELATED_PRODUCT", joinColumns = {
#JoinColumn(name = "PRODUCT_ID", referencedColumnName = "id", nullable = false)}, inverseJoinColumns = {
#JoinColumn(name = "RELATED_PRODUCT_ID", referencedColumnName = "id", nullable = false)})
#ManyToMany(fetch = FetchType.LAZY)
List<Product> relatedProducts;
}
As you can see the list is fetched lazy, which is what I want in most cases. In some cases however, I want the list of related products to be filled immediatly. I created a query for this with a LEFT JOIN FETCH. However, I want only the related products to be added that have a certain rating, let's say a rating of > 3.
I tried the following:
SELECT DISTINCT p FROM Product p LEFT JOIN FETCH p.comparableProducts cp WHERE p.id = :id AND cp.rating > 3 AND CURRENT_DATE BETWEEN p.commenceDate AND p.removeDate
But this doesn't work. It always returns back ALL related products in the database, not just the ones that have a rating above 3. How is this fixable?
The easiest way to solve this problem is to load related products separately instead of trying to fit them into relatedProducts field.
It also makes perfect sense from object oriented point of view. I suppose you have something like "Product page" that contains the selected product and "recommended products". If so, such a page is a separate concept that deserves its own class:
public class ProductPage {
private Product product;
private List<Product> recommendedProducts;
...
}
Then you can fill such a class either by a single query:
SELECT DISTINCT p, cp FROM Product p LEFT JOIN p.comparableProducts cp WHERE p.id = :id AND cp.rating > 3 AND CURRENT_DATE BETWEEN p.commenceDate AND p.removeDate
or by two separate queries.
Unfortunately, this approach doesn't allow you to receive an instance of ProductPage directly from JPA, you need to write conversion code manually.
Currently I have three tables.
User, Role and Institution.
As you know user and roles has many to many relation. (One user can have multiple roles, one role can have multiple users).
Create a many to many annotations as follows.
#In User Table.
#ManyToMany
#JoinTable(name = "USER_ROLES",
joinColumns = {#JoinColumn(name = "USER_ID")},
inverseJoinColumns = {#JoinColumn(name = "ROLE_ID")}
)
private Set<Role> roles = new HashSet<Role>();
#In Role Table.
#ManyToMany
#JoinTable(name = "USER_ROLES",
joinColumns = {#JoinColumn(name = "ROLE__ID")},
inverseJoinColumns = {#JoinColumn(name = "USER_ID")}
)
private Set<User> users = new HashSet<User>();
But later I have realized that a user can have a role in one institution and a different role in another Institution or the same in different institutions. How can I set that relation?
First of all, your simple many to many mapping is wrong. One side of the bidirectional association must be the inverse side, using the mappedBy attribute. For example:
#ManyToMany(mappedBy = "roles")
private Set<User> users = new HashSet<User>();
Now to answer your question, you just need one more entity. Let's call it Participation. A participation contains a role of a given user in a given institution. So, you have the following locical associations:
user - OneToMany - participation
participation - ManyToOne - role
participation - ManyToOne - institution