Search code examples
javamysqlmavenjpapersistence

JPA two unidirectional @OneToMany relationships to the same entity resulting in duplicate entry


I'm currently working on a school project where we have to create our own 'Twitter' application and I'm having some trouble with the persistence of the domain objects.

My Account class (simplified for readability):

@Entity
public class Account implements Serializable {

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
@Column(unique = true)
private String email;

@OneToMany
private final List<Account> following = new ArrayList<>();
@OneToMany(mappedBy = "tweetedBy", cascade = ALL)
private final List<Tweet> tweets = new ArrayList<>();

My Tweet class (simplified for readability):

@Entity
public class Tweet implements Serializable {

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
private String content;

@ManyToOne
private Account tweetedBy;

@OneToMany(cascade = CascadeType.PERSIST)
@JoinTable(name = "tweet_likes")
private final List<Account> likedBy = new ArrayList<>();

@OneToMany(cascade = CascadeType.PERSIST)
@JoinTable(name = "tweet_mentions")
private final List<Account> mentions = new ArrayList<>();

The persist code (simplified):

Account a1 = new Account("user1@gmail.com", "password1");
Account a2 = new Account("user2@gmail.com", "password2");
Account a3 = new Account("user3@gmail.com", "password3");

a1.addTweet("Sup mah dudes.");
a1.addTweet("yoyo");
a2.addTweet("Allo Allo #tweeting");
a2.addTweet("#testing yoyo");
a1.getTweets().get(0).addLike(a3);
a1.addFollowing(a3);

em.persist(a1);
em.persist(a2);
em.persist(a3);

The problem I'm having is that the likedBy and mentions aren't being persisted correctly. The linker tables are being generated and the data gets inserted but I keep getting a duplicate entry error on the insert of a user. I believe I modeled the relationship correctly (unidirectional OneToMany), because I don't want an Account to keep track of Tweets it was mentioned in.

What I have tried:

  • @JoinColumn for both likes and mentions (results in duplicate insert)
  • @JoinTable for both likes and mentions (results in duplicate insert)
  • Only @OneToMany for both likes and mentions (this does not result in an error but creates 1 linker table for both relationships where either cannot be null)
  • @OneToMany for likes and then @joinColumn for mentions where nullable = true (this results in the scenario where u cannot be mentioned in a tweet unless you like it, which is odd behaviour)
  • @OneToMany(cascade = CascadeType.MERGE) (results in duplicate insert)

Netbeans output of duplicate insert error:

Warning:   Local Exception Stack: 
Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.6.4.qualifier): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry 'user6@gmail.com' for key 'EMAIL'
Error Code: 1062
Call: INSERT INTO ACCOUNT (AVATARPATH, BIO, EMAIL, ENCRYPTEDPASSWORD, LOCATION, USERNAME, USERROLE, WEBSITE) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
bind => [8 parameters bound]
Query: InsertObjectQuery(domain.Account@3c7f9d54)

I believe this error occurs because of the folowing flow of my JPA imlementation:

  1. Account persisted
  2. Tweet persisted (because it is inside Account)
  3. Account persisted (because it is inside Tweet) <-- duplicate entry

What I expect:

  • 1 linker table with a tweet_id (fk) and an account_id (fk) representing likes
  • 1 linker table with a tweet_id (fk) and an account_id (fk) representing mentions

If someone could help me with the annotations or explain what else I'm doing wrong that would be very much appreciated.

Ty in advance for any help.


Solution

  • I solved the problem, it turns out it was the annotations at the likes and mentions. @JoinTable(name = "") fixed it. I think not dropping the tables manually before redeploying caused that I did not see this as the solution at first.

    for anyone with a similar problem, this is my solution:

    @OneToMany(cascade = CascadeType.PERSIST)
    @JoinTable(name = "tweet_likes") //<--CREATES SEPERATE TABLE
    private final List<Account> likedBy = new ArrayList<>();
    @OneToMany(cascade = CascadeType.PERSIST)
    @JoinTable(name = "tweet_mentions") //<--CREATES SEPERATE TABLE
    private final List<Account> mentions = new ArrayList<>();
    

    Also, apearently if I restart my entire project (restart the entire glassfish/payara server) I get the duplicate entry error because the EntityManager tries to persist to the database before the tables have been dropped and recreated. So if I just undeploy and redeploy my project while keeping the payara server online, it persists just fine. I have not yet been able to find a solution to this problem but thats for another question.