У меня есть случай, когда участник может регистркурсы.
В основном у меня есть следующая конфигурация объекта (геттеры и сеттеры опущены, а также другие бесполезные свойства):
@Entity
@Table(name = "course")
public class Course {
@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "course")
private Set<Registration> registrations;
}
@Entity
@Table(name = "participant")
public class Participant {
@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "participant")
private Set<Registration> registrations;
}
@Entity
@Table(name = "registration")
public class Registration {
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "course_id")
private Course course;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "participant_id")
private Participant participant;
@PreRemove
private void removeRegistrationFromHolderEntities() {
course.getRegistrations().remove(this);
participant.getRegistrations().remove(this);
}
}
Затем я могу из моей модели просмотра удалить регистрацию или курс (я также удалил ненужные вещи):
@Command
public void deleteRegistration(Registration reg) {
registrationMgr.delete(reg);
}
@Command
public void deleteCourse(Course crs) {
courseMgr.delete(crs);
}
Проблема :
@PreRemove, чтобы я мог удалить ссылки. Без этого удаление игнорируется (без ошибки, просто игнорируется)@PreRemove, иначе я получу ConcurrentModificationException (очевидно...)Я также не могу удалить ссылки из метода deleteRegistration (вместо @PreRemove), потому что регистрации участников загружаются лениво (вызовет исключение failed to lazily initialize a collection of role: ..., could not initialize proxy - no Session).
Каков наилучший подход здесь?
Я использую Ява 11 с Весенняя загрузка 1.0.4 (и весна-загрузка-стартер-данные-jpa).
РЕДАКТИРОВАТЬ :
Менеджеры/репозитории или определены таким образом (то же самое для registration и participant), поэтому он должен быть транзакционным (у меня нет @EnableTransactionManagement в моем основном классе, но это не требуется, поскольку я не использую транзакции вне репозиториев):
@Transactional
@Component("courseMgr")
public class CourseManager {
@Autowired
CourseRepository courseRepository;
public void saveOrUpdate(Course course) {
courseRepository.save(course);
}
public void delete(Course course) {
courseRepository.delete(course);
}
}
public interface CourseRepository extends CrudRepository<Course, Long> {
...
}
РЕДАКТИРОВАТЬ2 :
Я думаю, что нашел довольно простое решение:
Я удалил метод @PreRemove из сущности, а затем вместо удаления подобных ссылок в методе deleteRegistration (который я пробовал, но вызывал исключение failed to lazily initialize a collection of role):
@Command
public void deleteRegistration(Registration reg) {
reg.getCourse().getRegistrations().remove(reg);
reg.getParticipant.getRegistrations().remove(reg);
registrationMgr.delete(reg);
}
Я просто установил для родителей значение null, мне все равно, как оно будет удалено...
@Command
public void deleteRegistration(Registration reg) {
reg.setCourse(null);
reg.setParticipant(null);
registrationMgr.delete(reg);
}
Так что теперь я также могу удалить курс, не вызывая ConcurrentModificationException в @PreRemove.
РЕДАКТИРОВАТЬ3 : Плохо, регистрация не была удалена с помощью приведенного выше решения (по-прежнему нет ошибки, но ничего не происходит). Вместо этого я закончил с этим, что, наконец, работает:
@Command
public void deleteRegistration(Registration reg) {
// remove reference from course, else delete does nothing
Course c = getRegistration().getCourse();
c.getRegistrations().remove(getRegistration());
courseMgr.saveOrUpdate(c);
// delete registration from the database
registrationMgr.delete(reg);
}
Не нужно удалять ссылку с участника...
@K.Nicholas Мои менеджеры являются транзакционные (я отредактировал вопрос, добавив определение). У меня нет большого опыта работы с JPA, но я использую его не в первый раз, и у меня никогда не было таких проблем в других моих проектах, поэтому здесь должно быть что-то особенное.
Моя точка зрения/вопрос заключается в том, что кажется, что вы просто хотите удалить дочерние объекты Entity. Я действительно не читал все это слишком внимательно, но если это так, то я думаю, что @PreRemove не предназначен для этой цели, и поэтому меня не удивляет, что вы получаете ошибки. Если вы хотите удалить дочерние элементы или отношения сущности, вы должны делать все это с отдельными удалениями из отдельных репозиториев.
Спасибо, я согласен, что @PreRemove может быть не предназначен для этой цели, но это единственный способ, который я нашел для удаления ссылок в коллекциях. Выполнение этого в методе deleteRegistration всегда вызывает исключение failed to lazily initialize a collection of roles или ничего не делает. Я пробовал использовать @EnableTransactionManagement + @Transactional в методе, вызывать .size() перед удалением или даже использовать spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true, ничего не работает, либо я получаю сообщение об ошибке, либо ничего не происходит.
Вы говорите, что когда вы удаляете регистрацию, она игнорируется без ошибок, но я так не думаю. Я думаю, что его удаляют, а вы все еще видите его в регистрациях и думаете, что он не был удален. Либо так, либо он удаляется из базы данных, а каскад добавляет его обратно. Не знаю, не проверял. Смотрите мои комментарии ниже.
Я включил трассировку JPA и увидел, что запрос DELETE не выдавался, запись в БД осталась, а ID не изменился. Как будто полностью игнорируется. В зависимости от того, что я прочитал, может быть несколько причин (например, это или это или это, ...), но на самом деле ничего не относится к моему случаю. Во всяком случае, я нашел довольно простое решение, см. edit2 в вопросе. Большое спасибо за уделенное время!




