1

I have a problem with mapping an entity (Employee) with @ManyToOne relation to the entity (Community) with @OneToMany relation.

When I assign an entity (Community) to an entity (Employee) where the value of Community is null, it works fine.

The problem arises, if the Employee already has an assigned value to the Community. When I change this value and save the changes, the Employee has got new value for the Community and this new Community has got this Employee in the collection. The only thing is that the old Community still has this Employee in the collection to, but it should be removed. This only happens when the database is restarted.

I tried to solve this problem using both @JoinColumn and @JoinTable, but the problem is the same. I would like to avoid removing an Entity from the old relation by calling JPA in update/create service method.

Are there any solutions to this problem?

Here is the code:

  • A Community

     @Entity
     @Table(name = "community")
     public class Community implements Serializable {
    
     private static final long serialVersionUID = 1L;
    
     @Id
     @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
     @SequenceGenerator(name = "sequenceGenerator")
     @Column(name = "id")
     private Long id;
    
     @OneToMany(mappedBy = "community", orphanRemoval = true, cascade = CascadeType.ALL)
     private Set<Employee> employees = new java.util.LinkedHashSet<>();
    
     public Long getId() {
         return this.id;
     }
    
     public Community id(Long id) {
         this.setId(id);
         return this;
     }
    
     public void setId(Long id) {
         this.id = id;
     }
    
     public Set<Employee> getEmployees() {
         return this.employees;
     }
    
     public void setEmployees(Set<Employee> employees) {
         if (this.employees != null) {
             this.employees.forEach(i -> i.setCommunity(null));
         }
         if (employees != null) {
             employees.forEach(i -> i.setCommunity(this));
         }
         this.employees = employees;
     }
    
     public Community employees(Set<Employee> employees) {
         this.setEmployees(employees);
         return this;
     }
    
     public Community addEmployees(Employee employee) {
         this.employees.add(employee);
         employee.setCommunity(this);
         return this;
     }
    
     public Community removeEmployees(Employee employee) {
         this.employees.remove(employee);
         employee.setCommunity(null);
         return this;
     }
    
  • A Employee.

      @Entity
      @Table(name = "employee")
      public class Employee implements Serializable {
    
      private static final long serialVersionUID = 1L;
    
      @Id
      @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
      @SequenceGenerator(name = "sequenceGenerator")
      @Column(name = "id")
      private Long id;
    
      @ManyToOne(optional = false)
      @JoinColumn(name = "community_id")
      //    @JoinTable(name = "rel_community__employees",
      //        joinColumns = {@JoinColumn(name = "community_id", referencedColumnName = 
                "id", nullable = false)},
      //        inverseJoinColumns = {@JoinColumn(name = "employee_id", 
                referencedColumnName = "id", nullable = false)})
      @Cascade({CascadeType.PERSIST, CascadeType.REFRESH)
      private Community community;
    
      public Long getId() {
          return this.id;
      }
    
      public Employee id(Long id) {
          this.setId(id);
          return this;
      }
    
      public void setId(Long id) {
          this.id = id;
      }
    
      public Community getCommunity() {
          return this.community;
      }
    
      public void setCommunity(Community community) {
          this.community = community;
      }
    
      public Employee community(Community community) {
          this.setCommunity(community);
          return this;
      }
    
  • Service Implementation for managing {@link Employee}.

      @Service
      @Transactional
      public class EmployeeServiceImpl implements EmployeeService {
    
      @Override
      public EmployeeDTO save(EmployeeDTO employeeDTO) {
          Employee employee = employeeMapper.toEntity(employeeDTO);
          employee = employeeRepository.save(employee);
          return employeeMapper.toDto(employee);
      }
    
      @Override
      public EmployeeDTO update(EmployeeDTO employeeDTO) {
          Employee employee = employeeMapper.toEntity(employeeDTO);
          employee = employeeRepository.save(employee);
          return employeeMapper.toDto(employee);
      }
    

    }

  • Try using saveAndFlush instead of just save, this will force the entities to resync, see https://stackoverflow.com/questions/21203875/difference-between-save-and-saveandflush-in-spring-data-jpa – Igor Flakiewicz Jul 31 '22 at 17:20
  • I've used saveAndFlush before, but it doesn't solve my case – Dawid Łuczak Jul 31 '22 at 20:48

1 Answers1

0

This only happens when the database is restarted.

Are you sure that the Employee in the collection of the one Community is the same (i.e. has the same id) as the one in the other Community? My guess is that you might have duplicate Employee objects.

Your code looks fine to me, so AFAICT this should work just like you intended it to work.

If you don't have duplicate Employee rows, another possible explanation is that your previous update simply didn't commit properly to the database (your database lost data). The membership of a Employee to the collection of a Community is defined through the FK community_id in the table employee, so if you find a Employee in a collection of a Community where it doesn't belong, this means the FK value was not correctly updated, or the transaction that updated this wasn't properly committed.

So, now you have to figure out what is happening there exactly. My guess is, the FK is not updated correctly for some reason.

Christian Beikov
  • 15,141
  • 2
  • 32
  • 58