Same question here, but it didn't help me
I've got three tables and many to many connections. In my JSF-application I am trying to add users. In my groups-table there is three different groups: admin, customer and user.
What I have done:
After inserting data to jsf-form user clicks save.
ManagedBean called usersController fetches the group (customer-group) from groupsController
usersController ManagedBean creates the new user and save it to the mySQL-db.
PROBLEM is that groups_has_user-table is empty
and the code
Users:
public class Users implements Serializable {
#ManyToMany(mappedBy = "usersCollection")
private Collection<Groups> groupsCollection;
Groups:
public class Groups implements Serializable {
#JoinTable(name = "groups_has_user", joinColumns = {
#JoinColumn(name = "groups_group_id", referencedColumnName = "group_id", nullable = false)}, inverseJoinColumns = {
#JoinColumn(name = "user_username", referencedColumnName = "username", nullable = false)})
#ManyToMany
private Collection<Users> usersCollection;
UsersController-managedBean
// current is type Users
current.setIsCustomer(Boolean.TRUE);
//BalusC!! Why this is not working but the one below is?
//FacesContext facesContext = FacesContext.getCurrentInstance();
//HttpSession session = (HttpSession) facesContext.getExternalContext().getSession(false);
//GroupsController groupsC = (GroupsController)session.getAttribute("groupsController");
FacesContext context = FacesContext.getCurrentInstance();
GroupsController groupsC = (GroupsController) context.getApplication().evaluateExpressionGet(context, "#{groupsController}", GroupsController.class);
// Fetching group number 2, customer
Groups customerGroup = groupsC.getGroupByGroupId(new Integer(2));
List<Users> usersCollection = (List<Users>) customerGroup.getUsersCollection();
usersCollection.add(current);
//List<Groups> groupList = groupsC.getAllGroups();
customerGroup.setUsersCollection(usersCollection);
// Updating the group using GroupsController
groupsC.updateGroup(customerGroup);
//groupList.add(customerGroup);
//current.setGroupsCollection(groupList);
getFacade().create(current);
Only way how I got something to join-table (groups_has_user) is to add group at the same time to groups-table but then there is an own line for every user and I think that it's not the purpose. Many has asked that, but I couldn't figured that out even if I read those.
Thanks and sorry!
Sami
I got it working. My code was messy and I cleaned it out and it helped. I am not so sure why updating the groups-table inserts a new user to Users-table, but it is ok to me. At first I updated groups-table and inserted user to users-table and I always got an exception that there is the pk already (username is the pk). So it made two inserts to users-table, but now it's working perfectly. No more lines to groups-table just to joint-table and users-table.
// Add current new user to the customer-group
usersCollection.add(currentUser);
customerGroup.setUsersCollection(usersCollection);
// Updating the group using GroupsController, THIS INSERTS USER TO USERS-table as well!!
groupsC.updateGroup(customerGroup);
Related
I have a Spring Boot application and two entities, User and Role that look like the following:
#Entity
public class User {
// other fields
#ManyToMany
#JoinTable(
name = "user_role",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id")
)
#JsonDeserialize(using = RolesJsonDeserializer.class)
#NotEmpty
private Set<Role> roles = new HashSet<>();
// getters and setters
}
#Entity
public class Role {
// other fields
#ManyToMany(mappedBy = "roles")
private List<User> users;
#PreRemove // whenever role is removed, remove association with each user
private void removeRolesFromUsers() {
for (User u : users) {
u.getRoles().remove(this);
}
}
// getters and setters
}
And then I have a Spring JPA integration test annotated with #DataJpaTest and #AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) which is tested against PostgreSQL database. I want to assert that before role is deleted, all associated users are dissociated from that role before it is removed from database.
#Test
#Transactional
public void whenRoleIsDeleted_thenItIsDeletedFromAllUsersThatHadIt() {
clearDatabase(userRepository, roleRepository);
populateDatabase(USERS_COUNT, null, userRepository, roleRepository); // saves a bunch of pre-defined roles first, then saves users associated with those roles
entityManager.flush();
Role existingRole = randomExistingRole(); // obtains role that exists
System.out.println(existingRole.getUsers());
Condition<RoleRepository> existingRole_ = new Condition<>(repo -> repo.findRoleByName(existingRole.getName())
.isPresent(), "hasExistingRole");
assertThat(roleRepository).has(existingRole_);
entityManager.flush();
roleRepository.delete(existingRole);
// then assert that no user has existingRole in its associated roles
}
But my assertion couldn't complete as NullPointException is thrown when I try to remove existingRole. This exception does not happen when application is used normally. It is thrown because role has no associations to any user (and is actually a null), even though I specify that it is Role's users is mappedBy = "roles". User on the other hand has roles in its roles field. If I try to set a break point before roleRepository.delete(), I can see that there are no changes made to the actual database until the end of test method, which I suspect is the problem. Although in logs I can see that Hibernate populates the database and creates joining table just fine.
I tried the following:
Set entityManager.setFlushMode(FlushModeType.COMMIT) to force every flush to commit a transaction, that didn't work.
Removed #Transactional annotation from test method, that didn't work.
Tried #Rollback(false) annotation, that didn't work, but it confirmed that if database is populated before test method starts, then role has users associated with it and made me think that presence of users in actual database is important for this test to work as I expect it to.
I want to understand:
Why does this happen only in tests?
Why can't I see new users and roles in my database immediately after I commit a transaction, but only once test method finished executioe despite the absence of #Transactional?
EDIT
I solved this problem by adding logic to associate role with user manually when user.setRoles() is invoked. I don't know if this is right way? Why won't Hibernate do this automatically?
public void setRoles(Set<Role> roles) {
this.roles = roles;
roles.forEach(role -> {
if (!role.getUsers().contains(this)) {
role.getUsers()
.add(this);
}
});
}
I have a problem with the relationsship #OneToMany in JPA. I want to save a relationsship between a Customer and a Message Object but i got a NullPointerException. I don't know why, because i thought that the follwoing code will work smoothly.
Here's what i trie to do:
Customer new = new Customer();
new.setEmail(email);
new.setUserId(userId);
new.setLastname(lastname);
new.setFirstname(firstname);
new.setPhone(phone);
quick.customerNew(new);
Messages msg = new Messages ();
msg.setMessage(message);
quick.newMessage(msg);
//Here i got the NullPointerException
new.getCustomerMessages.add(msg);
quick.customerUpdate(new);
The Customer Object and the Message Object are stored in the DB. But the relationsship dosen't exists and i got, as i said, the NullPointerException
public class Customer implements Serializable {
[...]
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "_id_info", referencedColumnName = "_id")
private Set<Messages> customerMessages;
[getter/setter]
}
//Here i got the NullPointerException
new.getCustomerMessages.add(msg);
If this line throws the NullPointerException, it can mean two things: either "new" (geez, it hurts just to type it as a variable name) is null, or getCustomerMessages() returns null.
Since your code reaches this point, by accessing "new" multiple times before, I assume that "new" isn't the culprit here.
Since you never call setCustomerMessages() in your code, and there are no signs that any other calls would set your customerMessages attribute, I assume that this will be member you need to set.
It seems I have a problem with OneToMany, ManyToOne mapping.
I'm using a CrudRepository named "ur" here:
ur.save(new User("zx","z", "a", "email#email.com", "Baa")); //userRepo save
User u1 = ur.findOneByUserName("Bx");
MyToken t1 = new MyToken("X5");
u1.addToken(t1);
ur.save(u1);
MyToken t2 = TokenRepo.findOneByToken("X5"); // a different crudRepository
String foundUser = t2.getUser().getUserName(); // THIS "user" is null.
relevant sections of User.java (extends AbstractPersistable<Long>):
#OneToMany(fetch = FetchType.EAGER, cascade={CascadeType.ALL}, mappedBy = "user")
private Set<Role> roles = new HashSet<Role>(1);
#OneToMany(fetch = FetchType.EAGER, cascade={CascadeType.ALL}, mappedBy = "user")
private Set<MyToken> token = new HashSet<MyToken>();
MyToken.java (extends AbstractPersistable<Long>):
#ManyToOne(fetch = FetchType.EAGER, cascade={CascadeType.ALL})
#JoinColumn(name="user") // commenting this out or not does nothing...
private User user;
My debugger says 'user' is 'null' at line "String foundUser" even though that should be completely false according to the code.
As you can see, all is "eager" so I don't see why MyToken.setUser() is not automatically done. How are they not linked already? AnyInitiatedToken.getUser() should not be null if you already did User.addToken, and UserRepo.save().
NOTE: I have also tried .LAZY for the MyToken.java and Role.java class (but still doesn't work).
Since you did not post the setters, I gonna assume they look like default setter.
User.token is set, but it has a mappedby, so it is really irrelevant for what is stored in the DB. Token.user matters, but that is still an NULL so that's what get's saved and retrieved.
You have two options:
Change User.setToken() to update Token.user of the passed in Token (and of the one that was previously set.
Whenever you call User.setToken() also update Token.users to make both directions of the relationship match.
I think it's not related to the fetch strategy whether it's eager or lazy fetch type. I think you gave column alias for the user's id and missed that the name atributte's value of the #JoinColumn should reference that primary key column alias.
Like in the User you have:
#Id
#Column(name = "userid", unique = true, nullable = false)
public String getUserId() {
return this.userId;
}
then in the Token should be
#ManyToOne(fetch = FetchType.EAGER, cascade={CascadeType.ALL})
#JoinColumn(name="userid") // commenting this out or not does nothing...
private User user;
i have a Users and Tags table,and also a user_tag_xref table that holds the many to many relationship.now netbeans generates the entity classes for me (using eclipselink) below is the entity mapping relationship
on User class
#ManyToMany(mappedBy = "usersList")
private List<Tags> tagsList;
on Tags class
#JoinTable(name = "USERS_TAG_XREF", joinColumns = {
#JoinColumn(name = "TAG_ID", referencedColumnName = "TAG_ID")}, inverseJoinColumns = {
#JoinColumn(name = "USER_ID", referencedColumnName = "USER_ID")})
#ManyToMany
private List<Users> usersList;
Now im my business logic RESTfull service,a json client consumes this method
#POST
#Path("/registration2/tag")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public Response registration2_3(List<Tags>tagList,#Context HttpServletRequest req){
Profile p =(Profile) registerMap.get(req.getSession().getId());
Users u = em.find(Users.class,p.getUserId());
for(Tags t : tagList){
t.getUsersList().add(u);
u.getTagsList().add(t);
em.merge(t);
em.merge(u);
}
logger.log(Level.INFO, "the taglist created for user");
return Response.ok(u,MediaType.APPLICATION_JSON).build();
}
The problem is each time i merge a new user to create a many to many relationship, if an existing userid=6201 has a tag with 2,3,4 ,and the new user is try to use to tag id 2,3,4,
the existing user is deleted and the new user merges to the tags. i have read several articles on overriding hash and equals to method in my entity classes,those methods are overridden by default in eclipselink ,also i cannot change my collection type to Set or Collection since List<> type works perfectly well for a json array. i v been having a hard time right now,its been 24hours,could it be the default mapping strategy is wrong? do i need to cascasde?
You have to be extra cautious while using merge as its semantics ( as explained here) is bit different from just update.
As your relationship is bidirectional and users is the inverse side, so all of relationship persistence will be handled by
tags side. Assuming that you tag lists contains detached tags, meaning all Tags have their id set, then you need to iterate over tagList
Tag managedTag = em.merge(t);
This takes care that if t is new instance (unmanaged) then a persistent representation of it will be returned
which has to be used there after or if the instances were having their id set, then the ORM will create a managed instance with data from database ( or from first/second level cache if it exists there). The returned instance is the one managed
for(Tags t : tagList){
Tag managedTag = em.merge(t);
managedTag.getUsersList().add(u);
u.getTagsList().add(t);
User managedUser = em.merge(u);
}
Also you can set the Merge cascade option on the Tag side to save you the second merge call and let the ORM manage relationship automatically.
Here is how merge behaves with detached entities and relations.
#Entity
public class Tag {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
#ManyToMany(cascade=CascadeType.ALL)
private List<User> users = new ArrayList();
.......................
}
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
#ManyToMany(mappedBy="users",cascade=CascadeType.ALL)
private List<Tag> tags = new ArrayList();
........................
}
TestProgram
EntityManagerFactory emf = Persistence.createEntityManagerFactory("Test");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
User user = new User();
user.setName("User");
User managedUser = em.merge(user);
int userId = managedUser.getId();
//create a tag
Tag tag = new Tag();
tag.setName("Tag");
tag.addUser(managedUser);
Tag managedTag = em.merge(tag);
//save the id locally
int savedId = managedTag.getId();
//managed tag was sent to UI where its name will be changed to "Changed Tag"
em.getTransaction().commit();
em.close();
//create another transaction
EntityManager em1 = emf.createEntityManager();
em1.getTransaction().begin();
//simulate a tag sent form UI
Tag tagFromUI = new Tag();
tagFromUI.setId(savedId);
tagFromUI.setName("Changed Tag");
// I want to associate a new user to this tag
// so create a new user
User newUser = new User();
newUser.setName("newUser");
tagFromUI.addUser(newUser);
Tag managedTagFromUI = em1.merge(tagFromUI);
em1.getTransaction().commit();
em1.close();
emf.close();
Here is the SQL generated and corresponding explanation
//First transaction begins
insert into User (name) values ('User');
insert into Tag (name) values ('Tag');
insert into Tag_User (tags_id, users_id) values (1, 1);
//First transaction ends
//Second transaction begins
// Since a detached tag is merged, hibernate queries for tag id 1 to load it in persistent context
//This tag is associated with a user
select tag0_.id as id1_3_1_, tag0_.name as name2_3_1_, users1_.tags_id as tags_id1_3_3_, user2_.id as users_id2_4_3_, user2_.id as id1_5_0_, user2_.name as name2_5_0_ from Tag tag0_ left outer join Tag_User users1_ on tag0_.id=users1_.tags_id left outer join User user2_ on users1_.users_id=user2_.id where tag0_.id=1;
//copies the state of detached tag from UI to the managed tag and sends update
update Tag set name='Changed Tag' where id=1;
//since merge is cascaded, hibernate looks for the user list of supplied tag and sees an transient User
// the transient instance is merged (created new in database as it is not yet persisted)
insert into User (name) values ('newUser');
// merge is called on this new managed instance and the resulted instance is set in the managed Tag instance automatically
//but for this the old relation has to be broken
delete from Tag_User where tags_id=1;
// and the new relation has to be added in database
insert into Tag_User (tags_id, users_id) values (1, 2);
//second transaction ends
while Adding new entries you need to validate that the mappings are not duplicate or already present in the db.For more clarity on the solution plz take a look into my soultion provided in the link
Relationship table data overwritten in many-to-many hibernate
I have two entity classes user and device.
User entity:
public class User {
private Long userId;
#OneToMany( mappedBy = "userId", fetch = FetchType.LAZY)
private Collection<Device> deviceCollection;
and device entity:
public class Device implements Serializable {
#JoinColumn(name = "user_id", referencedColumnName = "user_id")
#ManyToOne(optional = false, fetch = FetchType.LAZY)
private User userId;
When I merge a previously detached device entity into the entity manager after the parent user has been deleted, both the (previously removed) user and the device are re-inserted into the database. There is no cascade annotation on user or device entity; therefore, I don't expect the user entity to be reinserted but it did;
How do I prevent the merge operation to cascade to the user entity?
Thanks in advance.
Any changes you do in detached state there is no possible way for Session Manager to know it so for it the changes are always new objects that needs to be merged (If you are calling merge)
So when you call merge it will load it from database so your object will have Prev+ new changes. So that is why mentioned behavior is happening.
What you can do is first load entity in the session apply changes and then call merge.
What you can do is something like below I have used similar relationship in one of my project with Eclipse Link
Query query = entityManager
.createNamedQuery("User.FindByUserId");
User fromDatabase = null;
try {
query.setParameter("userId", device.getUser().getUserId());
fromDatabase = (User) query.getSingleResult();
} catch (NoResultException noResultException) {
// There is no need to do anything here.
}
if (fromDatabase == null) {
User user= entityManager.merge(device.getUser());
device.setUser(user);
} else {
device.setUser(user);
}
entityManager.persist(device);
Try adding insertable=false, updatable=false to your JoinColumn, e.g.
#JoinColumn(name = "user_id", referencedColumnName = "user_id", insertable=false, updatable=false)
You should be using a version number to prevent entities from being mistakenly resurected. This will force an exception, where as the specification is a bit unclear on what should happen when merging over a relation that isn't marked cascade all or merge. The spec states that managed entities will be synchronized to the database, while the section dealing with merge implies that even entities referenced by relations without the cascade merge/all options will be managed afterward. This behavior is probably not what was intended, but shouldn't be relied on until clarified.
I had the same problem
and I found a bug about this: EntityManager.merge() cascading by default
but I really don't understand why this behaviour was never fix. It is one of reasons among others that I don't use EclipseLink (But it's not the point here)
Edit:
Chris, the comment which begin with "I'm not an expert" the argument that is put in head is not right, I think. What I understand, it's just that entity with a relation without cascade=MERGE or cascade=ALL, you can just navigate, that's all.
Otherwise why use Merge annotation ? It doesn't make sense.