javadatabasehibernate

Java Hibernate. OneToMany collection problem


I have two entities:

@Setter
@Getter
@Entity
@Table(name = "Task")
@NoArgsConstructor
public class TaskEntity implements AbstractEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    @Column(name = "mapName")
    private String mapName;

    @Column(name = "mapPath")
    private String mapPath;

    @Column(name = "mapCost")
    private String mapCost;

    @Column(name = "mapCostResult")
    private String mapCostResult;

    @Enumerated(EnumType.STRING)
    @Column(name = "taskStatus")
    private TaskStatus taskStatus;

    @ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    @JoinColumn(name = "ticketId")
    private TicketEntity ticket;

    @ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    @JoinTable(
        name = "Builder_Task",
        joinColumns = @JoinColumn(name = "taskId"),
        inverseJoinColumns = @JoinColumn(name = "builderDiscordId")
    )
    private Set<BuilderEntity> builders = new HashSet<>();

    public @NotNull TaskEntity addBuilder(@NotNull BuilderEntity builderEntity) {
        builders.add(builderEntity);
        return this;
    }

    public @NotNull TaskEntity removeBuilder(@NotNull BuilderEntity builderEntity) {
        builders.remove(builderEntity);
        return this;
    }
}

and

@Setter
@Getter
@Entity
@Table(name = "Ticket")
@NoArgsConstructor
public class TicketEntity implements AbstractEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    @Column(name = "channelId", unique = true)
    private String channelId;

    @Column(name = "creatorName")
    private String creatorName;

    @Column(name = "creationDate")
    @CreationTimestamp
    private Timestamp creationDate;

    @Column(name = "deadline", columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
    private Timestamp deadline;

    @Column(name = "available")
    private boolean available;

    @Column(name = "mapsAmount")
    private int mapsAmount;

    @OneToMany(fetch = FetchType.EAGER, mappedBy = "ticket", cascade = CascadeType.ALL, orphanRemoval = true)
    @OrderColumn(name = "ticketId")
    private Set<TaskEntity> tasks = new HashSet<>();

    public @NotNull TicketEntity addTask(@NotNull TaskEntity taskEntity) {
        tasks.add(taskEntity);
        return this;
    }

    public @NotNull TicketEntity removeTask(@NotNull TaskEntity taskEntity) {
        tasks.remove(taskEntity);
        return this;
    }
}

Here are my service and repository:

public class TicketRepository implements CRUDRepository<TicketEntity, Integer> {
    // Only needed function for question
    @Override
    public @NotNull TicketEntity update(@NotNull TicketEntity ticketEntity) {
        Transaction transaction = null;
        try (Session session = HibernateUtil.getSessionFactory().openSession()) {
            transaction = session.beginTransaction();
            session.merge(ticketEntity);
            transaction.commit();
            return ticketEntity;
        } catch (Exception e) {
            if (transaction != null) {
                transaction.rollback();
            }
            throw e;
        }
    }
}
public class TicketService {

      public @NotNull Optional<TicketEntity> addTask(@NotNull Integer ticketId, @NotNull TaskEntity task) {
          return updateTicketEntity(ticketId, ticketEntity -> {
          ticketEntity.addTask(task);
          task.setTicket(ticketEntity);
      });
    }

    private @NotNull Optional<TicketEntity> updateTicketEntity(@NotNull Integer ticketId, @NotNull Consumer<TicketEntity> updater) {
        Optional<TicketEntity> entity = findById(ticketId);
        return Preconditions.checkAndReturn(entity.isPresent(),
            () -> {
                TicketEntity ticketEntity = entity.get();
                updater.accept(ticketEntity);
                TicketEntity savedEntity = ticketRepository.update(ticketEntity);
                ticketCache.put(ticketId, savedEntity);
                return Optional.of(savedEntity);
            },
        Optional::empty);
    }
}

So, when I add the TaskEntity value into TicketEntity tasks Collection and merging to database, then all the values that were in the tasks are saved, but with a new ID enter image description here (11 - first saving, 12 - second). All that I need is saving the collection but without duplicates.


Solution

  • Finally, the problem was that I didn't add the task inside the transaction

    public TicketEntity addTask(int ticketId, TaskEntity taskEntity) {
        Transaction transaction = null;
        TicketEntity ticketEntity = null;
    
        try (Session session = HibernateUtil.getSessionFactory().openSession()) {
            transaction = session.beginTransaction();
            ticketEntity = session.get(TicketEntity.class, ticketId);
            if (ticketEntity == null) {
                throw new IllegalArgumentException("Ticket not found with ID: " + ticketId);
            }
            taskEntity.setTicket(ticketEntity);
            ticketEntity.addTask(taskEntity);
    
            session.merge(ticketEntity);
            transaction.commit();
        } catch (Exception e) {
            if (transaction != null) {
                transaction.rollback();
            }
            e.printStackTrace();
        }
    
        return ticketEntity;
    }