@ManyToMany inconsistent data on both side problem
I have a blog-like scenario , with two Java classes : Post and Tag , which is a @ManyToMany relationship , with a Post_Tag association table , here is my simplified definitions:
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>();
}
It seems OK , but it fails in the following testing scenario :
- Create a Tag , tag1
- Create 1st Post , post1
- Create 2nd Post , post2
- add tag1 to post1.getTags() and post2.getTags()
- update post1 , post2
- List list = dao.getPostByTag(tag1)
- assert list.size() == 2 , FAILED
Here is my test code :
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));
}
And here is my dao.getPostsByTag()'s implementation :
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();
}
The returned list size == 0 ! I noticed the generated SQL command and found hibernate first getPostsByTag() and then insert to association table , which makes the getPostsByTag() return 0-length list. :
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 de开发者_StackOverflowsc limit ?
Hibernate:
insert
into
Post_Tag
(Post_id, Tag_id)
values
(?, ?)
Hibernate:
insert
into
Post_Tag
(Post_id, Tag_id)
values
(?, ?)
How do I make sure the getPostsByTag() is executed after
inserting the association table ?
I know there was 'endTransaction() , and startNewTransaction()' methods in spring-JUnit3 , but seems not available in spring-with-junit4.
But I wonder how can I pass this test in one
transaction ?
Thanks.
environments : Spring4 (SpringJUnit4ClassRunner) , hibernate-3.5.6 , JPA 2.0
You can create two methods in the test class that will be executed each time a test method is called. These methods will open the transaction and do rollback after it:
@Before public void setUp() throws Exception {
em.getTransaction().begin();
}
@After public void tearDown() throws Exception {
em.getTransaction().rollback();
}
You should check also if you have a flushmode different from the deffault because normally the flushs are made before a query...
Two things you can try:
- Call
session.flush()
on your hibernate session after the two update calls, before calling getPostsByTag(). This should push your changes to the database. - Fix your object management. When you have a two-sided association, hibernate expects you to correctly maintain both sides of the association.
Hence:
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);
It's a good idea to create methods which manage both sides of the association at once.
精彩评论