Hibernate @OneToOne @NotNull - PullRequest
       60

Hibernate @OneToOne @NotNull

13 голосов
/ 28 апреля 2010

Допустимо ли объявлять @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.

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

Ответы [ 3 ]

18 голосов
/ 28 апреля 2010

Допустимо ли объявлять @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());
    }
}
0 голосов
/ 15 июня 2011

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

0 голосов
/ 28 апреля 2010

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

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

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

...