使用@ManyToMany对Spring Data JPA进行分页

时间:2016-07-27 14:26:07

标签: java spring-data spring-data-jpa

我有PostTag型号,这些型号有@manytomany关系。

发布

@Entity
public class Post {
     private long id;

     @ManyToMany(fetch = FetchType.EAGER, cascade = { CascadeType.PERSIST, CascadeType.MERGE })
     @JoinTable(joinColumns = @JoinColumn(name = "post_id"), inverseJoinColumns = @JoinColumn(name = "tag_id"))
     private Set<Tag> tags;

     ...
}

标签

@Entity
public class Tag {
     private String name;

     @ManyToMany(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.MERGE }, mappedBy = "tags")
     private List<Post> posts = new ArrayList<Post>();

我想创建一个按标记名称查找所有分页帖子的方法。

我发现JPQL不支持LIMIT

我是否必须使用setFirstResult().setMaxResults().getResultList()实现自己的分页逻辑?

@manytomany分页的最佳做法是什么?

我编辑了一下我的问题。我编写了如下所示的代码:

@SuppressWarnings("unchecked")
public Page<Post> findByTagName(String tagName, Pageable pageable) {

    long total = (long) em
            .createQuery("SELECT COUNT(p.id) FROM Post p JOIN p.tags t WHERE t.name = :tagName")
            .setParameter("tagName", tagName)
            .getSingleResult();

    List<Post> content = (List<Post>) em
            .createQuery("SELECT p FROM Post p JOIN FETCH p.tags t WHERE t.name = :tagName")
            .setParameter("tagName", tagName)
            .setFirstResult(pageable.getOffset())
            .setMaxResults(pageable.getPageSize())
            .getResultList();

    PageImpl<Post> page = new PageImpl<Post>(content, pageable, total);

    return page;
}

此代码工作正常,但我仍然想知道这是否是正确的方法。

谢谢。

2 个答案:

答案 0 :(得分:2)

使用页面和@ManyToMany映射是一项非常简单的任务。

这里首先是类似于你的模型(基本上只添加了@Id@GeneratedValue注释来获取生成的数据库标识符。)

发布实体:

package com.example.model;

import java.util.HashSet;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;

@Entity
public class Post {

    @Id
    @GeneratedValue
    private long id;

    @ManyToMany(fetch = FetchType.EAGER, cascade = { CascadeType.PERSIST, CascadeType.MERGE })
    @JoinTable(joinColumns = @JoinColumn(name = "post_id"), inverseJoinColumns = @JoinColumn(name = "tag_id"))
    private Set<Tag> tags = new HashSet<>();

    public Set<Tag> getTags() {
        return tags;
    }

}

标记实体:

package com.example.model;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToMany;

@Entity
public class Tag {

    @Id
    @GeneratedValue
    private long id;

    private String name;

    @ManyToMany(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.MERGE }, mappedBy = "tags")
    private List<Post> posts = new ArrayList<Post>();

    public void setName(String name) {
        this.name = name;
    }

}

现在您需要PagingAndSortingRepository来获取帖子实体:

package com.example.repository;

import java.util.Set;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import com.example.model.Post;

@Repository
public interface PostRepository extends PagingAndSortingRepository<Post, Long> {

    @Transactional(readOnly = true)
    Set<Post> findByTagsName(String name);

    @Transactional(readOnly = true)
    Page<Post> findByTagsName(String name, Pageable pageable);

}

使用pagables几乎和编写常规的Spring Data JPA finder方法一样简单。如果要按指定的标记实体的名称查找帖子,只需通过链接字段名称(如findBy Tags + Name)来编写常规查找程序。这会创建一个类似于JPQL方法SELECT p FROM Post p JOIN FETCH p.tags t WHERE t.name = :tagName的查询。将标记名称的参数作为方法参数传递。

现在 - 如果您想添加Pageable支持 - 只需添加Pageable类型的参数作为第二个参数,并将返回值转换为Page而不是Set。这就是全部。

至少这里有一些验证代码的测试:

package com.example.repository;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.hasSize;
import static org.junit.Assert.assertThat;

import java.util.Set;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;

import com.example.model.Post;
import com.example.model.Tag;

@RunWith(SpringRunner.class)
@Transactional
@SpringBootTest
public class PostRepositoryTests {

    @Autowired
    private PostRepository postRepository;

    @PersistenceContext
    private EntityManager entityManager;

