PageHelper的高级使用,一个注解即可实现分页

1,471 阅读6分钟

一个注解就能搞定分页,为何你的却那么复杂

一个java程序员都避免不了增删改查,最近这几天又开始去写增删改查的接口了。这个时候就避免不了做数据的分页。

所以这几天写下来发现,即使使用了 pagehelper 分页插件,去对数据物理分页。虽然 pagehelper 插件使用起来很简单了。

但是我是个非常懒的程序员,多一行非业务相关的代码,我都不想写。 一个函数下来,被来业务程序就四五行,一顿操作下来非业务代码都占用了3份1。我是非常不能接受这些无相关业务太多的代码嵌入到函数中的。

其中一点就是:代码阅读起来不清晰,代码结构冗余,我特别喜欢看即简洁,条理又清晰的的。

所以自己就封装了一个 基于 pagehelper 上分页工具使用,即简洁,使用又方便。

1、思路

使用Spring拦截器和aspectj的作用,MethodInterceptor.java 去做Mapper接口的拦截,然后对要分页的方法加上 自定义分页注解 @StartPage ,在拦截到该方法的时候就,方法执行前,利用 **pagehelper.startPage()**进行分页,然后继续执行该方法,将查询结果封装到Page里面。即可实现分页拦截,在不修改 pagehelper 的操作上。这样子就避免了,在也service层的业务中去写这个一个多余的代码。

还避免了,在业务的service函数中,出现误操作,提前执行了 PageHelper.start()方法。导致真正的分页方法不生效了。

比如:

PageHelper.start(1,10);
........
cityMapper.selectById(id); 
........
userMaper.selectList();
........

如上这种情况 userMaper.selectList(); 是需要分页的数据,但是在此之前执行了 cityMapper.selectById(id); 所以这个时候 cityMapper.selectById(id); 生效了。userMaper.selectList();分页 不生效。

使用这种注解方式,就不需要管这种情况,因为分页处理直接是在 当前mapper函数上处理了。

1、新建一个SpringBoot项目,将其做成一个starter。

2、相关文件介绍

封装一个 分页对象:

  • 1、Page.java
public class Page<T> implements IPage<T>
{
    private int pageNum = 1;
    private int pageSize = 15;
    private int pages;
    private long total;
    private int[] navigatepageNums = new int[]{0};
    private List<T> rows = Collections.emptyList();
    private boolean START_PAGE_ED = false;
    private OrderBy[] orderBys = new OrderBy[0];
  
    // 省略 set get
}
  • OrderBy: 排序查询对象
/**
 * <br>
 *
 * @author 永健
 * @since 2020-06-16 15:02
 */
public class OrderBy
{
    private String[] columns;
    private Direction direction;

    public OrderBy(OrderBy.Direction direction, String... properties)    {
        this.columns = properties;
        this.direction = direction;
    }

    // set get 

    public enum Direction    {
        ASC,
        DESC;

        Direction()
        {
        }

        public boolean isAscending()
        {
            return this.equals(ASC);
        }

        public boolean isDescending()
        {
            return this.equals(DESC);
        }
    }
}


  • PageHelperAutoConfiguration.java SpringBoot自动配置类,starter的关键操纵
@Configuration
// 加载SqlSessionFactory
@ConditionalOnBean({SqlSessionFactory.class})
//允许自动读取配置文件
@EnableConfigurationProperties({PageHelperProperties.class})
// 在加载配置的类
@AutoConfigureAfter({MybatisAutoConfiguration.class})
// 加载自定义Config配置类
@Import(PageHelperConfig.class) 
public class PageHelperAutoConfiguration
{
  
    @Autowired
    private List<SqlSessionFactory> sqlSessionFactoryList;

    /**
     * SpringBoot的参数自动配置类
     */
    @Autowired
    private PageHelperProperties properties;

    public PageHelperAutoConfiguration()
    {
    }

