Hibernate - How to remove entity from parent when deleting child

The following code is not working

        User user = new User();
        Project project = new Project();
        project.setOwner(user);

        projectRepository.delete(project);

        assertEquals(0, user.getProjects().size());

How should I configure the associations in order to achieve this?

Currently I have

public class User {
    @OneToMany(mappedBy = "owner")
    private Set<Project> projects = new HashSet<Project>();
}
public class Project {
    @ManyToOne
    private User owner;
}

The test returns

java.lang.AssertionError: 
Expected :0
Actual   :1

I try and succeed to add hibernate-enhance-maven-plugin but it only helps for the project.setOwner(user); not for the delete.

You need to add all the entities involved, the existing data related to the deleting entities, the data access logic that you are executing and the actual Hibernate log with the SQL statements generated by Hibernate.

I’ll take a look at it afterwards.

Java is complicated :wink:

Actually I’m at the beginning of my app and what you see is pretty much I have.

The complete test case:

    @Test
    public void testProjectAssociation() {
        User user = new User();
        user.setName("My user");

        Project project = new Project();
        project.setName("My project");
        project.setOwner(user);

        project = new Project();
        project.setName("My project");
        project.setOwner(user);

        user = userRepository.save(user);
        project = projectRepository.save(project);

        assertEquals("My user", project.getOwner().getName());
        assertEquals(2, user.getProjects().size());

        projectRepository.delete(project);
        projectRepository.flush();
        userRepository.flush();

        //user.getProjects().remove(project);

        assertEquals("My user", project.getOwner().getName());
        assertEquals(1, user.getProjects().size());
    }

The log:

