SpringdDataJpa如何创建一个不分页,但是排序的pageable

public Pageable createPagenation() {
        int page = WebClass.requestToInt("pageNo");
        int pageSize = WebClass.requestToInt("pageSize");
        
        Sort s = new Sort(Sort.Direction.DESC, "id");
        if (page > 0 && pageSize > 0) {
            return PageRequest.of(page - 1, pageSize, s);
        } else {
            return Pageable.unpaged();
        }
    }

想封装一个自动构建分页对象的方法,
但是,在不需要分页的时候,
Pageable.unpaged()返回不需要分页的对象,但是这个对象,无法设置排序属性,
只有getSort(),没有setSort()。

请问有大佬知道应该怎么处理吗?

阅读 2.6k
评论
    2 个回答
    • 1.9k

    2020-07-16 更新一波
    今天看到有个小哥在这个里面评论说他也报错了,报了UnsupportedOperationException
    image.png

    这跟之前题主说的报错也是差不多,就是我的方案总之会报错
    image.png

    但是当时我也给题主回复了,我说能不能把报错提出来让我看看,既然大家讨论,也不能就说报错就完事了吧,好歹我也得看到堆栈啥的才好说噻,而且当时我记得很清楚我也去试了我的方案是没有报错了,所以我当时就回复希望题主贴出点东西供我参考一下
    image.png

    当然后面题主也没有提,虽然我当时还等着题主给后续的堆栈或者其他信息呢,一直没有,这样就不了了之了,直到今天再有人说有报错,但是还是没有给其他信息,我是气不打一处来,我不质疑别人给我的答案说有问题,但是好歹得有点东西提供给我吧。。。这让我都怀疑我当时到底测试过没有了

    于是我今天再开了一个demo,试了一下我的,确实没有报错
    image.png

    可以正常返回
    image.png

    我再仔细看了一下当时题主的描述说的词语
    image.png

    看就是说某个判断之后,去调用了getPageSize方法导致报错,debug了一下,感觉应该是这些个地方
    image.png

    也就是PageableisPaged()方法,由于没有其他的堆栈和任何信息,我只能这么猜。

    但是其实我给的方案中,由于我们自己实现了Pageable,所以明显这里是false的(别说是我这次改的哈,大家可以看我的答案编辑记录,之前提交的一次之后再也没有改过,除了现在正在编辑的,第一版我就是返回的false
    image.png

    所以我只能猜测题主或者今天评论我这个会抛错的,估计是因为isPaged()返回的是true

    确实,我试了一下,如果这里改成了返回true,一定会报错
    image.png

    虽然这样之后可以证明我之前的方案起来来说不会报错,但是这个时候我在刚才读源码的过程中却发现我这个方案是有个 致命 问题的,最开始我尽然没发现

    其实也是可以发现,如果题主完全按照我的代码copy过去一定会发现的,因为copy过去的代码确实不会报错,但是,这个方案却不会排序了,原因在这
    image.png

    进入findAll()方法后,调用getQuery的参数中,直接采用的Sort.unsorted()了,也就是没有顺序了
    image.png

    那这样就失去意义了嘛,这样反而我自己证明我方案有问题的,于是我再走读了一下代码,从刚才最后getQuery入手,我们可以看到,其实getQuery方法是可以支持传不是unsorted()Sort的,所以我之前的思路上,其实应该是可行,只是不能直接简单用了一个实现了Pageable的枚举来做,还不够,因为原因出在findAll()的实现类上,也就是默认实现SimpleJpaRepository上,这是我们平常写的repository的默认实现,我们只写接口不写实现,就是因为这个类

    所以思路就简单了,我们要提供定制化的SimpleJpaRepository

    所以我找了一下怎么定制化这个,也很简单(当然比之前只是写枚举要复杂点)

    首先我们以示区分之前的findAll(Pageable pageable)方法,也就是要区分之前默认的接口PagingAndSortingRepository

    我们自定义一个接口CustomRepository,里面一个方法findAllCustom。参数也只有一个Pageable,但是作用应该是如果是不分页,但是有传入sort,则要按照sort进行排序,如果要分页,则按照传统分页操作来
    (当然为了以后扩展,我这里再加了一个增加查询条件Specification的分页查询方法,其实第一个方法就是Specificationnull的调用)

    @NoRepositoryBean
    public interface CustomRepository<T> {
    
        Page<T> findAllCustom(Pageable pageable);
    
        Page<T> findAllCustom(@Nullable Specification<T> spec, Pageable pageable);
    }
    

    其次我们再写SimpleJpaRepository的定制实现,依此我们来实现刚才说的findAllCustom逻辑,实现细节就不讲了,反正就是按照需求来就可以了

    public class CustomSimpleJpaRepository<T, ID> extends SimpleJpaRepository<T, ID>
            implements CustomRepository<T> {
    
        private final EntityManager em;
    
        public CustomSimpleJpaRepository(Class<T> domainClass, EntityManager em) {
            super(domainClass, em);
            this.em = em;
        }
    
        @Override
        public Page<T> findAllCustom(Pageable pageable) {
            if (isUnpaged(pageable) && isNotContainsSort(pageable)) {
                return new PageImpl<T>(findAll());
            }
            return findAllCustom((Specification<T>) null, pageable);
        }
    
        @Override
        public Page<T> findAllCustom(Specification<T> spec, Pageable pageable) {
            TypedQuery<T> query = getQueryCustom(spec, pageable);
            return isUnpaged(pageable) ? new PageImpl<T>(query.getResultList())
                    : readPage(query, getDomainClass(), pageable, spec);
        }
    
        protected TypedQuery<T> getQueryCustom(@Nullable Specification<T> spec, Pageable pageable) {
    
            Sort sort = (pageable.isPaged() || isContainsSort(pageable))
                    ? pageable.getSort()
                    : Sort.unsorted();
            return getQuery(spec, getDomainClass(), sort);
        }
    
        private static boolean isNotContainsSort(Pageable pageable) {
            return !isContainsSort(pageable);
        }
    
        private static boolean isContainsSort(Pageable pageable) {
            return !pageable.getSort().isEmpty();
        }
    
        private static boolean isUnpaged(Pageable pageable) {
            return pageable.isUnpaged();
        }
    }
    

    接下来我们要模拟默认的SimpleJpaRepository是如何注入到Spring里的方式把CustomSimpleJpaRepository注入进去

    默认的是通过FactoryBean的方式注入的,也就是JpaRepositoryFactoryBean,而JpaRepositoryFactoryBean其实又通过一个工厂JpaRepositoryFactory来创建了SimpleJpaRepository

    因此我们也需要模拟一个JpaRepositoryFactoryBeanJpaRepositoryFactory,分别叫CustomRepositoryFactoryBeanCustomRepositoryFactory

    public class CustomRepositoryFactoryBean<T extends Repository<S, ID>, S, ID>
            extends JpaRepositoryFactoryBean<T, S, ID> {
    
        public CustomRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
            super(repositoryInterface);
        }
    
        protected RepositoryFactorySupport createRepositoryFactory(EntityManager em) {
            return new CustomRepositoryFactory(em);
        }
    }
    public class CustomRepositoryFactory extends JpaRepositoryFactory {
        
        private final EntityManager em;
    
        public CustomRepositoryFactory(EntityManager em) {
            super(em);
            this.em = em;
        }
    
        @Override
        protected JpaRepositoryImplementation<?, ?> getTargetRepository(
                RepositoryInformation information, 
                EntityManager entityManager) {
            return new CustomSimpleJpaRepository(information.getDomainType(), entityManager);
        }
    
        protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
            return CustomSimpleJpaRepository.class;
        }
    }

    由于CustomRepositoryFactoryBeanCustomRepositoryFactory都是分别继承了JpaRepositoryFactoryBeanJpaRepositoryFactory,所以里面的代码不是很多,稍微改改就可以了,这里也不多说了

    最后我们的UserRepository当然要继承我们新的CustomRepository就可以了

    public interface UserRepository extends JpaRepository<User, Long>, 
                                            CustomRepository<User> {
    }

    当然没忘了加上@EnableJpaRepositories来指明我们当前的repositoryFactoryBeanClass是谁

    @SpringBootApplication
    @EnableJpaRepositories(repositoryFactoryBeanClass = CustomRepositoryFactoryBean.class)
    public class Demo2Application {
    
        public static void main(String[] args) {
            SpringApplication.run(Demo2Application.class, args);
        }
    
    }

    还有我们的调用的地方就不能再使用findAll(Pageable),而是我们的
    findAllCustom(Pageable)

    image.png

    最后执行也可以看到,是按照名字升序排列的了

    image.png

    以上就是答案吧,希望题主和刚评论我的小哥能理解我的心情,毕竟程序猿也不希望大家说他的代码有bug吧,但其实我一直是不怕bug我怕需求不明确,问题不明确,这让我想看有时候都看不了。。。╮(╯▽╰)╭



    虽然自己平常用的时候没有用到Pageable.unpaged()返回的这个枚举Unpaged,但是点进去看,无非是用枚举单例模式创建了一个Pageable的实例嘛

    clipboard.png

    所以针对你提到的问题,从中我想回答两个疑问,当然也许不是疑问,算是理解方式

    1. 你提到 只有getSort(),没有setSort()
      关于这一点,你的理解可能有点点偏差,当然编程思想很多,只要能解决问题都是好思想,不一概而论,不过这里分享哈我的理解,主要是Pageable.unpaged()本身就是Pageable接口的实例,没有setSort()方法,其实就是Pageable接口没有setSort()方法,Pageable接口本身代表着分页信息的抽象,这个抽象定义了分页信息应该有的抽象行为,这种抽象接口,在我自己的开发经验中定义为属于实体信息的抽象,不是流程类的抽象接口,实体信息的抽象我一般会更多的注意抽象实体信息本身所具有的性质或者叫属性,那是去定义从一个实体中你能有拿到什么信息,而不是注入或者修改什么信息,所以在我看来一个分页信息对象没有setSort()很正常,就像假如你把人类做一个抽象接口叫PersonPerson里有个属性名字(name),你觉得你的接口Person应不应该有个setName的方法么?
    2. 如何处理这种问题(你想要不分页,但是需要排序)
      处理方式其实也很简单,你都看到了别人官方怎么做了。。。你不就是学着官方改改不就完了么。。。也就是你也学着Pageable.unpaged()写一个新的枚举,其他的都仿照Pageable.unpaged(),只有getSort方法你进行你的定制修改,写一个新的枚举叫UnpagedSortById,举例如下:
    public enum UnpagedSortById implements Pageable {
    
        INSTANCE;
    
        /*
         * (non-Javadoc)
         * @see org.springframework.data.domain.Pageable#isPaged()
         */
        @Override
        public boolean isPaged() {
            return false;
        }
    
        /*
         * (non-Javadoc)
         * @see org.springframework.data.domain.Pageable#previousOrFirst()
         */
        @Override
        public Pageable previousOrFirst() {
            return this;
        }
    
        /*
         * (non-Javadoc)
         * @see org.springframework.data.domain.Pageable#next()
         */
        @Override
        public Pageable next() {
            return this;
        }
    
        /*
         * (non-Javadoc)
         * @see org.springframework.data.domain.Pageable#hasPrevious()
         */
        @Override
        public boolean hasPrevious() {
            return false;
        }
    
        /*
         * (non-Javadoc)
         * @see org.springframework.data.domain.Pageable#getSort()
         */
        @Override
        public Sort getSort() {
            return new Sort(Sort.Direction.DESC, "id");
        }
    
        /*
         * (non-Javadoc)
         * @see org.springframework.data.domain.Pageable#getPageSize()
         */
        @Override
        public int getPageSize() {
            throw new UnsupportedOperationException();
        }
    
        /*
         * (non-Javadoc)
         * @see org.springframework.data.domain.Pageable#getPageNumber()
         */
        @Override
        public int getPageNumber() {
            throw new UnsupportedOperationException();
        }
    
        /*
         * (non-Javadoc)
         * @see org.springframework.data.domain.Pageable#getOffset()
         */
        @Override
        public long getOffset() {
            throw new UnsupportedOperationException();
        }
    
        /*
         * (non-Javadoc)
         * @see org.springframework.data.domain.Pageable#first()
         */
        @Override
        public Pageable first() {
            return this;
        }
    }

    很狠哈,注释都不带改的,直接粘贴大法搞定,其实其中getSort方法改改

    clipboard.png

    当然这样是可以解决问题的,不过从我个人编码习惯来说,我觉得UnpagedSortById这个枚举不ok,因为从枚举来看只能按照id排序,如果以后还要其他字段排序,我还得写个UnpagedSortByXXX这样的枚举,里面的重复代码就多了,索性直接可以干脆大家共享一个枚举,只是区分不同的Sort即可,比如这样:

    @Getter
    @AllArgsConstructor
    public enum UnpagedSort implements Pageable {
    
        BY_ID_DESC(new Sort(Sort.Direction.DESC, "id")),
        BY_NAME_ASC(new Sort(Sort.Direction.ASC, "name")),
    
        ;
    
        private Sort sort;
    
        /*
         * (non-Javadoc)
         * @see org.springframework.data.domain.Pageable#isPaged()
         */
        @Override
        public boolean isPaged() {
            return false;
        }
    
        /*
         * (non-Javadoc)
         * @see org.springframework.data.domain.Pageable#previousOrFirst()
         */
        @Override
        public Pageable previousOrFirst() {
            return this;
        }
    
        /*
         * (non-Javadoc)
         * @see org.springframework.data.domain.Pageable#next()
         */
        @Override
        public Pageable next() {
            return this;
        }
    
        /*
         * (non-Javadoc)
         * @see org.springframework.data.domain.Pageable#hasPrevious()
         */
        @Override
        public boolean hasPrevious() {
            return false;
        }
    
        /*
         * (non-Javadoc)
         * @see org.springframework.data.domain.Pageable#getPageSize()
         */
        @Override
        public int getPageSize() {
            throw new UnsupportedOperationException();
        }
    
        /*
         * (non-Javadoc)
         * @see org.springframework.data.domain.Pageable#getPageNumber()
         */
        @Override
        public int getPageNumber() {
            throw new UnsupportedOperationException();
        }
    
        /*
         * (non-Javadoc)
         * @see org.springframework.data.domain.Pageable#getOffset()
         */
        @Override
        public long getOffset() {
            throw new UnsupportedOperationException();
        }
    
        /*
         * (non-Javadoc)
         * @see org.springframework.data.domain.Pageable#first()
         */
        @Override
        public Pageable first() {
            return this;
        }
    }

    以上是个人看法,仅供参考哈

      • 11.4k

      觉得需求有问题,不想分页,把pageSize搞大不就可以了么?比如说10000, 至少还有个保险,真的数据几十万也至于死掉。

        撰写回答

        登录后参与交流、获取后续更新提醒

        相似问题
        推荐文章