jpa: when merging many to many previous record get

2019-07-20 12:11发布

问题:

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?

回答1:

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.



回答2:

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