2019-01-28 22:15:39.213  INFO 74436 --- [           main] o.s.t.c.transaction.TransactionContext   : Began transaction (1) for test context [DefaultTestContext@33afa13b testClass = UserTest, testInstance = com.diffshelf.domain.UserTest@78aab498, testMethod = testProjectAssociation@UserTest, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@7a4ccb53 testClass = UserTest, locations = '{}', classes = '{class com.diffshelf.DiffshelfApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTestContextBootstrapper=true}', contextCustomizers = set[[ImportsContextCustomizer@309e345f key = [org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration, org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration, org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration, org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration, org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration, org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration, org.springframework.boot.test.autoconfigure.jdbc.TestDatabaseAutoConfiguration, org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManagerAutoConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@13eb8acf, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@43738a82, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.autoconfigure.OverrideAutoConfigurationContextCustomizerFactory$DisableAutoConfigurationContextCustomizer@401e7803, org.springframework.boot.test.autoconfigure.filter.TypeExcludeFiltersContextCustomizer@351584c0, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@20395fc8, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@27d415d9], contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map[[empty]]]; transaction manager [org.springframework.orm.jpa.JpaTransactionManager@9cfc77]; rollback [true]
2019-01-28 22:15:39.270 DEBUG 74436 --- [           main] org.hibernate.SQL                        : call next value for hibernate_sequence
Hibernate: call next value for hibernate_sequence
2019-01-28 22:15:39.292 DEBUG 74436 --- [           main] org.hibernate.SQL                        : call next value for hibernate_sequence
Hibernate: call next value for hibernate_sequence
2019-01-28 22:15:39.312 DEBUG 74436 --- [           main] org.hibernate.SQL                        : insert into users (createdAt, updatedAt, email, name, id) values (?, ?, ?, ?, ?)
Hibernate: insert into users (createdAt, updatedAt, email, name, id) values (?, ?, ?, ?, ?)
2019-01-28 22:15:39.314 TRACE 74436 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [TIMESTAMP] - [2019-01-28T22:15:39.309]
2019-01-28 22:15:39.316 TRACE 74436 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [TIMESTAMP] - [2019-01-28T22:15:39.310]
2019-01-28 22:15:39.316 TRACE 74436 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [3] as [VARCHAR] - [null]
2019-01-28 22:15:39.317 TRACE 74436 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [4] as [VARCHAR] - [My user]
2019-01-28 22:15:39.317 TRACE 74436 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [5] as [INTEGER] - [1]
2019-01-28 22:15:39.318 DEBUG 74436 --- [           main] org.hibernate.SQL                        : insert into projects (createdAt, updatedAt, apiKey, name, owner_id, id) values (?, ?, ?, ?, ?, ?)
Hibernate: insert into projects (createdAt, updatedAt, apiKey, name, owner_id, id) values (?, ?, ?, ?, ?, ?)
2019-01-28 22:15:39.318 TRACE 74436 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [TIMESTAMP] - [2019-01-28T22:15:39.318]
2019-01-28 22:15:39.318 TRACE 74436 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [TIMESTAMP] - [2019-01-28T22:15:39.318]
2019-01-28 22:15:39.318 TRACE 74436 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [3] as [VARCHAR] - [null]
2019-01-28 22:15:39.318 TRACE 74436 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [4] as [VARCHAR] - [My project]
2019-01-28 22:15:39.318 TRACE 74436 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [5] as [INTEGER] - [1]
2019-01-28 22:15:39.318 TRACE 74436 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [6] as [INTEGER] - [2]
2019-01-28 22:15:39.322 DEBUG 74436 --- [           main] org.hibernate.SQL                        : delete from projects where id=?
Hibernate: delete from projects where id=?
2019-01-28 22:15:39.322 TRACE 74436 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [INTEGER] - [2]
2019-01-28 22:15:39.330  INFO 74436 --- [           main] o.s.t.c.transaction.TransactionContext   : Rolled back transaction for test: [DefaultTestContext@33afa13b testClass = UserTest, testInstance = com.diffshelf.domain.UserTest@78aab498, testMethod = testProjectAssociation@UserTest, testException = java.lang.AssertionError: expected:<1> but was:<2>, mergedContextConfiguration = [MergedContextConfiguration@7a4ccb53 testClass = UserTest, locations = '{}', classes = '{class com.diffshelf.DiffshelfApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTestContextBootstrapper=true}', contextCustomizers = set[[ImportsContextCustomizer@309e345f key = [org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration, org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration, org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration, org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration, org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration, org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration, org.springframework.boot.test.autoconfigure.jdbc.TestDatabaseAutoConfiguration, org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManagerAutoConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@13eb8acf, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@43738a82, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.autoconfigure.OverrideAutoConfigurationContextCustomizerFactory$DisableAutoConfigurationContextCustomizer@401e7803, org.springframework.boot.test.autoconfigure.filter.TypeExcludeFiltersContextCustomizer@351584c0, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@20395fc8, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@27d415d9], contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map[[empty]]]

What do you mean with the data access logic that you are executing?

  1. The entities are missing from your posting.
  2. The log message does not indicate any issue.

Try to replicate it with this test case template. Once you have the replicating test case in place, give me the URL of your GitHub repository and I’ll take a look at it.

Hey Vlad! You can find the test case here: https://github.com/cbou/hibernate-test-case-templates

Thanks!

Your code is not correct because you don’t synchronize both sides of the bidirectional association. Check out this article for more details.

The same is true for adding and removing the entity. The remove operation will work just fine if you use LAZY fetching strategy for the many-to-one association. Check out this article for more details.

Ok I can see on your link what you mean with not correct. I didn’t implement User.addPost and User.removePost.

I thought there would be a way for Java to achieve this automatically without having to write any code. I’m looking for a way to achieve this like enableAssociationManagement but it seems that this boolean only activate the bi directional loading but not the delete.

I update the code and make my problem more self explaining.

I added a folder to the entities.

Now if I what to correctly delete a project I have to do it like that:

		folder.getProjects().remove(project);
		user.getProjects().remove(project);
		project.setOwner(null);
		project.setFolder(null);
		entityManager.remove(project);

This is really annoying as I have to think about adding two more lines for the next OneToMany association.

Another problem in your test case is that you use the same EntityManager to create and remove the entity. That’s not a realistic scenario. Just try to use a new EntityManager and you’ll see that removing a child entity does not require to remove it from the parent association.

Why is that so unrealistic? If you don’t need so much power in EntityManager you would use only one without customising it. This is typically an error new users will face when starting learning Hibernate and make the learning harder. Like for me :frowning:

Show me a real-life use case where this makes sense.

If you don’t need so much power in EntityManager you would use only one without customising it.

Why do you mean by power? Why would you use only one? What customization are you talking about?

This is typically an error new users will face when starting learning Hibernate and make the learning harder.

A new user that reads the Hibernate User Guide will learn how to avoid issues like this one.

Maybe I don’t understand you correctly. Why should I use multiple EntityManager ?

Because it’s unlikely that you will ever create and delete the very same entities or database rows in the same EntityManager, transaction or database connection.