Lucas_dev
Lucas_dev

Reputation: 1

@ManyToMany relationship with extra column in join table - Spring data JPA

Simplified code for better understanding:

Event Entity:

@Entity
@Table(name = "event")
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class Event {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @OneToMany(mappedBy = "event", cascade = CascadeType.ALL)
    @JsonIgnore
    private Set<EventSupplier> eventSupplier = new HashSet<>();

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Event event = (Event) o;
        return Objects.equals(id, event.id);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }

}

Supplier Entity:

@Entity
@Table(name = "supplier")
@Getter
@Setter
public class Supplier {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @OneToMany(mappedBy = "supplier", cascade =  CascadeType.ALL)
    @JsonIgnore
    private Set<EventSupplier> eventSupplier = new HashSet<>();

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Supplier sup = (Supplier) o;
        return Objects.equals(id, sup.id);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }

}

EventSupplier Junction Entity:

@Entity
@Table(name = "event_supplier")
@Getter
@Setter
public class EventSupplier {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY, optional = false, cascade = CascadeType.ALL)
    @JoinColumn(name = "event_id")
    @JsonIgnore
    private Event event;

    @ManyToOne(fetch = FetchType.LAZY, optional = false, cascade = CascadeType.ALL)
    @JoinColumn(name = "supplier_id")
    @JsonIgnore
    private Supplier supplier;

    @Column(name = "unit_value")
    @NotNull
    private BigDecimal unitValue;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        EventSupplier es = (EventSupplier) o;
        return Objects.equals(id, es.id);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }

}
    @Transactional
    public Event updateEvent(EventUpdateDto dto, Long id) {
        Event e = eventRepository.findById(id)
                .orElseThrow(() -> new EntityNotFoundException("Evento não encontrado!"));
        
        // Suppliers do Dto
        Set<Supplier> suppliers = supplierRepository.findAllByIdIn(dto.suppliers().keySet());
        Set<Servant> servants = servantRepository.findAllByIdIn(dto.servants());

        if (suppliers.isEmpty()) {
            throw new RuntimeException("O evento deve ter fornecedores ligados a ele!");
        }

        if (servants.isEmpty()) {
            throw new RuntimeException("O evento deve ter servidores ligados a ele!");
        }

        e.getServants().stream()
                .filter(servant -> !servants.contains(servant))
                .forEach(servant -> servant.getEvents().remove(e));

        e.setServants(servants);
        servants.forEach(s -> s.getEvents().add(e));
        
        // Lista de suppliers ligados ao evento
        List<Supplier> supsEvent = e.getEventSupplier()
                .stream()
                .map(EventSupplier::getSupplier)
                .collect(Collectors.toList());

        //Remove suppliers existentes que não estão na lista de Suppliers do dto
        for (Supplier sup : supsEvent) {
            if (!suppliers.contains(sup)) {
                List<EventSupplier> esDelete = e.getEventSupplier()
                        .stream()
                        .filter(es -> es.getSupplier().equals(sup))
                        .toList();

                e.getEventSupplier().removeAll(esDelete);
                sup.getEventSupplier().removeAll(esDelete);
            }
        }
        
        //Adiciona suppliers caso a relação não exista
        for (Supplier sup : suppliers) {
            if (!e.getEventSupplier().stream().anyMatch(es -> es.getSupplier().equals(sup))) {
                EventSupplier newEventSupplier = new EventSupplier();
                newEventSupplier.setSupplier(sup);
                newEventSupplier.setEvent(e);
                e.getEventSupplier().add(newEventSupplier);
            }
        }

        //Atualiza o preço unitário  da relação existente
        for (Supplier sup : suppliers) {
            e.getEventSupplier().stream()
                    .filter(es -> es.getSupplier().equals(sup))
                    .findFirst()
                    .ifPresent(es -> es.setUnitValue(dto.suppliers().get(sup.getId())));
        }


        e.setTotalValue(dto.totalValue());

        eventRepository.save(e);
        return e;
    }

I have this representation of an exotic @manyToMany relationship between Event and Supplier with an EventSupplier join table which has an extra column with the unit value of each supplier.

I'm having trouble deleting relationships between events and suppliers. When I save an event in my bank, the suppliers are saved with their unit prices. When I update an event and update the unit price of an existing supplier in the relationship, it updates smoothly. However, when I remove a relationship from a supplier with the event this does not happen, it simply does not remove it!

Upvotes: 0

Views: 40

Answers (0)

Related Questions