    /**
     * 注册Pagehelper拦截器
     */ 
    @PostConstruct
    public void addPageInterceptor()
    {
        PageInterceptor interceptor = new PageInterceptor();
        Properties properties = new Properties();
        properties.putAll(this.properties.getProperties());
        interceptor.setProperties(properties);
        Iterator var3 = this.sqlSessionFactoryList.iterator();

        while (var3.hasNext())
        {
            SqlSessionFactory sqlSessionFactory = (SqlSessionFactory) var3.next();
            sqlSessionFactory.getConfiguration().addInterceptor(interceptor);
        }
    }
}
  • PageHelperProperties.java 就是读取 PageHelper 插件的配置类的。属性如下:

  • PageHelperConfig.java 自定义配置类,主要是配置 Spring的增强类 DefaultPointcutAdvisor

/**
 * <br>
 *
 * @author 永健
 * @since 2020-11-11 11:19
 */
@Configuration
public class PageHelperConfig
{
    /**
     * 表达式
     * 拦截哪个包下的所有方法
     */
    private static final String EXECUTION = "execution(* %s..*.*(..))";

    @Bean
    public DefaultPointcutAdvisor defaultPointcutAdvisor2(PageHelperProperties pageHelperProperties) {

        // 拦截器
        PageMethodInterceptor interceptor = new PageMethodInterceptor();

        // 切点
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        String mapperPackage = pageHelperProperties.getMapperPackage();
        pointcut.setExpression(String.format(EXECUTION,mapperPackage));

        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut,interceptor);
        return advisor;
    }
}
  • PageMethodInterceptor.java 重点:拦截器
 /**
     * 核心方法
     */
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable
    {
        Method method = methodInvocation.getMethod();
        StartPage annotation = method.getAnnotation(StartPage.class);
        if (annotation != null)
        {
            LOGGER.info("start page mapper");
        }
        Page<?> page = getPage(methodInvocation);

        if (page != null)
        {
        if (!page.isStartPageEd())
        {

                PageHelper.startPage(page.getPageNum(), page.getPageSize(),PageUtils.filterSql(page.getOrderBy()));
                Object proceed = methodInvocation.proceed();
                page.setRows(proceed);
                return proceed;
            }
        }
        return methodInvocation.proceed();
    }
  • @StartPage.java 分页注解
/**
 * <p>
 *
 * </p>
 *
 * @author 永健
 * @since 2020-11-11 11:52
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface StartPage
{
}

......

项目搭建完毕! 将项目达成一个 jar文件引入到另外一个SpringBoot项目中。

3、使用

在另外一个项目中使用。

        <dependency>
            <groupId>cn.yj.pagehelper</groupId>
            <artifactId>annotation-pagehelper</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

1、pagehelper配置文件

# 如下的配置文件都除了多增加一个 mapper-package 属性外,其他的一切 pagehelper 的配置都保持原有的功能。
pagehelper:
  helper-dialect: mysql
  mapper-package: com.ex.das.mapper # mapper接口下的包
  reasonable: false # true:当默认值为pageNum<=0时,自动配置为1,pageSize>pages时候,默认为最后一页
  supportMethodsArguments: false # 不支持参数接口分页

2、在CityMapper.java中的方法加上 注解@StartPage

/**
 * <br>
 *
 * @author 永健
 * @since 2020-11-11 15:02
 */
public interface CityMapper
{
    /**
     *
     * @param page 分页参数
     * @return
     */
    @StartPage
    @Select("select * from city")
    List<City> selectPage(IPage<City> page);

    /**
     *
     * @param page 分页参数
     * @param city 查询对象
     * @return
     */
    @StartPage
    List<City> selectList(IPage<City> page,@Param("params") City city);
}

3、Service、Controller中使用

public interface ICityService
{
    // 可有返回值
    Page<City> getCityPage(Page<City> page);
		
    // 可没有返回值 
    void getCitysPage(Page<City> page);
    
    // 带查询参数
    Page<City> getCitysPage3(Page<City> page,City city);

    Page<City> getCitysPage4(Page<City> page,City city);
}

CityServiceImpl.java

@Service
public class CityServiceImpl implements ICityService
{

    private final CityMapper cityMapper;

    @Autowired
    public CityServiceImpl(CityMapper cityMapper)
    {
        this.cityMapper = cityMapper;
    }