    @Test
    public void receiveMultiplePostsWithTagsByName() {
        final String nameA = "A";
        final String nameB = "B";
        final String nameC = "C";
        final String nameD = "D";
        final String nameE = "E";

        final Tag tagA = new Tag();
        tagA.setName(nameA);
        final Tag tagB = new Tag();
        tagB.setName(nameB);
        final Tag tagC = new Tag();
        tagC.setName(nameC);
        final Tag tagD = new Tag();
        tagD.setName(nameD);
        final Tag tagE = new Tag();
        tagE.setName(nameE);

        final Post postOne = new Post();
        postOne.getTags().add(tagA);
        postOne.getTags().add(tagB);
        postRepository.save(postOne);

        final Post postTwo = new Post();
        postTwo.getTags().add(tagA);
        postTwo.getTags().add(tagB);
        postTwo.getTags().add(tagE);
        postRepository.save(postTwo);

        final Post postThree = new Post();
        postThree.getTags().add(tagA);
        postThree.getTags().add(tagB);
        postThree.getTags().add(tagC);
        postThree.getTags().add(tagE);
        postRepository.save(postThree);

        entityManager.flush();
        entityManager.clear();

        final Set<Post> tagsByA = postRepository.findByTagsName(nameA);
        assertThat("Expected three hits!", tagsByA, hasSize(3));

        final Set<Post> tagsByB = postRepository.findByTagsName(nameB);
        assertThat("Expected three hits!", tagsByB, hasSize(3));

        final Set<Post> tagsByC = postRepository.findByTagsName(nameC);
        assertThat("Expected one hit!", tagsByC, hasSize(1));

        final Set<Post> tagsByD = postRepository.findByTagsName(nameD);
        assertThat("Expected no hits!", tagsByD, empty());

        final Set<Post> tagsByE = postRepository.findByTagsName(nameE);
        assertThat("Expected two hits!", tagsByE, hasSize(2));
    }

    @Test
    public void receiveMultiplePostsWithTagsByNamePaged() {
        final String nameA = "A";

        final Tag tagA = new Tag();
        tagA.setName(nameA);

        final Post postOne = new Post();
        postOne.getTags().add(tagA);
        postRepository.save(postOne);

        final Post postTwo = new Post();
        postTwo.getTags().add(tagA);
        postRepository.save(postTwo);

        final Post postThree = new Post();
        postThree.getTags().add(tagA);
        postRepository.save(postThree);

        final Post postFour = new Post();
        postFour.getTags().add(tagA);
        postRepository.save(postFour);

        final Post postFive = new Post();
        postFive.getTags().add(tagA);
        postRepository.save(postFive);

        entityManager.flush();
        entityManager.clear();

        final Page<Post> tagsByAFirstPageSize2 = postRepository.findByTagsName(nameA, new PageRequest(0, 2));
        assertThat("Expected two page items!", tagsByAFirstPageSize2.getContent(), hasSize(2));
        assertThat("Expected five items in sum!", tagsByAFirstPageSize2.getTotalElements(), is(5L));
        assertThat("Should be first page!", tagsByAFirstPageSize2.isFirst(), is(true));
        assertThat("Should not be last page!", tagsByAFirstPageSize2.isLast(), is(false));

        final Page<Post> tagsBySecondPageSize2 = postRepository.findByTagsName(nameA, new PageRequest(1, 2));
        assertThat("Expected two page items!", tagsBySecondPageSize2.getContent(), hasSize(2));
        assertThat("Expected five items in sum!", tagsBySecondPageSize2.getTotalElements(), is(5L));
        assertThat("Should not be first page!", tagsBySecondPageSize2.isFirst(), is(false));
        assertThat("Should not be last page!", tagsBySecondPageSize2.isLast(), is(false));

        final Page<Post> tagsByLastPageSize2 = postRepository.findByTagsName(nameA, new PageRequest(2, 2));
        assertThat("Expected one last page item!", tagsByLastPageSize2.getContent(), hasSize(1));
        assertThat("Expected five items in sum!", tagsByLastPageSize2.getTotalElements(), is(5L));
        assertThat("Should not be first page!", tagsByLastPageSize2.isFirst(), is(false));
        assertThat("Should be last page!", tagsByLastPageSize2.isLast(), is(true));
    }

}

答案 1 :(得分:0)

不确定它是否适用于您的情况,但请查看此链接

setMaxResults for Spring-Data-JPA annotation?

此外,

你可以在spring jpa中使用findFirst或findTop方法进行限制。

http://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.limit-query-result