HHH-9663 means that orphan removal doesn’t work for OneToOne relationships. For example, given File and FileContent as below (taken from the bug report):
package pl.comit.orm.model;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.OneToOne;
@Entity
public class File {
private int id;
private FileContent content;
@Id
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@OneToOne(fetch = FetchType.LAZY, orphanRemoval = true)
public FileContent getContent() {
return content;
}
public void setContent(FileContent content) {
this.content = content;
}
}
package pl.comit.orm.model;
import javax.persistence.Entity;
import javax.persistence.Id;
@Entity
public class FileContent {
private int id;
@Id
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
package pl.comit.orm.dao;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import pl.comit.orm.model.File;
import pl.comit.orm.model.FileContent;
@Repository
public class Dao {
@PersistenceContext
private EntityManager entityManager;
@Transactional
public void assureCreatedTaskAndNote(int fileId, int contentId) {
FileContent content = entityManager.find(FileContent.class, contentId);
if (content == null) {
content = new FileContent();
content.setId(contentId);
entityManager.persist(content);
}
File file = entityManager.find(File.class, fileId);
if (file == null) {
file = new File();
file.setId(fileId);
entityManager.persist(file);
}
file.setContent(content);
}
@Transactional
public void removeContent(int fileId) {
File file = entityManager.find(File.class, fileId);
file.setContent(null);
}
public FileContent find(int contentId) {
return entityManager.find(FileContent.class, contentId);
}
}
Running this as the main class will result in an exception:
package pl.comit.orm;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import pl.comit.orm.dao.Dao;
import pl.comit.orm.model.FileContent;
public final class Application {
private static final String CFG_FILE = "applicationContext.xml";
public static void main(String[] args) {
test(new ClassPathXmlApplicationContext(CFG_FILE).getBean(Dao.class));
}
public static void test(Dao dao) {
dao.assureCreatedTaskAndNote(1, 2);
dao.removeContent(1);
FileContent content = dao.find(2);
if (content != null) {
System.err.println("Content found: " + content);
}
}
}
A workaround is to manually remove and detach the old referent, and then persist the new referent. Here’s an updated File.java:
package pl.comit.orm.model;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.PersistenceContext;
@Entity
public class File {
private int id;
private FileContent content;
@Id
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@OneToOne(fetch = FetchType.LAZY, orphanRemoval = true)
public FileContent getContent() {
return content;
}
public void setContent(FileContent content) {
if(this.content != content){
final oldContent = this.content;
this.content = content;
if(oldContent!=null){
// Hibernate won't remove the oldContent for us, so do it manually; workaround HHH-9663
WorkaroundHHH9663.entityManager.remove(oldContent);
WorkaroundHHH9663.entityManager.detach(oldContent);
}
}
}
// WORKAROUND https://hibernate.atlassian.net/browse/HHH-9663 "Orphan removal does not work for OneToOne relations"
@Component
public static class WorkaroundHHH9663 {
@PersistenceContext
private EntityManager injectedEntityManager;
private static EntityManager entityManager;
@PostConstruct
public void postConstruct(){
entityManager = injectedEntityManager;
}
@PreDestroy
public void preDestroy(){
this.entityManager = null; // NOPMD
}
}
// END WORKAROUND
}
Note that no Dao changes were made, so if, for example, Spring Data was used instead of such a Dao, you wouldn’t have to modify anything else. And you can just remove this workaround code easily when a version of Hibernate because available with HHH-9963 fixed.
Finally, yes, this approach does exhibit a bit of code smell (the use of the static variable in this way and the entity having a container managed component aren’t exactly best practices), but, it’s a workaround – hopefully just a temporary one.