Monday, May 2, 2016

Jpa - Hibernate ManyToMany do many insert into join table

Leave a Comment

I have follows ManyToMany relationship between WorkDay(has annotation ManyToMany) and Event

WorkDay entity

@Entity @Table(name = "WORK_DAY", uniqueConstraints = { @UniqueConstraint(columnNames = { "WORKER_ID", "DAY_ID" }) }) @NamedQueries({         @NamedQuery(name = WorkDay.GET_WORK_DAYS_BY_MONTH, query = "select wt from WorkDay wt where wt.worker = :worker and to_char(wt.day.day, 'yyyyMM') = :month) order by wt.day"),         @NamedQuery(name = WorkDay.GET_WORK_DAY, query = "select wt from WorkDay wt where wt.worker = :worker and wt.day = :day") }) public class WorkDay extends SuperClass {      private static final long serialVersionUID = 1L;      public static final String GET_WORK_DAYS_BY_MONTH = "WorkTimeDAO.getWorkDaysByMonth";     public static final String GET_WORK_DAY = "WorkTimeDAO.getWorkDay";      @ManyToOne(fetch = FetchType.LAZY)     @JoinColumn(name = "WORKER_ID", nullable = false)     private Worker worker;      @ManyToOne(fetch = FetchType.LAZY)     @JoinColumn(name = "DAY_ID", nullable = false)     private Day day;      @Column(name = "COMING_TIME")     @Convert(converter = LocalDateTimeAttributeConverter.class)     private LocalDateTime comingTime;      @Column(name = "OUT_TIME")     @Convert(converter = LocalDateTimeAttributeConverter.class)     private LocalDateTime outTime;      @Enumerated(EnumType.STRING)     @Column(name = "STATE", length = 16, nullable = false)     private WorkDayState state = WorkDayState.NO_WORK;      @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)     @JoinTable(name = "WORK_DAY_EVENT", joinColumns = {             @JoinColumn(name = "WORK_DAY_ID", nullable = false)}, inverseJoinColumns = {             @JoinColumn(name = "EVENT_ID", nullable = false)})     @OrderBy(value = "startTime desc")     private List<Event> events = new ArrayList<>();      protected WorkDay() {     }      public WorkDay(Worker worker, Day day) {         this.worker = worker;         this.day = day;         this.state = WorkDayState.NO_WORK;     } } 

Event entity

@Entity @Table(name = "EVENT") public class Event extends SuperClass {      @Column(name = "DAY", nullable = false)     @Convert(converter = LocalDateAttributeConverter.class)     private LocalDate day;      @ManyToOne(fetch = FetchType.LAZY)     @JoinColumn(name = "TYPE_ID", nullable = false)     private EventType type;      @Column(name = "TITLE", nullable = false, length = 128)     private String title;      @Column(name = "DESCRIPTION", nullable = true, length = 512)     private String description;      @Column(name = "START_TIME", nullable = false)     @Convert(converter = LocalDateTimeAttributeConverter.class)     private LocalDateTime startTime;      @Column(name = "END_TIME", nullable = true)     @Convert(converter = LocalDateTimeAttributeConverter.class)     private LocalDateTime endTime;      @Enumerated(EnumType.STRING)     @Column(name = "STATE", nullable = false, length = 16)     private EventState state;      protected Event() {     } } 

Attached UI form for clarity

enter image description here

When I push Clock with run icon first time, it means "create event and start work day" in bean, calling the following methods:

public void startEvent() {     stopLastActiveEvent();     Event creationEvent = new Event(workDay.getDay().getDay(), selectedEventType, selectedEventType.getTitle(),             LocalDateTime.now());     String addEventMessage = workDay.addEvent(creationEvent);     if (Objects.equals(addEventMessage, "")) {         em.persist(creationEvent);         if (workDay.isNoWork()                 && !creationEvent.getType().getCategory().equals(EventCategory.NOT_INFLUENCE_ON_WORKED_TIME)) {             startWork();         }         em.merge(workDay);     } else {         Notification.warn("Невозможно создать событие", addEventMessage);     }     cleanAfterCreation(); }  public String addEvent(Event additionEvent) {     if (!additionEvent.getType().getCategory().equals(NOT_INFLUENCE_ON_WORKED_TIME)             && isPossibleTimeBoundaryForEvent(additionEvent.getStartTime(), additionEvent.getEndTime())) {         events.add(additionEvent);         changeTimeBy(additionEvent);     } else {         return "Пересечение временых интервалов у событий";     }     Collections.sort(events, new EventComparator());     return ""; }  private void startWork() {     workDay.setComingTime(workDay.getLastWorkEvent().getStartTime());     workDay.setState(WorkDayState.WORKING); } 

In log I see:

  1. insert into event table
  2. update work_day table
  3. insert into work_day_event table

on UI updated only attached frame. Always looks fine.. current WorkDay object have one element in the events collection, also all data is inserted into DB.. but if this time edit event row

enter image description here

event row listener:

public void onRowEdit(RowEditEvent event) {     Event editableEvent = (Event) event.getObject();     LocalDateTime startTime = fixDate(editableEvent.getStartTime(), editableEvent.getDay());     LocalDateTime endTime = fixDate(editableEvent.getEndTime(), editableEvent.getDay());     if (editableEvent.getState().equals(END) && startTime.isAfter(endTime)) {         Notification.warn("Невозможно сохранить изменения", "Время окончания события больше времени начала");         refreshEvent(editableEvent);         return;     }     if (workDay.isPossibleTimeBoundaryForEvent(startTime, endTime)) {         editableEvent.setStartTime(startTime);         editableEvent.setEndTime(endTime);         workDay.changeTimeBy(editableEvent);         em.merge(workDay);         em.merge(editableEvent);     } else {         refreshEvent(editableEvent);         Notification.warn("Невозможно сохранить изменения", "Пересечение временых интервалов у событий");     } } 

to the work_day_event insert new row with same work_day_id and event_id data. And if edit row else do one more insert and etc.. In the result I have several equals rows in work_day_event table. Why does this happen?

enter image description here

3 Answers

Answers 1

Change CascadeType.ALL to CascadeType.MERGE for events in WokrDay entity

Use this code

@ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.MERGE) 

instead of

@ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL) 

Do not use ArrayList, use HashSet. Because ArrayList allows duplicates.

For more info about CasecadeType, follow the tutorial:

  1. Hibernate JPA Cascade Types
  2. Cascading best practices

Answers 2

I think the simple solution is to remove the cascade on many to many relationship and do the job manually ! . I see you already doing it redundantly anyway . So try removing you CascadeType.ALL

@ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL) 

How to persist @ManyToMany relation - duplicate entry or detached entity

Answers 3

It looks like the Event class does not have its database identity set(no primary key, or is this done in the SuperClass?). JPA uses the entity's identity to determine that 2 entities are equal. This might always be failing here, so with every merge call, another record will be inserted.

I think you should try to add something like:

@Id @GeneratedValue(strategy=GenerationType.AUTO) String id; 

And check that this fixes your problem.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment