Действительно ли это допустимо для объявления @OneToOne
и @NotNull
с обеих сторон отношений, таких как:
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;
}
}
Я не могу найти ничего, что говорит, что это недопустимо, но кажется, что во время персистентности по крайней мере одна сторона отношений должна быть нарушена. (Например, при записи changeEntry сначала, changeEntryDetails будет пустым временно).
При попытке этого я вижу выданное исключение not-null property references a null or transient value
.
Я хотел бы постараться не ослаблять ограничение, если это возможно, потому что должны присутствовать обе стороны.
Допустимо ли объявлять
@OneToOne
и@NotNull
с обеих сторон отношения (...) Я не могу найти ничего, что говорило бы, что это недействительно, но это кажется, что при настойчивости должна быть нарушена хотя бы одна сторона отношений. (например, если сначала записатьchangeEntry
,changeEntryDetails
временно будет нулевым).
Это верно, и все работает нормально с правильно отображенными объектами. Вам нужно объявить одну сторону вашей двунаправленной ассоциации как «владеющую» (это «контролирует» порядок вставок). Одно возможное рабочее решение:
@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
}
И для другого объекта (обратите внимание на атрибут mappedBy
, установленный на стороне ассоциации, не являющейся владельцем):
@Entity
public class ChangeEntryDetails implements Serializable {
@Id
@GeneratedValue
private Long id;
@OneToOne(optional = false, mappedBy = "changeEntryDetails")
@NotNull
private ChangeEntry changeEntry;
// constructor, getters and setters
}
Для этих объектов следующий тест (для демонстрационных целей ) проходит:
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());
}
}
Оно должно сохранять временное значение из-за вашего каскадного типа.
Если вы на самом деле пытаетесь сохранить первый элемент до того, как зададите другой переходный элемент, вы ожидаете этой ошибки.
Указанное вами ограничение только указывает, что значение не может быть нулевым в базе данных, а не в модели данных, очевидно, когда вы создаете новый экземпляр объекта, ссылка будет нулевой. Пока ссылка пуста, вы не можете сохранить сущность.