ManyToOne OneToMany JPA mapping not working right - jpa

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;

Related

Why does JPA call sql update on delete?

Let´s assume these two entities:
#Entity
public class MyEntity {
#Id private String id;
#OneToMany(mappedBy = "myEntity", cascade = ALL) private Set<MyEntityPredecessor> predecessors;
}
#Entity
public class MyEntityPredecessor{
#Id private String id;
#ManyToOne(name = "entityID", nullable = false) private MyEntity myEntity;
#ManyToOne(name = "entityPre", nullable = false) private MyEntity predecessor;
}
When I try to call a delete with Spring Boot Data (JPA) with a MyEntity Instance, it will work some times (I see the select and then the delete statements in correct order), but sometimes it will try to run an update on the second entity trying to set the "entityPre" Field to null (even thoug it is set to nullable=falsE), causing the DB to send an error (null not allowed!! from DB constraint).
Strangely, this will happen at "random" calls to the delete...
I just call "myEntityRepository.getOne(id)", and then myEntityRepository.delete() with the result... There is no data difference in the DB between calls, the data structure has no null values when calling the delete method, so that should not be the reason.
Why is JPA sometimes trying to call updates on the Predecessor Table, and sometimes directly deleting the values? Am I missing something?
Add a similar ManyToOne annotated set to MyEntity which refers to the other non-nullable property, like:
#OneToMany(mappedBy = "predecessor", cascade = ALL) private Set<MyEntityPredecessor> other;
some explanation:
The issue doesn't happen randomly, but happen when you try to delete an entity which is linked to one (or more) MyEntityPredecessor via the predecessor property (which is mapped to the entityPre field)
Only the other field (entityID) is mapped back to the MyEntity object, so the deletion-cascade only happens via by that field.

cascading on a target entity with other relationships

I have a manytomany relation mapped with these 3 entities :
#Entity
public class ApplicatifDo {
.....
#OneToMany(cascade = CascadeType.ALL, mappedBy = "applicatifDo", fetch = FetchType.EAGER, orphanRemoval = true)
private Set<ApplicatifTerminalDo> applicatifTerminalSet;
.....
}
#Entity
public class ApplicatifTerminalDo {
......
#ManyToOne
#JoinColumn(name = "idApplicatif", nullable = false)
private ApplicatifDo applicatifDo;
#ManyToOne
#JoinColumn(name = "idTerminal", nullable = false)
private TerminalDo terminalDo;
#Column
private String remarques;
......
}
#Entity
public class TerminalDo {
......
#OneToMany(cascade = CascadeType.ALL, mappedBy = "terminalDo", fetch = FetchType.EAGER, orphanRemoval = true)
private Set<ApplicatifTerminalDo> applicatifTerminalSet;
......
}
I try to do many cascading tests on the join table ApplicatifTerminalDo from the two entities ApplicatifDo and TerminalDo. When I create or update a ApplicatifTerminalDo in the Set the cascading works well, but when it comes to orphanRemoval or delete It's not working
First :
For the delete I get the error :
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`softtwo`.`applicatifterminal`, CONSTRAINT `FK_295tcnx7wjuvv5se1g3vldxxn` FOREIGN KEY (`idApplicatif`) REFERENCES `applicatif` (`id`))
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
at java.lang.reflect.Constructor.newInstance(Unknown Source)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:409)
at com.mysql.jdbc.Util.getInstance(Util.java:384)
.....
I would like that when I delete an entity ApplicatifDo or TerminalDo all rows related to them in the ApplicatifTerminal join table get deleted as well.
Second :
For the orphanRemoval, when I delete an element from the Set of ApplicatifTerminalSet in my ApplicatifDo entity and do a merge, then to test it I do a find by his Id of parent entity to get a new exact same entity and count the number of elements in the Set I get the good number (the number at the biginning with one less). But in my database I still have all my datas of my Set.
The code :
//My applicatifDo1 has 4 elements in the ApplicatifTerminalSet here
Assert.assertEquals(applicatifDo1.getApplicatifTerminalSet().size(), 4);
Iterator<ApplicatifTerminalDo> iterator = applicatifDo1
.getApplicatifTerminalSet().iterator();
boolean first = true;
while (iterator.hasNext()) {
ApplicatifTerminalDo element = iterator.next();
if (!first) {
element.setRemarques("remarques updated");
} else {
iterator.remove();
first = false;
}
}
// updateApplicatifDo do just a merge
applicatifDao.updateApplicatifDo(applicatifDo1.getId(), applicatifDo1);
ApplicatifDo applicatifDo = applicatifDao
.findApplicatifDo(applicatifDo1.getId());
Assert.assertEquals(applicatifDo.getApplicatifTerminalSet().size(), 3);
When I do that the update of setRemarques() works well. I have no error in the console when I do that. Then the remove seems to work because I retrieve the same object by his Id it still says I have 3 elements, then the fourth has been deleted : BUT, when I look in phpmyadmin my 4 elements/relations in my appplicationterminal table are still there.
If I do another TestNg later, just retrieving my applicatifDo by his Id this time it gets the four elements.
Then there a big integrity problem here, and still I always use eagerly fetching. Any idea why such problem here ? And how could I make my cascading works ?
Third :
More globally, I have another cascading + orphanRemoval rules on other two entities and that works very well, but the entity target of these cascades does not have other relations with other entities.
Clearly, there is specific rules (or maybe limitations) when cascading on entities with other relationships.
Please do you know tutorials/rules explaining the best practices for such mappings ?
Thanks in advance. I'm stuck on this for too long.

jpa: when merging many to many previous record gets deleted

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

EclipseLink merge unexpected cascade

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.

JPA ManyToMany in case of users and groups?

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);