Вы неправильно настроили свои репозитории. Вам нужен составной PK для Registration, и вы должны понимать, что двунаправленные сопоставления действительно предназначены только для запросов. Кроме того, двунаправленные сопоставления в Course и Participate представляют собой проблемы, потому что связь ManyToOne через сущность Registration по умолчанию является FetchType.EAGER. Со всеми имеющимися у вас аннотациями cascade и fetch вы запрашиваете сложную комбинацию вещей из JPA, и кажется, что вы еще не во всем разобрались. Начните с основ, обязательно распечатайте свои операторы SQL и продолжайте оттуда, если хотите попробовать больше утонченности JPA.
@Entity
@Data
public class Course {
@Id
private Integer id;
private String name;
}
@Entity
@Data
public class Participant {
@Id
private Integer id;
private String name;
}
@Entity
@Data
public class Registration {
@EmbeddedId
private RegistrationPK id;
@ManyToOne
@MapsId("participant_id")
private Participant participant;
@ManyToOne
@MapsId("course_id")
private Course course;
}
@Embeddable
@Data
public class RegistrationPK implements Serializable {
private static final long serialVersionUID = 1L;
private Integer course_id;
private Integer participant_id;
}
Является ли ваш основной Entities. RegistrationRepository нужен дополнительный запрос.
public interface RegistrationRepository extends JpaRepository<Registration, RegistrationPK> {
Set<Registration> findByCourse(Course c);
}
И использовать все это в примере:
@Override
public void run(String... args) throws Exception {
create();
Course c = courseRepo.getOne(1);
Set<Registration> rs = read(c);
System.out.println(rs);
deleteCourse(c);
}
private void create() {
Course c1 = new Course();
c1.setId(1);
c1.setName("c1");
courseRepo.save(c1);
Participant p1 = new Participant();
p1.setId(1);
p1.setName("p1");
participantRepo.save(p1);
Registration r1 = new Registration();
r1.setId(new RegistrationPK());
r1.setCourse(c1);
r1.setParticipant(p1);
registrationRepo.save(r1);
}
private Set<Registration> read(Course c) {
return registrationRepo.findByCourse(c);
}
private void deleteCourse(Course c) {
registrationRepo.deleteAll( registrationRepo.findByCourse(c) );
courseRepo.delete(c);
}
Это может быть решением, но я хочу избежать изменения основной структуры моей базы данных/приложения, которая в настоящее время работает в производстве (меня только что попросили добавить функцию удаления курса). А с двунаправленными маппами очень удобно работать в коде (т.е. мне очень часто приходится отображать регистрации курсов), поэтому я хочу их сохранить. В любом случае, спасибо за этот другой подход, мне все еще нужно узнать обо всей магии, которую JPA делает под сценой :)
ОК, решение было довольно простым.
Мне действительно нужно удалить ссылки из метода deleteRegistration. Это то, что я пробовал, но вызывал исключение failed to lazily initialize a collection of role:
@Command
public void deleteRegistration(Registration reg) {
reg.getCourse().getRegistrations().remove(reg);
reg.getParticipant.getRegistrations().remove(reg);
registrationMgr.delete(reg);
}
Хитрость в том, что я также должен сохранить объект курса, прежде чем пытаться удалить регистрацию.
Это работает :
@Command
public void deleteRegistration(Registration reg) {
// remove reference from course, else delete does nothing
Course c = getRegistration().getCourse();
c.getRegistrations().remove(getRegistration());
courseMgr.saveOrUpdate(c);
// delete registration from the database
registrationMgr.delete(reg);
}
Не нужно удалять ссылку с участника...
@PreRemove выполнял свою работу, но теперь я также могу удалить курс, не вызывая ConcurrentModificationException.
Зачем нужен
@PreRemoveдля удаления ссылок? Разве вы не должны управлять графом сущностей внутри транзакции?