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:
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?
link to github project repository(look ver-1.1.0-many-to-many-problem branch)
In order to map a many-to-many association, we use the @ManyToMany, @JoinTable and @JoinColumn annotations. Let's have a closer look at them. The @ManyToMany annotation is used in both classes to create the many-to-many relationship between the entities.
In JPA we use the @ManyToMany annotation to model many-to-many relationships. This type of relationship can be unidirectional or bidirectional: In a unidirectional relationship only one entity in the relationship points the other. In a bidirectional relationship both entities point to each other.
Hibernate creates two tables in a many to many relationship.
Hibernate one to many mapping is made between two entities where the first entity can have a relation with multiple instances of the second entity but the second can be associated with only one instance of the first entity. It is a 1 to N relationship.
Change CascadeType.ALL
to CascadeType.MERGE
for events
in the WorkDay 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:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With