删除无法使用JpaRepository


72

我有一个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);

}

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

但是,这不是理想的解决方案。如果没有这种额外的方法,我需要做什么才能使其正常工作?


2
您是否找到了解决方案?在最新的春季版本中也存在同样的问题。根据mysql日志表,没有发布删除语句,没有错误消息。
phil294

抱歉不行。我不再需要在这里执行此操作,而我不得不在其他地方执行的操作似乎很好。我不知道这件事有什么特别之处。
Twisty McGee,

您应该考虑发布@Modifying delete语句作为答案。这是我也可以解决它的唯一方法。我赞成这个问题,但是我也赞成那个答案。
Taugenichts

覆盖删除方法对我也解决了这个问题,不确定为什么会发生(从Spring Boot 2.1.5升级到2.1.6)
Brunaldo

Answers:


46

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

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

@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可以在这里使用。


在这里的解释真的很好。我有相同的问题,也有相同的“解决方法”-通过自定义查询删除,但我不想使用它,因为它显然是黑客。@PreRemove钩子确实在这里产生了奇迹。谢谢!
OgnjenMišić,

很高兴我能帮助你!
pzeszko

38

我有同样的问题

也许您的UserAccount实体具有@OneToMany,且在某些属性上具有Cascade。

我只是删除了层叠,删除时它可能会持续下去...


1
这也为我工作。具体来说,我玩耍并能够保留以下层叠类型:[CascadeType.PERSIST,CascadeType.REFRESH]并仍然可以通过子存储库进行删除。对我来说毫无意义的是,我通过所有者的“ mappedBy”使所有者实体成为孩子
GameSalutes 2016年

14
此解决方案似乎有效,但未给出任何解释。
伊万·马塔武吉

16

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

6

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

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


4

这适用于所有来自Google的人,无论他们是从JpaRepository / CrudRepository还是自定义存储库(调用或)中使用了他们的delete方法在Spring Boot / Hibernate中都不起作用的原因。deletesession.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可以自动创建sessionFactoryBean,如果要升级到Bean ,还请删除所有Bean entityManager(否则Spring Boot将无法正确执行自动配置)。最后,@Transactional如果您不手动处理事务,请确保您的方法正确。


3

我也刚刚经历了这个。就我而言,我必须使子表具有可为空的外键字段,然后通过将其设置为null,然后调用save,delete和flush来从关系中删除父表。

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


我遇到了同样的问题,并遇到了这个问题。我不知道我是否称其为解决方案或解决方法,但这是如何删除子对象的方法。
Brian Kates 2015年


1

您ID的初始值为500。这意味着您的ID以500开头

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

然后您选择一个ID为1的项目

 Token deleted = tokenRepository.findOne(1L);

因此,请检查您的数据库以明确说明


我不认为这是这里的问题,因为当我播种用测试数据测试,我指定ID为1
曲折麦基


1
@Transactional
int deleteAuthorByName(String name);

您应该在存储库中编写@Transactional扩展JpaRepository


0

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


0

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.