Reputation: 1910
I have Article
and Tag
entities:
@Entity
@Table(name = "articles")
public class Article implements Serializable{
//other things
@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private List<Tag> tags;
}
@Entity
@Table(name = "tags")
public class Tag implements Serializable{
//other things
@ManyToMany(mappedBy="tags" ,cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private List<Article> articles;
}
And the idea is: I have 3 Article
s when First has first
and second
tags. Second has second
and third
tags. Second has first
and third
tags. When I filter Article
s by first
tag, I get two Article
s - because first and third Article
s was tagged with first
tag. When I filter Article
s by second
tag and third
, I got all 3 Article
s - because every Article
was tagged with one of them. Generally, the goal is to filter Article
s by one of specified Tag
s. I wrote a Unit test which shows what I want to achieve:
@Test
public void test_WhenTagIdsAreSpecified_ArticlesShouldBeFilteredByOneOfTags() throws Exception {
AuthorDTO author = getExpectedAuthor();
List<TagDTO> tags = saveThreeTags();
TagDTO firstTag = tags.get(0);
TagDTO secondTag = tags.get(1);
TagDTO thirdTag = tags.get(2);
service.save(new ArticleDTO(null, author,
Arrays.asList(firstTag, secondTag), articleContent + "_firstAndSecondTag"));
service.save(new ArticleDTO(null, author,
Arrays.asList(secondTag, thirdTag), articleContent + "_secondAndThirdTag"));
service.save(new ArticleDTO(null, author,
Arrays.asList(firstTag, thirdTag), articleContent + "_firstAndThirdTag"));
assertEquals(3, tagService.getAll().size());
Collection<Long> tagIdsContainingFirst = Collections.singletonList(firstTag.getId());
List<ArticleDTO> articlesByFirstTag = service.getByTags(tagIdsContainingFirst);
Collection<Long> tagIdsContainingSecondOrThirdTag =
Arrays.asList(secondTag.getId(), thirdTag.getId());
List<ArticleDTO> articlesBySecondOrThirdTag =
service.getByTags(tagIdsContainingSecondOrThirdTag);
assertEquals(2, articlesByFirstTag.size());
assertEquals(articleContent + "_firstAndSecondTag", articlesByFirstTag.get(0).getContent());
assertEquals(articleContent + "_firstAndThirdTag", articlesByFirstTag.get(1).getContent());
assertEquals(3, articlesBySecondOrThirdTag.size()); //it fails
}
But instead of 3, I got 4 Article
s which is pretty weird cause I have only 3 in database. That is how I try to filter Article
s:
@Override
public List<Article> getByTags(Collection<Long> tagIds) {
return (List<Article>)createCriteria()
.createAlias("tags", "t")
.add(Restrictions.in("t.id", tagIds))
.list();
}
Second tag is counted twice (I think because it has second
and third
tags). It seems like it "loops" in every Tag
, then check if it is in (second
, third
) and then if it's true add Article
related with Tag
. I also tried to add Restrinction.in
for every tag ID separately:
criteria.createAlias("tags", "t");
for (Long tagId: tagIds) {
criteria.add(Restrictions.in("t.id", Collections.singleton(tagIds)));
}
But then result was 0, no expected 3 - IMO also because it doesn't look on all Tag
s, bot on every Tag
separately. Is there any idea how to "look on all tags at once" (if you know what I mean)? When I want to achieve something like that in normal language it would be a little bit like (Java-like pseudocode):
for (Article article: articles){
List<Tag> tags = article.getTags();
// As many conditions as tags to filter specified
if ( tags.contains(second) || tags.contains(third) ... ) {
filteredArticles.add(article);
}
}
Can anybody help me with this problem? Thank you in advance for every answer.
Upvotes: 0
Views: 425
Reputation: 1910
I found a solution on my own:
@Override
public List<Article> getByTags(Collection<Long> tagIds) {
return (List<Article>)createCriteria()
.createAlias("tags", "t")
.add(Restrictions.in("t.id", tagIds))
.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
.list();
}
.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
was missing.
Upvotes: 1