2010-11-16 5 views
2

j'ai un scénario blog semblable, avec deux classes Java: Post et Tag, qui est une relation @ManyToMany, avec une table d'association Post_Tag, voici mes définitions simplifiées:données incohérentes de @ManyToMany sur les deux problèmes de côté

public class Post 
{ 
    @ManyToMany(fetch=FetchType.LAZY) 
    @Fetch(FetchMode.SELECT) 
    @JoinTable(name = "Post_Tag" 
    , joinColumns  = @JoinColumn(name="Post_id") 
    , inverseJoinColumns = @JoinColumn(name="Tag_id") 
    ) 
    private Set<PostTag> tags = new HashSet<PostTag>(); 
} 

public class Tag 
{ 
    @ManyToMany(mappedBy="tags" , fetch=FetchType.LAZY) 
    private Set<Post> comments = new HashSet<Post>(); 
} 

il semble OK, mais il échoue dans le scénario de test suivant:

  1. Créer une marque, balise1
  2. Créer 1er post, post1
  3. Créer 2ème message , Post2
  4. add balise1 à post1.getTags() et() post2.getTags
  5. mise à jour post1, post2
  6. Liste list = dao.getPostByTag (balise1)
  7. assert list.size() == 2 , ECHEC

Voici mon code de test:

public void testGetCommentsByTag() 
{ 
    Tag tag1 = tagDao.save(new Tag("tag1")); 
    assertTrue(tag1.getId() > 0); 

    Post post1 = dao.save("..."); 
    Post post2 = dao.save("..."); 

    post1.getTags().add(tag1); 
    post2.getTags().add(tag1); 
    dao.update(post1); 
    dao.update(post2); 

    List<Post> list = dao.getPostsByTag(tag1 , 0 , 100); 

    assertSame(2 , list.size()); // FAILED ! 
    assertTrue(list.contains(post1)); 
    assertTrue(list.contains(post2)); 
} 

Et voici mon dao.getPostsByTag() la mise en œuvre de:

public List<Post> getPostsByTag(Tag tag , int start, int count) 
{ 
    Session session = (Session) em.getDelegate(); 
    Criteria c = session.createCriteria(Post.class); 

    c.createCriteria("tags") 
    .add(Restrictions.eq("id", tag.getId())); 

    c.setFirstResult(start); 
    c.setMaxResults(count); 
    c.setCacheable(true); 

    return c.list(); 
} 

La taille de la liste renvoyée == 0! J'ai remarqué la commande SQL générée et trouvé hibernate getPostsByTag() d'abord, puis insérer dans la table d'association, ce qui rend la getPostsByTag() retourner la liste de longueur 0. :

Hibernate: 
    insert 
    into 
     Tag 
    values 
     (?, ?, ?, ?) 
Hibernate: 
    insert 
    into 
     Post 
     (...) 
    values 
     (???) 
Hibernate: 
    insert 
    into 
     Post 
     (...) 
    values 
     (???) 
Hibernate: 
    select 
     ooxx 
    from 
     Post this_ 
    inner join 
     Post_Tag tags3_ 
      on this_.id=tags3_.Post_id 
    inner join 
     Tag tag1_ 
      on tags3_.Tag_id=tag1_.id 
    where 
     and tag1_.id=? 
    order by 
     this_.created desc limit ? 

Hibernate: 
    insert 
    into 
     Post_Tag 
     (Post_id, Tag_id) 
    values 
     (?, ?) 
Hibernate: 
    insert 
    into 
     Post_Tag 
     (Post_id, Tag_id) 
    values 
     (?, ?) 

Comment puis-je faire que le getPostsByTag() est exécutée after l'insertion de la table d'association?

Je sais qu'il y avait des méthodes 'endTransaction(), et startNewTransaction()' dans spring-JUnit3, mais ne semble pas disponible dans spring-with-junit4.

Mais je me demande comment puis-je passer ce test dans one transaction? Merci.

environnements: Spring4 (SpringJUnit4ClassRunner), mise en veille prolongée-3.5.6, JPA 2.0

Répondre

1

Vous pouvez créer deux méthodes dans la classe de test qui seront exécutées chaque fois qu'une méthode de test est appelée.Ces méthodes ouvriront la transaction et ne rollback après:

@Before public void setUp() throws Exception { 
    em.getTransaction().begin(); 
} 

@After public void tearDown() throws Exception {  
     em.getTransaction().rollback(); 
} 

Vous devriez également vérifier si vous avez un FlushMode différent du deffault parce que normalement les flushs sont faits avant une requête ...

1

Deux choses que vous pouvez essayer:

  1. Appel session.flush() sur votre session de mise en veille prolongée après les deux appels de mise à jour, avant d'appeler getPostsByTag(). Cela devrait pousser vos changements à la base de données.
  2. Corrigez la gestion de vos objets. Lorsque vous avez une association à deux faces, Hibernate s'attend à ce que vous conserviez correctement les deux côtés de l'association.

Par conséquent:

Tag tag1 = tagDao.save(new Tag("tag1")); 
assertTrue(tag1.getId() > 0); 

Post post1 = dao.save("..."); 
Post post2 = dao.save("..."); 

post1.getTags().add(tag1); 
tag1.getPosts().add(post1); 
post2.getTags().add(tag1); 
tag1.getPosts().add(tag2); 
dao.update(post1); 
dao.update(post2); 

Il est une bonne idée de créer des méthodes qui gèrent les deux côtés de l'association à la fois.

+0

Salut, je essayé la 2ème manière, en gérant soigneusement les deux côtés de l'association, mais dao.getPostsByTag() est toujours exécuté avant l'hibernation en insérant la table d'association. J'ai essayé d'insérer tagDao.update (tag1) avant dao.getPostsByTag(), mais toujours la même mauvaise réponse (liste vide). – smallufo

+0

Avez-vous essayé d'appeler session.flush()? – RMorrisey

+0

Bonjour, J'ai un problème pour appeler session.flush(), car il s'agit d'une classe de test, qui est en dehors de la portée d'hibernate. Ces DAO (postDao, tagDao) sont injectés dans la classe (au printemps). La classe de test ne connaît pas les implémentations sous-jacentes. Oui, il existe peut-être un moyen d'obtenir la session actuellement liée à threadLocal ou quelque chose, mais je pense que ce n'est pas une façon normale de «vider session» dans la classe de test. Il devrait y avoir un moyen pour que hibernate sache qu'il devrait exécuter getPostsByTag() après la mise à jour de la table d'association, n'est-ce pas? – smallufo

Questions connexes