Ответ 1
У меня была та же проблема
Возможно, у вашего объекта UserAccount есть @OneToMany с Cascade по некоторому атрибуту.
Я просто удаляю каскад, чем он может сохраняться при удалении...
У меня есть приложение spring 4, где я пытаюсь удалить экземпляр объекта из моей базы данных. У меня есть следующий объект:
@Entity
public class Token implements Serializable {
@Id
@SequenceGenerator(name = "seqToken", sequenceName = "SEQ_TOKEN", initialValue = 500, allocationSize = 1)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seqToken")
@Column(name = "TOKEN_ID", nullable = false, precision = 19, scale = 0)
private Long id;
@NotNull
@Column(name = "VALUE", unique = true)
private String value;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "USER_ACCOUNT_ID", nullable = false)
private UserAccount userAccount;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "EXPIRES", length = 11)
private Date expires;
...
// getters and setters omitted to keep it simple
}
У меня есть интерфейс JpaRepository:
public interface TokenRepository extends JpaRepository<Token, Long> {
Token findByValue(@Param("value") String value);
}
У меня есть установка unit test, которая работает с базой данных в памяти (H2), и я предварительно заполняю базу данных двумя токенами:
@Test
public void testDeleteToken() {
assertThat(tokenRepository.findAll().size(), is(2));
Token deleted = tokenRepository.findOne(1L);
tokenRepository.delete(deleted);
tokenRepository.flush();
assertThat(tokenRepository.findAll().size(), is(1));
}
Первое утверждение проходит, второе терпит неудачу. Я попробовал еще один тест, который изменяет значение токена и сохраняет его в базе данных, и он действительно работает, поэтому я не уверен, почему удаление не работает. Он также не бросает никаких исключений, просто не сохраняется в базе данных. Он также не работает против моей базы данных оракула.
Все еще проблема. Я смог получить удаление для сохранения базы данных, добавив это в мой интерфейс TokenRepository:
@Modifying
@Query("delete from Token t where t.id = ?1")
void delete(Long entityId);
Однако это не идеальное решение. Любые идеи относительно того, что мне нужно сделать, чтобы заставить его работать без этого дополнительного метода?
У меня была та же проблема
Возможно, у вашего объекта UserAccount есть @OneToMany с Cascade по некоторому атрибуту.
Я просто удаляю каскад, чем он может сохраняться при удалении...
Скорее всего, такое поведение возникает, когда у вас двунаправленные отношения, и вы не синхронизируете обе стороны, пока сохраняются оба родителя и потомок (привязанный к текущему сеансу).
Это сложно, и я собираюсь объяснить это на следующем примере.
@Entity
public class Parent {
@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "id", unique = true, nullable = false)
private Long id;
@OneToMany(cascade = CascadeType.PERSIST, mappedBy = "parent")
private Set<Child> children = new HashSet<>(0);
public void setChildren(Set<Child> children) {
this.children = children;
this.children.forEach(child -> child.setParent(this));
}
}
@Entity
public class Child {
@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "id", unique = true, nullable = false)
private Long id;
@ManyToOne
@JoinColumn(name = "parent_id")
private Parent parent;
public void setParent(Parent parent) {
this.parent = parent;
}
}
Давай напишем тест (транзакционный между прочим)
public class ParentTest extends IntegrationTestSpec {
@Autowired
private ParentRepository parentRepository;
@Autowired
private ChildRepository childRepository;
@Autowired
private ParentFixture parentFixture;
@Test
public void test() {
Parent parent = new Parent();
Child child = new Child();
parent.setChildren(Set.of(child));
parentRepository.save(parent);
Child fetchedChild = childRepository.findAll().get(0);
childRepository.delete(fetchedChild);
assertEquals(1, parentRepository.count());
assertEquals(0, childRepository.count()); // FAILS!!! childRepostitory.counts() returns 1
}
}
Довольно простой тест, верно? Мы создаем parent и child, сохраняем его в базе данных, затем извлекаем дочерний элемент из базы данных, удаляем его и наконец проверяем, что все работает так, как ожидалось. И это не так.
Удаление здесь не сработало, потому что мы не синхронизировали другую часть отношений, которая СОСТОЯЛАСЬ В ТЕКУЩЕЙ СЕССИИ. Если родитель не был связан с текущим сеансом, наш тест прошел бы, т.е.
@Component
public class ParentFixture {
...
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void thereIsParentWithChildren() {
Parent parent = new Parent();
Child child = new Child();
parent.setChildren(Set.of(child));
parentRepository.save(parent);
}
}
а также
@Test
public void test() {
parentFixture.thereIsParentWithChildren(); // we're saving Child and Parent in seperate transaction
Child fetchedChild = childRepository.findAll().get(0);
childRepository.delete(fetchedChild);
assertEquals(1, parentRepository.count());
assertEquals(0, childRepository.count()); // WORKS!
}
Конечно, это только подтверждает мою точку зрения и объясняет поведение, с которым столкнулся ОП. Правильный путь, очевидно, заключается в синхронизации обеих частей отношений, что означает:
class Parent {
...
public void dismissChild(Child child) {
this.children.remove(child);
}
public void dismissChildren() {
this.children.forEach(child -> child.dismissParent()); // SYNCHRONIZING THE OTHER SIDE OF RELATIONSHIP
this.children.clear();
}
}
class Child {
...
public void dismissParent() {
this.parent.dismissChild(this); //SYNCHRONIZING THE OTHER SIDE OF RELATIONSHIP
this.parent = null;
}
}
Очевидно, что @PreRemove можно использовать здесь.
Вам нужно добавить функцию PreRemove в класс, в котором у вас есть много объектов в качестве атрибута, например, в классе образования, которые имеют отношение к UserProfile Education.java
private Set<UserProfile> userProfiles = new HashSet<UserProfile>(0);
@ManyToMany(fetch = FetchType.EAGER, mappedBy = "educations")
public Set<UserProfile> getUserProfiles() {
return this.userProfiles;
}
@PreRemove
private void removeEducationFromUsersProfile() {
for (UsersProfile u : usersProfiles) {
u.getEducationses().remove(this);
}
}
Я тоже прошел через это. В моем случае мне пришлось сделать дочернюю таблицу с полем с нулевым внешним ключом, а затем удалить родителя из отношения, установив нуль, а затем вызвать save и delete и flush.
Я не видел удаления в журнале или каких-либо исключений до этого.
Если вы используете более новую версию Spring Data, вы можете использовать синтаксис deleteBy... так что вы можете удалить одну из своих аннотаций: P
следующая вещь: поведение уже трактуется билетом Джиры: https://jira.spring.io/browse/DATAJPA-727
Ваше начальное значение для id равно 500. Это означает, что ваш идентификатор начинается с 500
@SequenceGenerator(name = "seqToken", sequenceName = "SEQ_TOKEN",
initialValue = 500, allocationSize = 1)
И вы выбираете один элемент с идентификатором 1 здесь
Token deleted = tokenRepository.findOne(1L);
Итак, проверьте свою базу данных, чтобы уточнить, что
Одним из способов является использование cascade = CascadeType.ALL
например, в вашей службе userAccount:
@OneToMany(cascade = CascadeType.ALL)
private List<Token> tokens;
Затем сделайте что-то вроде следующего (или похожую логику)
@Transactional
public void deleteUserToken(Token token){
userAccount.getTokens().remove(token);
}
Обратите внимание на аннотацию @Transactional
. Это позволит Spring (Hibernate) узнать, хотите ли вы сохранить, объединить или что-то еще, что вы делаете в методе. AFAIK приведенный выше пример должен работать так, как будто у вас не установлен CascadeType, и вызывать JPARepository.delete(token)
.
У меня та же проблема, тест в порядке, но строка БД не удаляется.
Вы добавили аннотацию @Transactional в метод? для меня это изменение заставляет его работать
В моем случае это был CASCADE.PERSIST, я перешел на CASCADE.ALL и внес изменения через каскад (изменение объекта отца).
CascadeType.PERSIST и orphanRemoval = true не работают вместе.