2010-04-27 4 views
12

est-il valide pour déclarer @OneToOne et @NotNull des deux côtés d'une relation, tels que:Mise en veille prolongée @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; 
    } 
} 

Je ne peux pas trouver quelque chose qui dit que c'est invalide, mais il semble que, pendant la persistance d'au moins un côté de la relation doit être violée. (Par exemple, si vous écrivez changeEntry en premier, changeEntryDetails sera temporairement nul). En essayant cela, je vois une exception not-null property references a null or transient value levée.

Je voudrais éviter de relâcher la contrainte si possible, car les deux côtés doivent être présents.

+0

Cela me semble être un modèle de données problématique. – Yishai

+0

Avoir une cascade des deux côtés est un peu étrange, mais cela ne devrait pas vraiment poser problème. Pourriez-vous élaborer? – Geoff

+0

La cascade était vraiment une approche de coup de fusil pour essayer de maintenir la propriété transitoire à persister. Il ne doit pas être sur l'enregistrement Détails –

Répondre

14

Est-il valide pour déclarer @OneToOne et @NotNull des deux côtés d'une relation (...) Je ne peux pas trouver quelque chose qui dit que c'est invalide, mais il semble que pendant la persistance au moins un côté de la relation doit être violé. (Par exemple, si vous écrivez changeEntry en premier, changeEntryDetails sera nul temporairement).

Il est valide et tout fonctionne correctement avec les entités correctement mappées. Vous devez déclarer un côté de votre association bidirectionnelle comme le côté «propriétaire» (ce «contrôle» de l'ordre des insertions). Une solution de travail possible:

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

Et pour l'autre entité (notez l'attribut mappedBy situé sur le côté non propriétaire de l'association):

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

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

    // constructor, getters and setters 
} 

Avec ces entités, le test suivant (pour des fins de démonstration) passe:

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

Vous, monsieur, rock. Réponse exceptionnelle - merci beaucoup. Si je devrais demander - pourquoi déclarer @NotNull et optionnel = false sur la déclaration @OneToOne? Servent-ils à des fins différentes? –

+2

@Marty De rien, je vous remercie de votre aide.En ce qui concerne l'utilisation de '@ NotNull' et' @JoinColumn (nullable = false) ', ma compréhension de l '[Appendice D] (http://people.redhat.com/~ebernard/validation/#appendix-jpa) de la spécification Bean Validation est que générer DDL conscient de la validation de Bean n'est pas obligatoire pour les fournisseurs de persistance, donc j'utilise à la fois des API JPA et BV, au cas où. –

0

Il devrait persister la valeur transitoire en raison de votre type de cascade.

Si vous essayez réellement de conserver le premier élément avant d'avoir défini l'autre élément transitoire, vous vous attendez à cette erreur. La contrainte que vous avez spécifiée spécifie uniquement que la valeur ne peut pas être nulle dans la base de données, plutôt que dans le modèle de données, clairement lorsque vous construisez une nouvelle instance de l'objet, la référence sera null. Alors que la référence est null, vous ne pouvez pas persister l'entité.

+0

Merci d'avoir clarifié la base de données par rapport à l'aspect du modèle. Logique. –

0

Si vous avez été ici d'avoir le même problème avec OpenJPA et Pascals solution ne fonctionne toujours pas pour vous, vous pouvez définir la propriété openJPA openjpa.InverseManager à true dans votre persistence.xml

Questions connexes