删除不使用JpaRepository

时间:2014-03-27 12:59:31

标签: java hibernate jpa spring-data spring-data-jpa

我有一个spring 4 app,我试图从我的数据库中删除一个实体的实例。我有以下实体:

@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);

}

我有一个与内存数据库(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));
}

第一个断言通过,第二个断言失败。我尝试了另一个更改令牌值的测试,并将其保存到数据库中,确实有效,所以我不确定为什么删除不起作用。它也不会抛出任何异常,只是不会将它持久化到数据库中。它对我的oracle数据库也不起作用。


修改

还有这个问题。通过将此删除添加到我的TokenRepository接口,我能够将删除保留到数据库中:

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

然而,这不是一个理想的解决方案。关于如何在没有这种额外方法的情况下使其工作的任何想法?

12 个答案:

答案 0 :(得分:20)

我遇到了同样的问题

也许你的UserAccount实体在某些属性上有一个带有Cascade的@OneToMany。

我刚刚删除级联,而不是在删除时可以保留......

答案 1 :(得分:8)

当您具有双向关系并且您没有同步双方时(父级和子级都持续存在)(附加到当前会话),很可能会发生这种情况。

这很棘手,我将通过以下示例对此进行解释。

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

相当简单的测试对吗?我们正在创建父级和子级,将其保存到数据库中,然后从数据库中获取一个子级,将其删除,最后确保一切正常。而且不是。

此处的删除无效,因为我们没有同步关系的另一部分,即当前会话中存在的部分。如果父级与当前会话没有关联,则我们的测试将通过,即

@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!
}

当然,这仅证明了我的观点,并解释了OP面临的行为。正确的做法显然是使关系的两个部分保持同步,这意味着:

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。

答案 2 :(得分:6)

你需要在你有很多对象作为属性的类中添加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);
    }
}

答案 3 :(得分:4)

这是针对那些来自Google的人,无论他们是从JpaRepository / CrudRepository的delete还是从自定义存储库调用中使用删除方法在 Spring Boot / Hibernate 中都不起作用的原因session.delete(entity)entityManager.remove(entity)

我正在从Spring Boot 1.5升级到2.2.6版(以及Hibernate 5.4.13),并且一直在为transactionManager使用自定义配置,如下所示:

@Bean
public HibernateTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
    return new HibernateTransactionManager(entityManagerFactory.unwrap(SessionFactory.class));
}

我设法使用@EnableTransactionManagement并删除了自定义 transactionManager上面的bean定义。

如果您仍然必须使用各种自定义事务管理器,那么将Bean定义更改为以下代码也可能有效:

@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
    return new JpaTransactionManager(entityManagerFactory);
}

最后一点,请记住启用Spring Boot的自动配置,以便可以自动创建entityManagerFactory bean,如果要升级到sessionFactory,还可以删除任何entityManager bean (否则,Spring Boot将无法正确执行自动配置)。最后,如果您不手动处理交易,请确保您的方法为@Transactional

答案 4 :(得分:3)

我也经历过这个。在我的例子中,我必须使子表具有可空的外键字段,然后通过设置null从关系中删除父,然后调用save和delete并刷新。

在执行此操作之前,我没有在日志中看到删除或任何异常。

答案 5 :(得分:2)

如果您使用较新版本的Spring Data,则可以使用deleteBy语法...这样您就可以删除其中一个注释:P

接下来的事情是,该行为已经由Jira票证管理: https://jira.spring.io/browse/DATAJPA-727

答案 6 :(得分:1)

您的初始值为500.这意味着您的ID以500

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

并选择一个ID为1的项目

 Token deleted = tokenRepository.findOne(1L);

请检查您的数据库以澄清

答案 7 :(得分:1)

@Transactional
int deleteAuthorByName(String name);

您应该在Repository中编写@Transactional扩展JpaRepository

答案 8 :(得分:0)

一种方法是在userAccount服务中像这样使用cascade = CascadeType.ALL

@OneToMany(cascade = CascadeType.ALL)
private List<Token> tokens;

然后执行以下操作(或类似的逻辑)

@Transactional
public void deleteUserToken(Token token){
    userAccount.getTokens().remove(token);
}

注意@Transactional注释。这将使Spring(休眠)知道您要持久化,合并还是在方法中执行任何操作。 AFAIK上面的示例应该可以像未设置CascadeType一样工作,并调用JPARepository.delete(token)

答案 9 :(得分:0)

我遇到了同样的问题,测试还可以,但是没有删除数据库行。

您是否已将@Transactional注释添加到方法中?对我来说,这种更改使其有效

答案 10 :(得分:0)

在我的例子中是CASCADE.PERSIST,我更改为CASCADE.ALL,并通过级联进行了更改(更改了父对象)。

答案 11 :(得分:0)

CascadeType.PERSIST和orphanRemoval = true不能一起使用。