Hibernate @OneToOne @NotNull

15

在关系中的两端同时声明@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

如果可能,我想避免放宽约束,因为两侧必须存在。


对我来说,这似乎是一个有问题的数据模型。 - Yishai
在两侧都使用级联可能有点奇怪,但实际上不应该有问题。您能详细说明一下吗? - Geoff
级联实际上是一种散弹式的方法,旨在使瞬态属性持久化。它不必在详细记录上。 - Marty Pitt
3个回答

19
声明在关系的两侧都使用@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());
    }
}

1
您真棒。非常出色的回答,非常感谢。如果我可以问一下 - 为什么在@OneToOne声明中同时声明@NotNull和optional=false?它们有不同的作用吗? - Marty Pitt
2
@Marty 不用谢,很高兴你觉得有帮助。关于同时使用@NotNull@JoinColumn(nullable=false),根据Bean Validation规范的附录D的理解,生成支持Bean Validation的DDL对于持久化提供者来说并不是强制性的,因此我会同时使用JPA和BV API,以防万一。 - Pascal Thivent

0
它应该因为级联类型而持久化瞬态值。
如果您实际上是在设置其他瞬态元素之前尝试持久化第一个元素,那么您会遇到此错误。
您指定的约束仅指定数据库中的值不能为空,而不是数据模型中的值,在构造对象的新实例时,引用将为空。当引用为空时,您无法持久化实体。

感谢您澄清数据库与模型方面的问题。很有道理。 - Marty Pitt

0
如果您遇到与 openJPA 相关的问题,并且 Pascals 的解决方案仍然无法解决您的问题,您可能需要在 persistence.xml 中将 openJPA 属性 openjpa.InverseManager 设置为 true

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接