Подтвердить что ты не робот

Удалить не работает с JpaRepository

У меня есть приложение 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));
}

Первое утверждение проходит, второе терпит неудачу. Я попробовал еще один тест, который изменяет значение токена и сохраняет его в базе данных, и он действительно работает, поэтому я не уверен, почему удаление не работает. Он также не бросает никаких исключений, просто не сохраняется в базе данных. Он также не работает против моей базы данных оракула. ​​


Edit

Все еще проблема. Я смог получить удаление для сохранения базы данных, добавив это в мой интерфейс TokenRepository:

@Modifying
@Query("delete from Token t where t.id = ?1")
void delete(Long entityId);

Однако это не идеальное решение. Любые идеи относительно того, что мне нужно сделать, чтобы заставить его работать без этого дополнительного метода?

4b9b3361

Ответ 1

У меня была та же проблема

Возможно, у вашего объекта UserAccount есть @OneToMany с Cascade по некоторому атрибуту.

Я просто удаляю каскад, чем он может сохраняться при удалении...

Ответ 2

Скорее всего, такое поведение возникает, когда у вас двунаправленные отношения, и вы не синхронизируете обе стороны, пока сохраняются оба родителя и потомок (привязанный к текущему сеансу).

Это сложно, и я собираюсь объяснить это на следующем примере.

@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 можно использовать здесь.

Ответ 3

Вам нужно добавить функцию 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);
    }
}

Ответ 4

Я тоже прошел через это. В моем случае мне пришлось сделать дочернюю таблицу с полем с нулевым внешним ключом, а затем удалить родителя из отношения, установив нуль, а затем вызвать save и delete и flush.

Я не видел удаления в журнале или каких-либо исключений до этого.

Ответ 5

Если вы используете более новую версию Spring Data, вы можете использовать синтаксис deleteBy... так что вы можете удалить одну из своих аннотаций: P

следующая вещь: поведение уже трактуется билетом Джиры: https://jira.spring.io/browse/DATAJPA-727

Ответ 6

Ваше начальное значение для id равно 500. Это означает, что ваш идентификатор начинается с 500

@SequenceGenerator(name = "seqToken", sequenceName = "SEQ_TOKEN",
initialValue = 500, allocationSize = 1)

И вы выбираете один элемент с идентификатором 1 здесь

 Token deleted = tokenRepository.findOne(1L);

Итак, проверьте свою базу данных, чтобы уточнить, что

Ответ 7

Одним из способов является использование 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).

Ответ 8

У меня та же проблема, тест в порядке, но строка БД не удаляется.

Вы добавили аннотацию @Transactional в метод? для меня это изменение заставляет его работать

Ответ 9

В моем случае это был CASCADE.PERSIST, я перешел на CASCADE.ALL и внес изменения через каскад (изменение объекта отца).

Ответ 10

CascadeType.PERSIST и orphanRemoval = true не работают вместе.