Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is JPA double-inserting child entities?

I have a pair of entities, call them "Ticket" and "TicketComment".

Ticket has this property:

@OneToMany(mappedBy="ticket", cascade = CascadeType.ALL)
@OrderBy("date")
private List<TicketComment> comments = new ArrayList<>();

And, TicketComment has this:

@ManyToOne
@JoinColumn(name="TKT_ID")
@NotNull
private Ticket ticket;

The code I'm using should, I think, add a single new comment. However, it seems to add the new comment to the database twice:

Ticket ticket = ticketRepo.findOne(id);

TicketComment newComment = new TicketComment();
// ...
newComment.setTicket(ticket);
ticket.getComments().add(newComment);

ticketRepo.save(ticket);

I think I've been able to do this successfully with similar entities before without the duplicate child entities... what might I be missing?


Update: a workaround seems to be:

  1. Create another JpaRepository for "TicketComment"
  2. Save any other updates to Ticket without creating/adding the comment using ticketRepo.
  3. Create the new comment, linked up with the ticket (newComment.setTicket(ticket);), and save it with a ticketCommentRepo.

So, there's two distinct saves, one which should only affect the parent, and one which should only affect the child.

I'd like to bring it down to a single save, but I'm not sure that's possible?


Update 2: clarification of the observed symptoms.

What happens if I try to simply save my parent "Ticket" entity with the new "TicketComment" added is that I see two distinct records created in the database, with different primary keys, but which have identical comment text, and identical, or nearly so, timestamps.

So, for example, say I posted a comment like "Looks good". Even though I confirmed in a debugger that there's only a single "Looks good" showing in my ticket.getComments()... after I load the ticket's details page again, I see something like:

me @ 2015-09-11 23:16:58: Looks good

me @ 2015-09-11 23:16:59: Looks good

When I looked at some debug logs of the SQL statements being prepared, I saw two identical looking INSERT statements being called for my TicketComments table, followed by a single UPDATE for my Tickets table...

like image 515
pioto Avatar asked Oct 31 '22 21:10

pioto


1 Answers

Really I do not understand what you mean with "add the new comment to the database twice". Are you seeing two registries with same PK?

I think you should use Set interface instead of List if you are expecting that each comment appears once in the ticket's collection. Anyway, I will explains what I know about the behaivor expected for that snippet of code by parts.

Ticket ticket = ticketRepo.findOne(id);
TicketComment newComment = new TicketComment();

The ticket was already persisted? doesn't matter because you will save() it. And you create a new comment.

newComment.setTicket(ticket);

This is necessary due to the owner side of the association is in TicketComment entity, and to keep object model consistently (in some cases may not be needed).

ticket.getComments().add(newComment);

As you set cascade=ALL on ticket.comments collection, all the actions (save, update, etc.) triggered on ticket will be propagated to all the entity objects in the collection.

ticketRepo.save(ticket);

When you pass ticket to save() method, the action is propagated to newComment entity you just add. The newComment will be passed to saveOrUpdate() and then persisted along with its relationship with ticket.

like image 67
Guillermo Avatar answered Nov 15 '22 05:11

Guillermo