簡體   English   中英

為什么我們需要雙向同步方法?

[英]Why do we need bidirectional synchronized methods?

如主題所述。 為什么我們需要雙向同步方法? 它解決了哪些現實世界的用例? 如果我不使用它們會怎樣?

在 Hibernate 的用戶指南中:

每當形成雙向關聯時,應用程序開發人員必須確保雙方始終保持同步。 addPhone() 和 removePhone() 是實用方法,它們在添加或刪除子元素時同步兩端。

源 - Hibernate 用戶指南

在 Vlad 的一篇博文中:

但是,我們仍然需要讓雙方同步,否則會破壞域模型關系的一致性,並且除非雙方正確同步,否則實體狀態轉換不能保證工作。

來源 - Vlad Mihalcea 博客

最后,在 Vlad 的書 - 高性能 Java 持久性,第 216 頁中:

對於雙向@ManyToMany 關聯,必須將輔助方法添加到更有可能與之交互的實體中。 在我們的例子中,根實體是 Post,所以輔助方法被添加到 Post 實體

但是,如果我使用簡單生成的 setter,Hibernate 似乎也能正常工作。 此外,同步方法可能會導致性能下降。

同步方法:

    public void joinProject(ProjectEntity project) {
        project.getEmployees().add(this);
        this.projects.add(project);
    }

生成這個:

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
        (?, ?)

請注意在獲取項目后立即為 Employee 選擇的附加選項。 如果我簡單地使用employeeEntity.getProjects().add(projectEntity); ,它產生:

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
        (?, ?)

沒有更多的員工取貨。

完整代碼。

控制器。

@RestController
@RequestMapping(path = "${application.endpoints.projects}", produces = MediaType.APPLICATION_JSON_VALUE)
@Validated
public class ProjectsEndPoint {


    @PostMapping("add-employee")
    @ApiOperation("Add employee to project")
    public void addEmployeeToProject(@RequestBody @Valid EmployeeProjectRequest request) {
        LOGGER.info("Add employee to project. Request: {}", request);

        this.projectsService.addEmployeeToProject(request);
    }
}

員工項目請求。

@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
public record EmployeeProjectRequest(
        @NotNull @Min(0) Long employeeId,
        @NotNull @Min(0) Long projectId) {
}

項目服務。

@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(() -> new NotFoundException("Employee with id: %d does not exist".formatted(request.employeeId())));

        var projectEntity = this.projectRepo.getProject(request.projectId())
                .orElseThrow(() -> new NotFoundException("Project with id: %d does not exists".formatted(request.projectId())));

        //This line can be changed with employeeEntity.joinProject(projectEntity);
        employeeEntity.getProjects().add(projectEntity);
    }
}

項目回購。

@Repository
public class ProjectRepo {

    private final EntityManager em;

    public ProjectRepo(EntityManager em) {
        this.em = em;
    }

    public Optional<ProjectEntity> getProject(Long id) {
        var result = this.em.createQuery("SELECT p FROM ProjectEntity p where p.id = :id", ProjectEntity.class)
                .setParameter("id", id)
                .getResultList();

        return RepoUtils.fromResultListToOptional(result);
    }
}

員工回購。

@Repository
public class EmployeeRepo {

    private final EntityManager em;

    public EmployeeRepo(EntityManager em) {
        this.em = em;
    }

    public Optional<EmployeeEntity> getEmployee(Long id) {
        var employees = this.em.createQuery("""
                SELECT e FROM EmployeeEntity e
                JOIN FETCH e.projects p
                JOIN FETCH e.team t
                WHERE e.id = :id""", EmployeeEntity.class)
                .setParameter("id", id)
                .getResultList();

        return Optional.ofNullable(employees.isEmpty() ? null : employees.get(0));
    }
}

員工實體。

@Entity
@Table(name = "employee", schema = "employees")
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 = "employees", name = "employee_project",
            joinColumns = @JoinColumn(name = "employee_id", referencedColumnName = "id"),
            inverseJoinColumns = @JoinColumn(name = "project_id", referencedColumnName = "id"))
    private Set<ProjectEntity> projects = new HashSet<>();

    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 ...
}

項目實體。

Entity
@Table(name = "project", schema = "employees")
public class ProjectEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private BigDecimal budget;

    @ManyToMany(fetch = FetchType.LAZY, mappedBy = "projects")
    private Set<EmployeeEntity> employees = new HashSet<>();

    public ProjectEntity() {
    }

    ... Getters and Setters ...
}

如果 Many 端確實有很多元素,那么您可能根本不應該使用 OneToMany。 獲取大型集合意味着使用某種分頁\\過濾,但 OneToMany 加載整個集合。

首先,您需要更新一個擁有實體(FK 所在的位置)以將其存儲在數據庫中。 Vlad 和 Hibernate 指南關於一致性的含義是指更新當前會話中的實體對象。 這些對象在生命周期中具有轉換,並且當您具有雙向關聯時,如果您不設置反向側,那么反向側實體將不會更新該字段,並且將與擁有側實體不一致(並且可能與DB 最終,在 TX 提交之后)在當前會話中。 讓我用 OneToMany 的例子來說明。 如果我們得到 2 個托管實體 Company 和 Employee:

set employee.company = X -> persist(employee) -> managed List<Employee> company.employees gets inconsistent with db

並且可能存在不同類型的不一致,例如從company.employees字段獲取之后並產生副作用(猜測它不是空的,但只是沒有您剛剛添加的員​​工),如果有 Cascade.ALL,您可能會錯過或通過斷開的關系錯誤地刪除\\更新\\添加實體,因為您的實體處於模棱兩可的狀態,並且休眠以防御性但有時不可預測的方式處理它: Delete Not Working with JpaRepository

此外,您可能會發現這個答案很有趣: https : //stackoverflow.com/a/5361587/2924122

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM