Я использую последнюю версию Hibernate с двумя сущностями: Project
и ProjectShare
, которые имеют двунаправленное отношение «один ко многим».
A ProjectShare
однозначно определяетсясоставной идентификатор, содержащий project_id
и user_id
.Помимо ключа, ProjectShare
содержит логический флаг, получает ли пользователь права на чтение или запись в проекте.
@Entity
@Table(name = "projects")
public class Project {
@Id
@GeneratedValue // UUID generator
@Column(name = "project_id")
private String id;
@Column(name = "name")
private String name;
@OneToMany(mappedBy = "project", cascade = CascadeType.ALL, orphanRemoval = true)
private List<ProjectShare> shares = new ArrayList<>();
public Project(String name) {
this.name = name;
}
public void addShare(ProjectShare share) {
shares.add(share);
share.setProject(this);
}
public void removeShare(ProjectShare share) {
shares.remove(share);
share.setProject(null);
}
}
@Entity
@Table(name = "project_shares")
public class ProjectShare {
@EmbeddedId
private ProjectShareId id;
@Column(name = "has_write_access")
private boolean hasWriteAccess;
@MapsId("projectId")
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "project_id", nullable = false, updatable = false)
private Project project;
public ProjectShare(ProjectShareId id, boolean hasWriteAccess) {
this.id = id;
this.hasWriteAccess = hasWriteAccess;
}
public void setProject(Project project) {
this.project = project;
}
}
@Embeddable
public class ProjectShareId implements Serializable {
@Column(name = "project_id")
private String projectId;
@Column(name = "user_id")
private String userId;
public ProjectShareId(String userId) {
this.userId = userId;
}
// equals and hashCode go here...
}
Если я создаю новый Project
и назначаю ему новые ProjectShare
ассоциациивсе работает нормально:
Project project = new Project("my_project");
project.addShare(new ProjectShare(new ProjectShareId("user1"), false));
project.addShare(new ProjectShare(new ProjectShareId("user2"), false));
projectRepository.save(project); // assume project id is '123'
Поскольку все объекты являются новыми и еще не сохранены, выполняется 1 вставка для самого проекта и 1 вставка для каждого ProjectShare.Предположим, что проект вставлен с идентификатором '123'.
Теперь, если я загружу этот существующий проект и добавлю в него новые ProjectShares, все пойдет не так:
Project project = projectRepository.findById("123");
project.addShare(new ProjectShare(new ProjectShareId("user2"), true));
project.addShare(new ProjectShare(new ProjectShareId("user3"), true));
projectRepository.save(project);
Для каждого ProjectShareэто выполняет SELECT для значений в ProjectShareId
(project_id и user_id), после чего следует либо INSERT of UPDATE, в зависимости от того, была ли найдена запись.Это базовая стратегия слияния в Hibernate, и именно этого мы и хотим.
Желаемым результатом этого должно быть:
- Оставить ProjectShare на
user1
нетронутым - Обновление ProjectShare для
user2
(с false
до true
) - Создание нового ProjectShare для
user3
Однако, когдаSELECT выполняется для ProjectShare
, внешнего ключа project_id всегда равно нулю .Это означает, что существующие записи никогда не будут найдены, вместо INSATE и INSERT предпринята попытка INSERT, и будет инициировано нарушение ограничения уровня DB.
Как мне решить эту проблему?Должен ли я вручную просмотреть коллекцию project.getShares()
, найти существующие записи и обновить их?Я надеялся, что Hibernate сделает это с помощью своей стратегии слияния.
Или это может быть ошибка в Hibernate, связанная с ассоциациями с внешними ключами в встроенных идентификаторах?