Gus
Gus

Reputation: 6871

JPA (Hibernate) seemed to be attempting to duplicate persistent object when added to a collection

TLDR summary: Title is what I thought was happening, but actually it was a violation in the bridging table for the relationship, due to an initial wrong mapping, and a constraint that didn't get removed. If you've landed here from a google search and you're not generating your tables using hbm2ddl, this probably won't be your problem.


Here's the situation: I have a User object. It gets created (via hibernate) just fine when someone invites another user, and email gets sent, click the link, fill in more info, submit and the user is registered... you know the pattern.

The user clicking the link loads up the record (no problem) I can update the object, remove the invite key from the user, and add a couple of newly created dependent objects, (still no problem)

I then added code (in the same transaction) to fetch the basic user role object, and added this role object a list of roles on the user object. (roles have permissions, actions require permissions, etc Guice/shiro etc all wired up just fine so far, works for the first user I created just fine) Now when the transaction commits for this second user, I get this error:

ERROR: duplicate key value violates unique constraint "uk_qxxf5l78ywbvc6myw7kdqvfny"
  Detail: Key (roles)=(1) already exists.

the role collection is mapped very simply nothing fancy...

@OneToMany // also tried @ManyToMany, same effect
private List<Role> roles = new ArrayList<>();

There is no reference from role to user of course. User was loaded like this:

user = ((MyUser) entityManager
      .createQuery("FROM MyUser u WHERE u.invitationKey = :keyval and u.email = :email")
      .setParameter("keyval", invite)
      .setParameter("email", u.getEmail())
      .getSingleResult());

Role is loaded like this:

  Role teamMemberRole = (Role) entityManager.createQuery("from Role r WHERE r.name = 'team_member'").getSingleResult();

They are associated in the predictable way:

  user.getRoles().add(teamMemberRole);

Things then wrap up with

} finally {
  try {
    entityManager.getTransaction().commit();
  } catch (PersistenceException e) {
    log.warn(e);
    // do stuff to let the UI know what went wrong... (omitted)
  }
}

I've tried putting this in a variety of places (with the suspicion it wouldn't do much which seems to have been confirmed)

  entityManager.persist(user);

It's been a little while since I did substantial hibernate work, I have a strong suspicion I am doing/forgetting something completely boneheaded here, and just can't see it staring me in the face. Many thanks if you can point it out.

Hibernate 4.3.11.Final on Postgresql 9.4, Java 8

The mappings for the collection land in the following table that was generated by hibernate from my mapping annotations. There is no object associated with this bridging table.

CREATE TABLE tbl_user_roles
(
  tbl_user bigint NOT NULL,
  roles bigint NOT NULL,
  CONSTRAINT fk_p2gigc6et5vnnfqqlkqdwx43f FOREIGN KEY (tbl_user)
      REFERENCES tbl_user (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION,
  CONSTRAINT fk_qxxf5l78ywbvc6myw7kdqvfny FOREIGN KEY (roles)
      REFERENCES tbl_role (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION,
  CONSTRAINT uk_qxxf5l78ywbvc6myw7kdqvfny UNIQUE (roles)
)
WITH (
  OIDS=FALSE
);

Upvotes: 1

Views: 368

Answers (1)

Gus
Gus

Reputation: 6871

Ok as I suspected, I just wasn't seeing something. Careful inspection of the error message and actual tracing back to the identifier for the constraint mentioned, revealed that the bridging table for the relationship actually had a unique constraint on one side. This is because I had initially mapped it incorrectly as @OneToMany (as shown above).

I realized my error early on, but when hbm2ddl is told to "update" rather than "create" it does not know that it should remove the existing constraint, so my test with @ManyToMany failed even though that was the correct mapping, and I erroneously discarded the correct solution. This left me thinking that the violation was due to a primary key violation on the Role table instead.

Thanks to Richard Barker's comment I re-checked my assumptions and once I recreated without the unique constraint all worked as expected. Hopefully if anyone else falls into this constraint not removed issue with hbm2ddl update they'll find this.

Upvotes: 1

Related Questions