【问题标题】:Hibernate @OneToOne @NotNull休眠@OneToOne @NotNull
【发布时间】:2011-02-13 02:01:14
【问题描述】:

在关系的两边声明@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

如果可能,我想避免放松约束,因为双方必须都在场。

【问题讨论】:

  • 对我来说似乎是一个有问题的数据模型。
  • 两边都有级联有点奇怪,但应该不会有问题。你能详细说明一下吗?
  • 级联实际上是一种霰弹枪方法,试图让瞬态属性持续存在。它不必在详细信息记录中

标签: java hibernate orm jpa bean-validation


【解决方案1】:

由于您的级联类型,它应该保持瞬态值。

如果您实际上是在设置另一个瞬态元素之前尝试保留第一个元素,那么您会遇到此错误。

您指定的约束仅指定该值不能在数据库中为空,而不是在数据模型中,显然当您构造对象的新实例时,引用将为空。当引用为空时,您不能持久化实体。

【讨论】:

  • 感谢您阐明数据库与模型方面。有道理。
【解决方案2】:

在关系的两侧声明@OneToOne@NotNull 是否有效(...)我找不到任何说这是无效的,但似乎在持久性期间至少有一侧必须违反关系。 (例如,如果先写changeEntrychangeEntryDetails 将暂时为空)。

它是有效的,并且在正确映射的实体上一切正常。您需要将双向关联的一侧声明为“拥有”侧(此“控制”插入顺序)。一种可行的解决方案:

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

【讨论】:

  • 你,先生,摇滚。出色的答案 - 非常感谢。如果我可能会问 - 为什么在 @OneToOne 声明中同时声明 @NotNull 和 optional=false ?它们有不同的用途吗?
  • @Marty 不客气,很高兴你觉得它有帮助。关于 @NotNull@JoinColumn(nullable=false) 的使用,我对 Bean Validation 规范的 Appendix D. 的理解是,对于 Persistence Providers,生成 Bean Validation-aware DDL 不是强制性的,所以我同时使用 JPA 和 BV API,只是以防万一。
【解决方案3】:

如果您在这里遇到与 openJPA 相同的问题并且 Pascals 解决方案仍然不适合您,您可能需要在您的 persistence.xml 中将 openJPA 属性 openjpa.InverseManager 设置为 true

【讨论】:

    猜你喜欢
    • 2021-06-25
    • 2012-10-14
    • 2012-07-30
    • 2011-02-25
    • 2014-12-27
    • 1970-01-01
    • 2014-03-12
    • 1970-01-01
    相关资源
    最近更新 更多