Is it valid to declare @OneToOne
and @NotNull
on both sides of a relationship, such as:
class ChangeEntry
{
@OneToOne(cascade=CascadeType.ALL)
@NotNull
ChangeEntryDetails changeEntryDetails;
public void addDetails(ChangeEntryDetails details) {
this.changeEntryDetails = details;
details.setChangeEntry(this);
}
}
class ChangeEntryDetails
{
@OneToOne(cascase=CascadeType.ALL)
@NotNull
ChangeEntry changeEntry;
public void setChangeEntry(ChangeEntry changeEntry)
{
this.changeEntry = changeEntry;
}
}
I can't find anything that says this is invalid, but it seems that during persistence at least one side of the relationship must be violated. (Eg., if writing changeEntry first, changeEntryDetails will be null temporarily).
When trying this, I see an exception thrown not-null property references a null or transient value
.
I'd like to avoid relaxing the constraint if possible, because both sides must be present.
Is it valid to declare
@OneToOne
and@NotNull
on both sides of a relationship (...) I can't find anything that says this is invalid, but it seems that during persistence at least one side of the relationship must be violated. (e.g. if writingchangeEntry
first,changeEntryDetails
will be null temporarily).
It is valid and everything works fine with properly mapped entities. You need to declare one side of your bi-directional association as the "owning" side (this "control" the order of inserts). One possible working solution:
@Entity
@NamedQueries( { @NamedQuery(name = ChangeEntry.FIND_ALL_CHANGEENTRIES, query = "SELECT c FROM ChangeEntry c") })
public class ChangeEntry implements Serializable {
public final static String FIND_ALL_CHANGEENTRIES = "findAllChangeEntries";
@Id
@GeneratedValue
private Long id;
@OneToOne(optional = false, cascade = CascadeType.ALL)
@JoinColumn(name = "DETAILS_ID", unique = true, nullable = false)
@NotNull
private ChangeEntryDetails changeEntryDetails;
public void addDetails(ChangeEntryDetails details) {
this.changeEntryDetails = details;
details.setChangeEntry(this);
}
// constructor, getters and setters
}
And for the other entity (note the mappedBy
attribute set on the non-owning side of the association):
@Entity
public class ChangeEntryDetails implements Serializable {
@Id
@GeneratedValue
private Long id;
@OneToOne(optional = false, mappedBy = "changeEntryDetails")
@NotNull
private ChangeEntry changeEntry;
// constructor, getters and setters
}
With these entities, the following test (for demonstration purposes) passes:
public class ChangeEntryTest {
private static EntityManagerFactory emf;
private EntityManager em;
@BeforeClass
public static void createEntityManagerFactory() {
emf = Persistence.createEntityManagerFactory("TestPu");
}
@AfterClass
public static void closeEntityManagerFactory() {
emf.close();
}
@Before
public void beginTransaction() {
em = emf.createEntityManager();
em.getTransaction().begin();
}
@After
public void rollbackTransaction() {
if (em.getTransaction().isActive()) {
em.getTransaction().rollback();
}
if (em.isOpen()) {
em.close();
}
}
@Test
public void testCreateEntryWithoutDetails() {
try {
ChangeEntry entry = new ChangeEntry();
em.persist(entry);
fail("Expected ConstraintViolationException wasn't thrown.");
} catch (ConstraintViolationException e) {
assertEquals(1, e.getConstraintViolations().size());
ConstraintViolation<?> violation = e.getConstraintViolations()
.iterator().next();
assertEquals("changeEntryDetails", violation.getPropertyPath()
.toString());
assertEquals(NotNull.class, violation.getConstraintDescriptor()
.getAnnotation().annotationType());
}
}
@Test
public void testCreateDetailsWithoutEntry() {
try {
ChangeEntryDetails details = new ChangeEntryDetails();
em.persist(details);
fail("Expected ConstraintViolationException wasn't thrown.");
} catch (ConstraintViolationException e) {
assertEquals(1, e.getConstraintViolations().size());
ConstraintViolation<?> violation = e.getConstraintViolations()
.iterator().next();
assertEquals("changeEntry", violation.getPropertyPath()
.toString());
assertEquals(NotNull.class, violation.getConstraintDescriptor()
.getAnnotation().annotationType());
}
}
@Test
public void validEntryWithDetails() {
ChangeEntry entry = new ChangeEntry();
ChangeEntryDetails details = new ChangeEntryDetails();
entry.addDetails(details);
em.persist(entry);
Query query = em.createNamedQuery(ChangeEntry.FIND_ALL_CHANGEENTRIES);
assertEquals(1, query.getResultList().size());
}
}