I have two Entity like below...
#Entity
#Table(name = "USER")
public class User {
#Id
private Long id;
private String name;
#ManyToMany(fetch = FetchType.LAZY, mappedBy = "groupMemberList")
#Fetch(FetchMode.SELECT)
private List<Group> groupList = new ArrayList<>();
// Getters - Setters
}
#Entity
#Table(name = "GROUP")
public class Group {
#Id
private Long id;
private String name;
#ManyToMany(fetch = FetchType.LAZY/*, mappedBy = "groupList"*/)
#Fetch(FetchMode.SELECT)
#JoinTable(name = "SEC_GROUP_VS_MEMBER", joinColumns = #JoinColumn(name = "GROUP_ID"),
inverseJoinColumns = #JoinColumn(name = "MEMBER_ID"))
private List<User> groupMemberList;
// Getters - Setters
}
I want to update the User sometimes and also Group sometimes with the methods below...
Method#1
public boolean updateGroup(Long groupId, List<Staff> groupMemberList) {
Group group = hibernateTemplate.get(Group.class, groupId);
group.setGroupMemberList(groupMemberList);
hibernateTemplate.merge(group); // Group updated with the users
return true;
}
Method#2
public boolean updateUser(Long userId, List<Group> groupList) {
User user = hibernateTemplate.get(User.class, userId);
user.setGroupList(groupList);
hibernateTemplate.merge(user); // User not updated with the groups
return true;
}
The first method works fine but not the second one. BUT when I move the join table from Group.class to User.class then second method works fine and not the first one.
The issue is an Owning Entity issue.
Assuming that Staff is a subclass of User, your issue is that only one side of the relationship is the owning entity. The mappedBy = "groupMemberList" makes the Group entity the owning entity and so only changes to that entity are persisted. This means that you have to update the groupMemberList in the Group entity in both cases. If you have a list of groups for a User then you have to iterate over the list of groups and add the User to it. The groupList in User is only for retrieval.
Given User and GroupMember entities:
#Entity
public class User {
#Id #GeneratedValue
private Long id;
#ManyToMany(mappedBy = "groupMemberList")
private List<GroupMember> groupList;
#Entity
public class GroupMember {
#Id #GeneratedValue
private Long id;
#ManyToMany
private List<User> groupMemberList;
Then:
// create starting user and membergroup
tx.begin();
User user = new User();
em.persist(user);
GroupMember group = new GroupMember();
em.persist(group);
tx.commit();
em.clear();
// update users for groupId 2
System.out.println("update users for groupId 2");
tx.begin();
List<User> users = new ArrayList<>();
users.add(user);
group.setGroupMemberList(users);
em.merge(group);
tx.commit();
em.clear();
// update groups for userId 1 -- doesn't work, not owner of relationship
System.out.println("update groups for userId 1 -- doesn't work, not owner of relationship");
tx.begin();
List<GroupMember> groups = new ArrayList<>();
groups.add(group);
user.setGroupList(groups);
em.merge(user);
tx.commit();
em.clear();
// update groups for userId 1 -- works
System.out.println("update groups for userId 1 -- works");
tx.begin();
for ( GroupMember groupMember: groups) {
groupMember.getGroupMemberList().add(user);
em.merge(groupMember);
}
tx.commit();
em.clear();
Gives the following SQL output:
Hibernate: insert into User (id) values (?)
Hibernate: insert into GroupMember (id) values (?)
update users for groupId 2
Hibernate: select groupmembe0_.id as id1_0_0_ from GroupMember groupmembe0_ where groupmembe0_.id=?
Hibernate: select groupmembe0_.groupList_id as groupLis1_1_0_, groupmembe0_.groupMemberList_id as groupMem2_1_0_, user1_.id as id1_4_1_ from GroupMember_User groupmembe0_ inner join User user1_ on groupmembe0_.groupMemberList_id=user1_.id where groupmembe0_.groupList_id=?
Hibernate: select user0_.id as id1_4_0_ from User user0_ where user0_.id=?
Hibernate: insert into GroupMember_User (groupList_id, groupMemberList_id) values (?, ?)
update groups for userId 1 -- doesn't work, not owner of relationship
Hibernate: select user0_.id as id1_4_0_ from User user0_ where user0_.id=?
Hibernate: select groupmembe0_.id as id1_0_0_ from GroupMember groupmembe0_ where groupmembe0_.id=?
update groups for userId 1 -- works
Hibernate: select groupmembe0_.id as id1_0_0_ from GroupMember groupmembe0_ where groupmembe0_.id=?
Hibernate: select groupmembe0_.groupList_id as groupLis1_1_0_, groupmembe0_.groupMemberList_id as groupMem2_1_0_, user1_.id as id1_4_1_ from GroupMember_User groupmembe0_ inner join User user1_ on groupmembe0_.groupMemberList_id=user1_.id where groupmembe0_.groupList_id=?
Hibernate: delete from GroupMember_User where groupList_id=?
Hibernate: insert into GroupMember_User (groupList_id, groupMemberList_id) values (?, ?)
Hibernate: insert into GroupMember_User (groupList_id, groupMemberList_id) values (?, ?)
Reference: MappedBy in bi-directional #ManyToMany : what is the reason
JPA - difference in the use of the mappedBy property to define the owning entity
Related
Assume I have the next data model:
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<Item> items;
... getters, setters, equals and hashcode.
}
#Entity
public class Item {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
#ManyToOne
#JoinColumn(name = "user")
private User user;
private Boolean deleted;
... getters, setters, equals and hashcode.
}
I need to query a certain user by id with non-deleted items. Can I do it via Spring Data Repositories?
I tried something like:
public interface UserRepository extends CrudRepository<User, Long> {
#Query("from User u left join u.items i on i.deleted = false where u.id = ?1")
List<User> findUserWithNonDeletedItems(Long userId);
}
But this approach generates 2 separate SQL queries:
select user0_.id as id1_1_0_, items1_.id as id1_0_1_, items1_.deleted as deleted2_0_1_, items1_.user as user3_0_1_ from user user0_ left outer join item items1_ on user0_.id=items1_.user and (items1_.deleted=0) where user0_.id=?
select items0_.user as user3_0_0_, items0_.id as id1_0_0_, items0_.id as id1_0_1_, items0_.deleted as deleted2_0_1_, items0_.user as user3_0_1_ from item items0_ where items0_.user=?
And as result, I receive user with deleted items in the item list.
Nothing wrong with creation of two separete queries. One is to get users from user table, other is to get items of related user from items table.
join clause is used to combine columns from one or more tables.
join u.items i on i.deleted = false is not a proper use. It should be on the where clause.
You should change the query this way:
#Query("from User u left join u.items i where i.deleted = false and u.id = ?1")
List<User> findUserWithNonDeletedItems(Long userId);
I have the entity User:
#Entity
#Table(name = "users")
public class User {
(...)
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "users_roles",
joinColumns = #JoinColumn(name = "user_id", nullable = false),
inverseJoinColumns = #JoinColumn(name = "role_id", nullable = false)
)
private List<Role> roles;
}
and entity Role with simple id and name columns.
User and Role have many-to-many relation with join table users_roles.
I have created method to remove a user:
public void remove(long userId) {
Session session = getSession();
//NativeQuery joinTableQuery = session.createNativeQuery("DELETE FROM users_roles ur WHERE ur.user_id = :userId");
//joinTableQuery.setParameter("userId", userId);
//joinTableQuery.executeUpdate();
Query userQuery = session.createQuery("DELETE FROM User u WHERE u.id = :userId");
userQuery.setParameter("userId", userId);
userQuery.executeUpdate();
}
I have commented out first NativeQuery on purpose to check what happens. And what is interesting now Hibernate generates two queries:
Hibernate: delete from users_roles where (user_id) in (select id from users where id=?)
Hibernate: delete from users where id=?
Question:
Why does Hibernate generate additional query on users_roles (join table) while my User entity has no CascadeType.REMOVE set on #ManyToMany relation? I thought I have to write it myself (commented part).
Your User entity owns the many-to-many association to the Role entity. When you remove a User, Hibernate automatically also removes all entries from the association table. But it doesn't cascade the remove operation to the Role entity.
You should never use CascadeType.REMOVE on a many-to-many association. If you remove an entity, Hibernate will remove all associated entities even if they are still referenced by other entities. I explained that in great detail on my blog.
If you would use CascadeType.REMOVE on your roles association and remove a User, Hibernate would remove all Role entities referenced by that User. It would do that even if there are other User entity objects that are associated with these Roles.
I have two tables with a one-to-many relationship. I want to fetch those records and insert into another database which having same table by changing the primary key.
My application entity class
#Entity
#Table(name = "EM_APPLICATION")
public class ApplicationTable {
#Id
private int APPLICATION_ID;
#Id
private String CUSTOMER_ID;
private String LAST_NAME;
private String FIRST_NAME;
#OneToMany( fetch = FetchType.EAGER,cascade = CascadeType.ALL)
#JoinColumns({ #JoinColumn(name = "CUSTOMER_ID", referencedColumnName = "CUSTOMER_ID"),
#JoinColumn(name = "APPLICATION_ID", referencedColumnName = "APPLICATION_ID") })
private Set<AddressTable> address;
//Getters and setters
}
Address entity class..
#Entity
#Table(name="EM_APPL_ADDRESS")
public class AddressTable{
#Id
private int APPLICATION_ID;
#Id
private String CUSTOMER_ID;
#Id
private String ADDRESS_TYPE;
//Getters and setters
}
I have to execute a method for fetching records from DB using hibernate:
public void execute(String applId, String customerId) {
Session session = HibernateQAUtil.openSession();
Transaction tx = session.beginTransaction();
String hql = "FROM ApplicationTable WHERE CUSTOMER_ID =:CUSTOMER_ID AND APPLICATION_ID =:APPLICATION_ID";
Query query = session.createQuery(hql);
query.setParameter("CUSTOMER_ID", customerId);
query.setParameter("APPLICATION_ID", Integer.parseInt(applId));
List<ApplicationTable> list = query.list();
tx.commit();
session.close();
ApplicationTable applVO = list.get(0);
insertApplication(applVO );
}
After fetching the records, I am changing APPLICATION_ID, CUSTOMER_ID and some other columns in address table and after inserting in another database.
private void insertApplication(ApplicationTable emApplVO) {
applVO.setAPPLICATION_ID(123456);
applVO.setCUSTOMER_ID("88888888");
Set<AddressTable> addressSet = emApplVO.getAddress();
for (AddressTable address : addressSet) {
address.setAPPLICATION_ID(123456);
address.setCUSTOMER_ID("88888888");
address.setZIP(500032);
}
Session session1 = HibernateUtil.openSession();
Transaction beginTransaction = session1.beginTransaction();
session1.save(emApplVO);
beginTransaction.commit();
session1.close();
}
Hibernate queries in console log are... (below mentioned queries are too large so copied to some extent only..)
Hibernate: select em_applica0_.CUSTOMER_ID as CUSTOMER1_0_,em_applica0_.APPLICATION_ID as APPLICAT2_0_,em_applica0_.ARCHIVE_IND as ARCHIVE8_0_ where em_applica0_.CUSTOMER_ID=? and em_applica0_.APPLICATION_ID=?
Hibernate: select address0_.CUSTOMER_ID as CUSTOMER1_0_1_, address0_.APPLICATION_ID as APPLICAT2_0_1_, address0_.ADDRESS_TYPE as ADDRESS3_1_0_ where em_applica0_.CUSTOMER_ID=? and em_applica0_.APPLICATION_ID=?
Hibernate: insert into EM_APPLICATION (CUSTOMER_ID, APPLICATION_ID, APPLICATION_NBR, APPLICATION_STATUS, APPLICATION_TYPE) values (?, ?, ?, ?)
Hibernate: insert into EM_APPL_ADDRESS (CUSTOMER_ID, APPLICATION_ID, ADDRESS_TYPE) values (?, ?, ?)
Question 1: in the insert method, I have assigned address to addresSet and made some changes in addresSet, after making those changes, I am not assigned the addressSet to applVO (i.e. not written applVO.setAddress(addresSet )) but it inserted a record with updated values into the Address table. What is happening here?
When I am changing code inside insertApplication(ApplicationTable emApplVO) method to
private void insertApplication(ApplicationTable emApplVO) {
applVO.setAPPLICATION_ID(123456);
applVO.setCUSTOMER_ID("88888888");
Set<AddressTable> addressSet = emApplVO.getAddress();
Set<AddressTable> newAddressSet = new HashSet<AddressTable>();
for (AddressTable address : newAddressSet) {
address.setAPPLICATION_ID(emApplVO.getAPPLICATION_ID());
address.setCUSTOMER_ID(emApplVO.getCUSTOMER_ID());
address.setZIP(500032);
newAddressSet.add(address);
}
emApplVO.setAddress(null);
emApplVO.setAddress(newAddressSet);
Session session1 = HibernateUtil.openSession();
Transaction beginTransaction = session1.beginTransaction();
session1.save(emApplVO);
beginTransaction.commit();
session1.close();
}
Hibernate queries in console log are... It also executing update ...
Hibernate: select em_applica0_.CUSTOMER_ID as CUSTOMER1_0_,em_applica0_.APPLICATION_ID as APPLICAT2_0_,em_applica0_.ARCHIVE_IND as ARCHIVE8_0_ where em_applica0_.CUSTOMER_ID=? and em_applica0_.APPLICATION_ID=?
Hibernate: select address0_.CUSTOMER_ID as CUSTOMER1_0_1_, address0_.APPLICATION_ID as APPLICAT2_0_1_, address0_.ADDRESS_TYPE as ADDRESS3_1_0_ where em_applica0_.CUSTOMER_ID=? and em_applica0_.APPLICATION_ID=?
Hibernate: insert into EM_APPLICATION (CUSTOMER_ID, APPLICATION_ID, APPLICATION_NBR, APPLICATION_STATUS, APPLICATION_TYPE) values (?, ?, ?, ?)
Hibernate: insert into EM_APPL_ADDRESS (CUSTOMER_ID, APPLICATION_ID, ADDRESS_TYPE) values (?, ?, ?)
update EM_APPL_ADDRESS set CUSTOMER_ID=?, APPLICATION_ID=? where CUSTOMER_ID=? and APPLICATION_ID=? and ADDRESS_TYPE=?
Question 2: why is the update query executed?
Question 3: while using List<AddressTable> instead of Set<AddressTable>, I got some errors. What is the difference?
I have an entity as below. I am curious if it is possible to create a relationship as I will be describing with the example:
I am creating 2 Person entities Michael and Julia.
I am adding Julia to Michael's friends set.
After that I am retrieving Michael as a JSON response and Julia is available in the response. But when I am retrieving Julia, her friends set is empty. I want to create the bidirectional friendship relation by saving just one side of the friendship. I would like to get Michael on Julia's friends set without doing any other operations. I think that it must be managed by Hibernate. Is it possible and how should I do it?
#ToString(exclude = "friends") // EDIT: these 2 exclusion necessary
#EqualsAndHashCode(exclude = "friends")
public class Person{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Column(name = "name",unique = true)
private String name;
#JsonIgnoreProperties("friends") // EDIT: will prevent the infinite recursion
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "FRIENDSHIP",
joinColumns = #JoinColumn(name = "person_id",
referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "friend_id",
referencedColumnName = "id"))
private Set<Person> friends;
Here is my service layer code for creating a friendship:
#Override
public Person addFriend(String personName, String friendName)
throws FriendshipExistsException, PersonNotFoundException {
Person person = retrieveWithName(personName);
Person friend = retrieveWithName(friendName);
if(!person.getFriends().contains(friend)){
person.getFriends().add(friend);
return repository.save(person);
}
else{
throw new FriendshipExistsException(personName, friendName);
}
}
Related Question:
N+1 query on bidirectional many to many for same entity type
Updated the source code and this version is working properly.
// Creating a graph to help hibernate to create a query with outer join.
#NamedEntityGraph(name="graph.Person.friends",
attributeNodes = #NamedAttributeNode(value = "friends"))
class Person {}
interface PersonRepository extends JpaRepository<Person, Long> {
// using the named graph, it will fetch all friends in same query
#Override
#EntityGraph(value="graph.Person.friends")
Person findOne(Long id);
}
#Override
public Person addFriend(String personName, String friendName)
throws FriendshipExistsException, PersonNotFoundException {
Person person = retrieveWithName(personName);
Person friend = retrieveWithName(friendName);
if(!person.getFriends().contains(friend)){
person.getFriends().add(friend);
friend.getFriends().add(person); // need to setup the relation
return repository.save(person); // only one save method is used, it saves friends with cascade
} else {
throw new FriendshipExistsException(personName, friendName);
}
}
If you check your hibernate logs, you will see:
Hibernate: insert into person (name, id) values (?, ?)
Hibernate: insert into person (name, id) values (?, ?)
Hibernate: insert into friendship (person_id, friend_id) values (?, ?)
Hibernate: insert into friendship (person_id, friend_id) values (?, ?)
Can you help me with some issue? I have two entities Project and Page connected by 'one to many':
Project model (also connected with User model)
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#JsonProperty
private Integer id;
#ManyToOne(optional = false, fetch = FetchType.EAGER)
#JoinColumn(name = "username", referencedColumnName = "username", foreignKey = #ForeignKey(ConstraintMode.NO_CONSTRAINT))
private User user;
#Column(name="projectName", unique = true, nullable = false)
#JsonProperty
private String projectName;
#Column(name="style")
#JsonProperty
private String style;
#Column(name="menu")
#JsonProperty
private String menu;
#JsonProperty
#Fetch(value = FetchMode.SELECT)
#OneToMany(orphanRemoval=true, mappedBy = "project", cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
private Set<Page> pages;
//getters and setters
Page model:
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#JsonProperty
private Integer id;
#Column(name="content")
#JsonProperty
private String content;
#Column(name="pageName", nullable = false)
#JsonProperty
private String pageName;
#ManyToOne(optional = false)
#JoinColumn(name = "project", referencedColumnName = "projectName", foreignKey = #ForeignKey(ConstraintMode.NO_CONSTRAINT))
private Project project;
//getters and setters
Now I want to do something like this:
#Override
public void updatePages(Set<Page> pages, Integer projectId) {
Project project = entityManager.find(Project.class, projectId);
project.setPages(pages);
}
But have Hibernate queries without rezult and errors.
Hibernate:
select
project0_.id as id1_1_0_,
project0_.menu as menu2_1_0_,
project0_.projectName as projectN3_1_0_,
project0_.style as style4_1_0_,
project0_.username as username5_1_0_,
user1_.id as id1_4_1_,
user1_.email as email2_4_1_,
user1_.enabled as enabled3_4_1_,
user1_.password as password4_4_1_,
user1_.username as username5_4_1_,
userroles2_.username as username3_4_2_,
userroles2_.id as id1_2_2_,
userroles2_.id as id1_2_3_,
userroles2_.role as role2_2_3_,
userroles2_.username as username3_2_3_
from
projects project0_
inner join
users user1_
on project0_.username=user1_.username
left outer join
roles userroles2_
on user1_.username=userroles2_.username
where
project0_.id=?
Hibernate:
/* load project.model.User */ select
user0_.id as id1_4_2_,
user0_.email as email2_4_2_,
user0_.enabled as enabled3_4_2_,
user0_.password as password4_4_2_,
user0_.username as username5_4_2_,
projects1_.username as username5_4_4_,
projects1_.id as id1_1_4_,
projects1_.id as id1_1_0_,
projects1_.menu as menu2_1_0_,
projects1_.projectName as projectN3_1_0_,
projects1_.style as style4_1_0_,
projects1_.username as username5_1_0_,
userroles2_.username as username3_4_5_,
userroles2_.id as id1_2_5_,
userroles2_.id as id1_2_1_,
userroles2_.role as role2_2_1_,
userroles2_.username as username3_2_1_
from
users user0_
left outer join
projects projects1_
on user0_.username=projects1_.username
left outer join
roles userroles2_
on user0_.username=userroles2_.username
where
user0_.username=?
Hibernate:
select
pages0_.project as project4_1_0_,
pages0_.id as id1_0_0_,
pages0_.id as id1_0_1_,
pages0_.content as content2_0_1_,
pages0_.pageName as pageName3_0_1_,
pages0_.project as project4_0_1_
from
pages pages0_
where
pages0_.project=?
I want to update pages in project. What is the best way to do it?
UPD
Project project = entityManager.find(Project.class, projectId);
entityManager.createQuery("DELETE FROM Page p WHERE p.project = :project")
.setParameter("project",project)
.executeUpdate();
The owner side of the association Project-Pages is on Page entity, in hibernate (because the way it resolves bi-directional one-to-many) it means that you must set the Page.project for every Page that you want to link with the Project. It won't work if you only add the Pages to the Project's collection of pages.
Do this,
#Override
public void updatePages(Set<Page> pages, Integer projectId) {
Project project = entityManager.find(Project.class, projectId);
for (Page p: pages) {
p.setProject (project);
//the pages are new? then also do em.persist(p)
}
}
The select statement you see right now, according your entity model, are needed to retrieve the project and generated when you invoke the entityManager.find method.