2010-04-27 23 views
12

¿Es válido para declarar @OneToOne y @NotNull a ambos lados de una relación, tales como:Hibernate @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; 
    } 
} 

no puedo encontrar nada de lo que dice que esto no es válido, pero parece que durante persistencia, se debe violar al menos un lado de la relación. (Por ejemplo, si escribe changeEntry primero, changeEntryDetails será nulo temporalmente).

Al intentar esto, veo una excepción lanzada not-null property references a null or transient value.

me gustaría evitar la relajación de la restricción, si es posible, debido a que ambos lados deben estar presente.

+0

Me parece un modelo de datos problemático. – Yishai

+0

Tener cascada en ambos lados es un poco extraño, pero no debería ser realmente problemático. ¿Podrías elaborar? – Geoff

+0

La cascada era realmente un enfoque de disparo para tratar de que persistiera la propiedad transitoria. No tiene que estar en el registro de detalles –

Respuesta

14

¿Es válido para declarar @OneToOne y @NotNull a ambos lados de una relación (...) no puedo encontrar nada de lo que dice que esto no es válido, pero parece que durante la persistencia de al menos uno de los lados de la relación debe ser violado (por ejemplo, si escribe changeEntry primero, changeEntryDetails será nulo temporalmente).

Es válido y todo funciona bien con entidades debidamente mapeadas. Debe declarar un lado de su asociación bidireccional como el lado "dueño" (este "control" el orden de los insertos). Una posible solución de trabajo:

@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 
} 

Y por la otra entidad (tenga en cuenta el atributo mappedBy situado en el lado de no ser dueño de la asociación):

@Entity 
public class ChangeEntryDetails implements Serializable { 
    @Id 
    @GeneratedValue 
    private Long id; 

    @OneToOne(optional = false, mappedBy = "changeEntryDetails") 
    @NotNull 
    private ChangeEntry changeEntry; 

    // constructor, getters and setters 
} 

Con estas entidades, la siguiente prueba (por fines de demostración) pasa:

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

Usted, señor, rock. Excelente respuesta, muchas gracias. Si pudiera preguntar, ¿por qué declarar @NotNull y opcional = false en la declaración @OneToOne? ¿Sirven para diferentes propósitos? –

+2

@Marty De nada, me alegra que lo encuentre útil.Con respecto al uso de ambos '@ NotNull' y' @JoinColumn (nullable = false) ', mi comprensión del [Apéndice D.] (http://people.redhat.com/~ebernard/validation/#appendix-jpa) de la especificación de Validación de frijol es que la generación de DDL con conciencia de validación de frijol no es obligatoria para los proveedores de persistencia, por lo que uso ambas API JPA y BV, por si acaso. –

0

Debería persistir el valor transitorio debido a su tipo de cascada.

Si en realidad está intentando persistir el primer elemento antes de haber configurado el otro elemento transitorio, entonces esperaría este error.

La restricción que ha especificado solamente especifica que el valor no puede ser nulo en la base de datos, en lugar de en el modelo de datos, con claridad cuando se construye una nueva instancia del objeto, la referencia será nulo. Si bien la referencia es nula, no puede persistir la entidad.

+0

Gracias por aclarar la base de datos frente al aspecto del modelo. Tiene sentido. –

0

, si tienes aquí tienen el mismo problema con solución OpenJPA y pascales todavía no funciona para usted, es posible que desee establecer la propiedad openJPA openjpa.InverseManager en true en su persistence.xml

Cuestiones relacionadas