Hibernate @OneToOne @NotNull

Можно ли объявить @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 будет null временно).

При попытке этого я вижу исключение, сброшенное not-null property references a null or transient value.

Я хотел бы избежать ослабления ограничения, если это возможно, потому что присутствуют обе стороны должны.

Ответ 1

Можно ли объявить @OneToOne и @NotNull с обеих сторон отношения (...), я не могу найти ничего, что говорит о том, что это неверно, но кажется, что при сохранении хотя бы одной стороны отношения должны быть нарушены. (например, при записи changeEntry во-первых, changeEntryDetails будет иметь значение null временно).

Это действительно так, и все работает нормально с правильно отображенными объектами. Вам нужно объявить одну сторону вашей двунаправленной ассоциации как "владеющую" стороной (это "управление" порядком вставок). Одно из возможных рабочих решений:

@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());
    }
}

Ответ 2

Должно сохраняться переходное значение из-за вашего типа каскада.

Если вы на самом деле пытаетесь перенести первый элемент до того, как вы установите другой переходный элемент, вы можете ожидать эту ошибку.

Указанное ограничение указывает только, что значение не может быть нулевым в базе данных, а не в модели данных, ясно, когда вы создадите новый экземпляр объекта, ссылка будет нулевой. Хотя ссылка имеет значение null, вы не можете сохранить объект.

Ответ 3

Если у вас возникла такая же проблема с openJPA, и решение Pascals все еще не работает для вас, вы можете установить свойство openJPA openjpa.InverseManager на true в вашем файле persistence.xml