11

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?

link to github project repository(look ver-1.1.0-many-to-many-problem branch)

enter image description here

Kukeltje
  • 12,223
  • 4
  • 24
  • 47
HAYMbl4
  • 1,450
  • 2
  • 15
  • 29

2 Answers2

11

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:

  1. Hibernate JPA Cascade Types
  2. Cascading best practices
Albert
  • 195
  • 9
SkyWalker
  • 28,384
  • 14
  • 74
  • 132
  • ty for your answer. I have tried to use `CascadeType.MERGE` instead `CascadeType.ALL`, also I have tried to use `Set` but it did not resolve my problem. – HAYMbl4 May 03 '16 at 06:42
  • @HAYMbl4 If possible you can share your project through github. I will try to fix your issue. – SkyWalker May 03 '16 at 06:47
  • @HAYMbl4 You can go through http://viralpatel.net/blogs/hibernate-many-to-many-annotation-mapping-tutorial/ for better understanding. – SkyWalker May 03 '16 at 06:51
  • I think the problem may be that I'm using `EntityManager` rather than the `ManyToMany`. – HAYMbl4 May 03 '16 at 07:06
  • @HAYMbl4 There is same issue happens. Would you please check this link http://stackoverflow.com/questions/31738710/update-existing-records-in-hibernate-manytomany-relation and give me update. Then I will try your code. – SkyWalker May 03 '16 at 07:37
  • I use JTA transaction type and I cant manage session. Also if I do `workDay.getEvents().get(0).equals(editableEvent)` I gote true. – HAYMbl4 May 03 '16 at 08:29
0

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

Community
  • 1
  • 1
Sagar
  • 818
  • 1
  • 6
  • 13