Subtle behavior difference between OpenJPA and Hibernate
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.