Delete Not Working with JpaRepository

前端 未结 12 2009
情书的邮戳
情书的邮戳 2020-12-07 11:55

I have a spring 4 app where I\'m trying to delete an instance of an entity from my database. I have the following entity:

@Entity
public class Token impleme         


        
相关标签:
12条回答
  • 2020-12-07 12:31

    Your initial value for id is 500. That means your id starts with 500

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

    And you select one item with id 1 here

     Token deleted = tokenRepository.findOne(1L);
    

    So check your database to clarify that

    0 讨论(0)
  • 2020-12-07 12:41

    In my case was the CASCADE.PERSIST, i changed for CASCADE.ALL, and made the change through the cascade (changing the father object).

    0 讨论(0)
  • 2020-12-07 12:42

    Most probably such behaviour occurs when you have bidirectional relationship and you're not synchronizing both sides WHILE having both parent and child persisted (attached to the current session).

    This is tricky and I'm gonna explain this with the following example.

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

    Let's write a test (a transactional one btw)

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

    Pretty simple test right? We're creating parent and child, save it to database, then fetching a child from database, removing it and at last making sure everything works just as expected. And it's not.

    The delete here didn't work because we didn't synchronized the other part of relationship which is PERSISTED IN CURRENT SESSION. If Parent wasn't associated with current session our test would pass, i.e.

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

    and

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

    Of course it only proves my point and explains the behaviour OP faced. The proper way to go is obviously keeping in sync both parts of relationship which means:

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

    Obviously @PreRemove could be used here.

    0 讨论(0)
  • 2020-12-07 12:43

    I've the same problem, test is ok but on db row isn't deleted.

    have you added the @Transactional annotation to method? for me this change makes it work

    0 讨论(0)
  • 2020-12-07 12:46

    You need to add PreRemove function ,in the class where you have many object as attribute e.g in Education Class which have relation with 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);
        }
    }
    
    0 讨论(0)
  • 2020-12-07 12:46

    This is for anyone coming from Google on why their delete method is not working in Spring Boot/Hibernate, whether it's used from the JpaRepository/CrudRepository's delete or from a custom repository calling session.delete(entity) or entityManager.remove(entity).

    I was upgrading from Spring Boot 1.5 to version 2.2.6 (and Hibernate 5.4.13) and had been using a custom configuration for transactionManager, something like this:

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

    And I managed to solve it by using @EnableTransactionManagement and deleting the custom transactionManager bean definition above.

    If you still have to use a custom transaction manager of sorts, changing the bean definition to the code below may also work:

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

    As a final note, remember to enable Spring Boot's auto-configuration so the entityManagerFactory bean can be created automatically, and also remove any sessionFactory bean if you're upgrading to entityManager (otherwise Spring Boot won't do the auto-configuration properly). And lastly, ensure that your methods are @Transactional if you're not dealing with transactions manually.

    0 讨论(0)
提交回复
热议问题