    /**
     * 注解分页
     * @param page 分页参数
     * @return
     */
    @Override
    public Page<City> getCityPage(Page<City> page)
    {
        return page.setRows(cityMapper.selectPage(page));
    }

    /**
     * 使用注解分页可不带返回值,已经将结果封装到page 中
     * @param page
     */
    @Override
    public void getCitysPage(Page<City> page)
    {
        cityMapper.selectPage(page);
    }

    /**
     *  不使用中注解分页
     * @param page 分页参数
     * @param city 查询参数
     * @return
     */
    @Override
    public Page<City> getCitysPage3(Page<City> page, City city)
    {
        return page.startPage().setRows(cityMapper.selectList(page, city));
    }

    /**
     * 使用注解分页
     * @param page 分页参数
     * @param city 查询参数
     * @return
     */
    @Override
    public Page<City> getCitysPage4(Page<City> page, City city)
    {
        cityMapper.selectList(page, city);
        return page;
    }
}

SpringController.java

/**
 * <br>
 *
 * @author 永健
 * @since 2020-04-22 09:30
 */
@RestController
public class SpringController extends BaseController<City>
{

    @Autowired
    ICityService cityService;


    @GetMapping("/list")
    public R page1()
    {
        return success(cityService.getCityPage(page()));
    }

    @GetMapping("/list2")
    public R page2()
    {
        Page<City> page = page(new OrderBy(OrderBy.Direction.ASC, "id"));
        cityService.getCitysPage(page);
        return success(page);
    }

    @GetMapping("/list3")
    public R page3(City city)
    {
        return success(cityService.getCitysPage3(page(new OrderBy(OrderBy.Direction.ASC, "id")), city));
    }

    @GetMapping("/list4")
    public R page4(City city)
    {
        return success(cityService.getCitysPage4(page(new OrderBy(OrderBy.Direction.ASC, "id")), city));
    }
}

controller 中的使用,为了更加将分页参数与查询参数分离开。抽取一个共用的BaseController 也可以将封装好统一的返回对象给客户端,这样子即规范统一又好管理,简洁又方便。还可以放一些当前用户的信息啥的....

所以 BaseController.java 负责获取分页参数和封装好分页对象。包括统一响应客户端的对象,不用特意去 new一个对象了直接重父类拿

public class BaseController<T>
{
    // 。。。。。。
    protected Page<T> page()
    {
        int pageNumber = getParamsInt("pageNum");
        int pageSize = getParamsInt("pageSize");
        return new Page<>(pageNumber, pageSize);
    }

    /**
     *
     * @param orderBy 排序对象
     * @return
     */
    protected Page<T> page(OrderBy... orderBy)
    {
        int pageNumber = getParamsInt("pageNum");
        int pageSize = getParamsInt("pageSize");
        return new Page<>(pageNumber==0?1:pageNumber, pageSize==0?15:pageSize, orderBy);
    }

    private HttpServletRequest getRequest()
    {
        RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
        return ((ServletRequestAttributes) attributes).getRequest();
    }

    private int getParamsInt(String name)
    {
        String parameter = getRequest().getParameter(name);
        if (parameter == null || "".equals(parameter.trim()))
        {
            return 0;
        }
        return Integer.valueOf(parameter);
    }
}

4、验证分页结果

项目启动访问获取数据。

  • list 接口

拦截器已经生效。

  • list3带参数的分页注解分页

使用结束了,一个注解就能简单的搞定分页操作,mysql的分页操作。你还在写多那几行非业务相关的代码吗????

当然,如果不使用该注解分页方式也可使用 PageHelper 的方式进行分页。两不冲突。引入到你的项目中根据自己需要定制自己的一套规则。没有固定死的,合适你的项目的就是好的。

因为我懒,所以我就爱动脑爱动手,减轻开发中的痛苦,去写一个更加适合我个人开发的组建,抽离开来,方便以后个人使用便捷,不需要重复去写。直接引用这一套代码即可,更能提升个人开发的效率。

如上代 码可完善地方很多,感兴趣的,可以将其继续扩展使用学习使用...

如果公司让你封装一个分页插件的使用,你会怎么去封装一个???

更多PageHelper的使用方式,请移步官网

github.com/pagehelper/…