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
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:
- insert into event table
- update work_day table
- 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
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?
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:
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.
0 comments:
Post a Comment