title: Why do we need bidirectional synchronized methods?
body: <p>As stated in the topic. <strong>Why do we need bidirectional synchronized methods? What real world use case does it solve? What happens if I don't use them?</strong></p>
  <p>In Hibernate's User Guide:</p>
  <blockquote>
  <p>Whenever a bidirectional association is formed, the application developer must make sure both sides are in-sync at all times.
  The addPhone() and removePhone() are utility methods that synchronize both ends whenever a child element is added or removed.</p>
  </blockquote>
  <p><a href="https://docs.jboss.org/hibernate/stable/orm/userguide/html_single/Hibernate_User_Guide.html#associations-one-to-many-bidirectional" rel="nofollow noreferrer">Source - Hibernate User Guide</a></p>
  <p>In one of Vlad's blog posts:</p>
  <blockquote>
  <p>However, we still need to have both sides in sync as otherwise, we break the Domain Model relationship consistency, and the entity state transitions are not guaranteed to work unless both sides are properly synchronized.</p>
  </blockquote>
  <p><a href="https://vladmihalcea.com/jpa-hibernate-synchronize-bidirectional-entity-associations/" rel="nofollow noreferrer">Source - Vlad Mihalcea Blog</a></p>
  <p>Lastly, in Vlad's book - High Performance Java Persistance, page 216:</p>
  <blockquote>
  <p>For a bidirectional @ManyToMany association, the helper methods must be added to the entity that is more likely to interact with. In our case, the root entity is the Post, so the helper methods are added to the Post entity</p>
  </blockquote>
  <p>However, if I use simple generated setters, Hibernate seems to work just fine as well. Furthermore, synchronized methods might lead to performance degredation.</p>
  <p>Synchronized methods:</p>
  <pre><code>    public void joinProject(ProjectEntity project) {
          project.getEmployees().add(this);
          this.projects.add(project);
      }
  </code></pre>
  <p>Generates this:</p>
  <pre><code>Hibernate:
      select
          employeeen0_.id as id1_0_0_,
          projectent2_.id as id1_2_1_,
          teamentity3_.id as id1_3_2_,
          employeeen0_.first_name as first_na2_0_0_,
          employeeen0_.job_title as job_titl3_0_0_,
          employeeen0_.last_name as last_nam4_0_0_,
          employeeen0_.team_id as team_id5_0_0_,
          projectent2_.budget as budget2_2_1_,
          projectent2_.name as name3_2_1_,
          projects1_.employee_id as employee1_1_0__,
          projects1_.project_id as project_2_1_0__,
          teamentity3_.name as name2_3_2_
      from
          employees.employee employeeen0_
      inner join
          employees.employee_project projects1_
              on employeeen0_.id=projects1_.employee_id
      inner join
          employees.project projectent2_
              on projects1_.project_id=projectent2_.id
      inner join
          employees.team teamentity3_
              on employeeen0_.team_id=teamentity3_.id
      where
          employeeen0_.id=?
  Hibernate:
      select
          projectent0_.id as id1_2_,
          projectent0_.budget as budget2_2_,
          projectent0_.name as name3_2_
      from
          employees.project projectent0_
      where
          projectent0_.id=?
  Hibernate:
      select
          employees0_.project_id as project_2_1_0_,
          employees0_.employee_id as employee1_1_0_,
          employeeen1_.id as id1_0_1_,
          employeeen1_.first_name as first_na2_0_1_,
          employeeen1_.job_title as job_titl3_0_1_,
          employeeen1_.last_name as last_nam4_0_1_,
          employeeen1_.team_id as team_id5_0_1_
      from
          employees.employee_project employees0_
      inner join
          employees.employee employeeen1_
              on employees0_.employee_id=employeeen1_.id
      where
          employees0_.project_id=?
  Hibernate:
      insert
      into
          employees.employee_project
          (employee_id, project_id)
      values
          (?, ?)
  </code></pre>
  <p>Notice additional select for Employee right after Projects were fetched. If I use simply <code>employeeEntity.getProjects().add(projectEntity);</code>, it generates:</p>
  <pre><code>Hibernate:
      select
          employeeen0_.id as id1_0_0_,
          projectent2_.id as id1_2_1_,
          teamentity3_.id as id1_3_2_,
          employeeen0_.first_name as first_na2_0_0_,
          employeeen0_.job_title as job_titl3_0_0_,
          employeeen0_.last_name as last_nam4_0_0_,
          employeeen0_.team_id as team_id5_0_0_,
          projectent2_.budget as budget2_2_1_,
          projectent2_.name as name3_2_1_,
          projects1_.employee_id as employee1_1_0__,
          projects1_.project_id as project_2_1_0__,
          teamentity3_.name as name2_3_2_
      from
          employees.employee employeeen0_
      inner join
          employees.employee_project projects1_
              on employeeen0_.id=projects1_.employee_id
      inner join
          employees.project projectent2_
              on projects1_.project_id=projectent2_.id
      inner join
          employees.team teamentity3_
              on employeeen0_.team_id=teamentity3_.id
      where
          employeeen0_.id=?
  Hibernate:
      select
          projectent0_.id as id1_2_,
          projectent0_.budget as budget2_2_,
          projectent0_.name as name3_2_
      from
          employees.project projectent0_
      where
          projectent0_.id=?
  Hibernate:
      insert
      into
          employees.employee_project
          (employee_id, project_id)
      values
          (?, ?)
  </code></pre>
  <p>No more fetching of employee.</p>
  <p><strong>Full code.</strong></p>
  <p>Controller.</p>
  <pre><code>@RestController
  @RequestMapping(path = &quot;${application.endpoints.projects}&quot;, produces = MediaType.APPLICATION_JSON_VALUE)
  @Validated
  public class ProjectsEndPoint {
  
  
      @PostMapping(&quot;add-employee&quot;)
      @ApiOperation(&quot;Add employee to project&quot;)
      public void addEmployeeToProject(@RequestBody @Valid EmployeeProjectRequest request) {
          LOGGER.info(&quot;Add employee to project. Request: {}&quot;, request);
  
          this.projectsService.addEmployeeToProject(request);
      }
  }
  
  </code></pre>
  <p>EmployeeProjectRequest.</p>
  <pre><code>@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
  public record EmployeeProjectRequest(
          @NotNull @Min(0) Long employeeId,
          @NotNull @Min(0) Long projectId) {
  }
  </code></pre>
  <p>ProjectService.</p>
  <pre><code>@Service
  public class ProjectsService {
  
      private final ProjectRepo projectRepo;
      private final EmployeeRepo repo;
  
      public ProjectsService(ProjectRepo projectRepo, EmployeeRepo repo) {
          this.projectRepo = projectRepo;
          this.repo = repo;
      }
  
      @Transactional
      public void addEmployeeToProject(EmployeeProjectRequest request) {
          var employeeEntity = this.repo.getEmployee(request.employeeId())
                  .orElseThrow(() -&gt; new NotFoundException(&quot;Employee with id: %d does not exist&quot;.formatted(request.employeeId())));
  
          var projectEntity = this.projectRepo.getProject(request.projectId())
                  .orElseThrow(() -&gt; new NotFoundException(&quot;Project with id: %d does not exists&quot;.formatted(request.projectId())));
  
          //This line can be changed with employeeEntity.joinProject(projectEntity);
          employeeEntity.getProjects().add(projectEntity);
      }
  }
  
  </code></pre>
  <p>ProjectRepo.</p>
  <pre><code>@Repository
  public class ProjectRepo {
  
      private final EntityManager em;
  
      public ProjectRepo(EntityManager em) {
          this.em = em;
      }
  
      public Optional&lt;ProjectEntity&gt; getProject(Long id) {
          var result = this.em.createQuery(&quot;SELECT p FROM ProjectEntity p where p.id = :id&quot;, ProjectEntity.class)
                  .setParameter(&quot;id&quot;, id)
                  .getResultList();
  
          return RepoUtils.fromResultListToOptional(result);
      }
  }
  </code></pre>
  <p>EmployeeRepo.</p>
  <pre><code>@Repository
  public class EmployeeRepo {
  
      private final EntityManager em;
  
      public EmployeeRepo(EntityManager em) {
          this.em = em;
      }
  
      public Optional&lt;EmployeeEntity&gt; getEmployee(Long id) {
          var employees = this.em.createQuery(&quot;&quot;&quot;
                  SELECT e FROM EmployeeEntity e
                  JOIN FETCH e.projects p
                  JOIN FETCH e.team t
                  WHERE e.id = :id&quot;&quot;&quot;, EmployeeEntity.class)
                  .setParameter(&quot;id&quot;, id)
                  .getResultList();
  
          return Optional.ofNullable(employees.isEmpty() ? null : employees.get(0));
      }
  }
  
  </code></pre>
  <p>EmployeeEntity.</p>
  <pre><code>@Entity
  @Table(name = &quot;employee&quot;, schema = &quot;employees&quot;)
  public class EmployeeEntity {
  
      @Id
      @GeneratedValue(strategy = GenerationType.IDENTITY)
      private Long id;
      private String firstName;
      private String lastName;
  
      @Enumerated(EnumType.STRING)
      private JobTitle jobTitle;
  
      @ManyToOne(fetch = FetchType.LAZY)
      private TeamEntity team;
  
      @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
      @JoinTable(schema = &quot;employees&quot;, name = &quot;employee_project&quot;,
              joinColumns = @JoinColumn(name = &quot;employee_id&quot;, referencedColumnName = &quot;id&quot;),
              inverseJoinColumns = @JoinColumn(name = &quot;project_id&quot;, referencedColumnName = &quot;id&quot;))
      private Set&lt;ProjectEntity&gt; projects = new HashSet&lt;&gt;();
  
      public EmployeeEntity() {
      }
  
      public void joinProject(ProjectEntity project) {
          project.getEmployees().add(this);
          this.projects.add(project);
      }
  
      public void leaveProject(ProjectEntity project) {
          project.getEmployees().remove(this);
          this.projects.remove(project);
      }
  
          ... Getters and Setters ...
  }
  </code></pre>
  <p>ProjectEntity.</p>
  <pre><code>Entity
  @Table(name = &quot;project&quot;, schema = &quot;employees&quot;)
  public class ProjectEntity {
  
      @Id
      @GeneratedValue(strategy = GenerationType.IDENTITY)
      private Long id;
  
      private String name;
      private BigDecimal budget;
  
      @ManyToMany(fetch = FetchType.LAZY, mappedBy = &quot;projects&quot;)
      private Set&lt;EmployeeEntity&gt; employees = new HashSet&lt;&gt;();
  
      public ProjectEntity() {
      }
  
      ... Getters and Setters ...
  }
  </code></pre>