The problem

Recently I’ve stumbled upon a weird snippet of code in a project I’ve been involved into. The previous developer set some entity relations to null before deleting it. I was wondering why he did so. The project uses Hibernate.

So I decided to investigate this.

Here are the components (these classes are made up only for this case) :

@Entity
@Table(name = "artists")
public class Artist {

    @NotNull
    private String artistName;

    @OneToMany(cascade = CascadeType.ALL)
    @JoinColumns(@JoinColumn(name = "artistId"))
    private Set<Album> albums = new HashSet<Album>();

    // Id and getters/setters not shown for the sake of brevity.
}

@Entity
@Table(name = "albums")
public class Album {

    @NotNull
    private String name;
}

And here is a test case :

 
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class,
        CleanDataSetTestExecutionListener.class })
public class ArtistRepositoryTest implements ApplicationContextAware {

    @Autowired
    private ArtistRepository dao;

    @Test
    @DatabaseSetup("/testDelete.xml")
    public void testDelete() {
        Artist artist = dao.findByArtistName("Led Zeppelin");
        assertNotNull(artist);

        artist.setAlbums(null);
        dao.delete(artist);

        assertEquals("Number of artists in table =>", 0, countRowsInTable("artists"));
        assertEquals("Number of albums in table =>", 1, countRowsInTable("albums"));
    }
}

With the flat xml dataset :

<?xml version="1.0" encoding="UTF-8"?>
<dataset>
    <artists id="1" artistName="Led Zeppelin"/>
    <albums id="1" name="Physical Graffiti" artistId="1"/>
</dataset>

Note that I’m not executing this test in a transaction, the transaction demarcation is on the findByArtistName() method.

I’ve launched this test case with both OpenJPA (2.2.1) and Hibernate (3.6.9)

The results

OpenJPA

With OpenJPA, I get the following :

java.lang.AssertionError: Number of albums in table => expected:<1> but was:<0>
    at org.junit.Assert.fail(Assert.java:93)
    at org.junit.Assert.failNotEquals(Assert.java:647)
    at org.junit.Assert.assertEquals(Assert.java:128)
    at org.junit.Assert.assertEquals(Assert.java:472)
    at be.codinsanity.sandbox.db.ArtistRepositoryTest.testDelete(ArtistRepositoryTest.java:51)
    ...

This seems normal : since I delete the artist and there is a CascadeType.ALL associated to albums, it alse deletes the latter, even though I set it null.

Now let’s try with Hibernate

Hibernate

Here’s a completely different story since the test pass ! So it means that setting the albums collection to null really has an impact on the behavior. Better, if I don’t change the albums field, the test don’t pass anymore, which is what I was expected to see at first.

So it seems Hibernate updates the state of the entity before deleting it in the database. Is it a correct behavior ? Can we change this with a property ?

Reflections

I must say that I’m very intrigued by this behavior. I must investigate further in order to have the final word on this, but time lacks. If someone has the answer to this, please leave a comment.