├── src
├── test
│ ├── resources
│ │ ├── mybatis-config.xml
│ │ ├── application.yaml
│ │ ├── mapper
│ │ │ └── simplePagingXMLMapper.xml
│ │ └── test2.sql
│ └── java
│ │ └── com
│ │ └── xiwh
│ │ └── paginator
│ │ ├── TestApplication.java
│ │ └── demo
│ │ ├── simplePaging
│ │ ├── SimplePagingXMLMapper.java
│ │ ├── SimplePagingXMLTests.java
│ │ ├── SimplePagingMapper.java
│ │ └── SimplePagingTests.java
│ │ ├── common
│ │ ├── ATablePO.java
│ │ ├── BTablePO.java
│ │ └── PagingController.java
│ │ ├── nplusonePaging
│ │ ├── NPlusonePagingMapper.java
│ │ └── NPlusOnePagingTests.java
│ │ ├── sql
│ │ └── SQLCountTests.java
│ │ └── cache
│ │ └── CountCacheTests.java
└── main
│ └── java
│ └── com
│ └── xiwh
│ └── paginator
│ ├── wrappers
│ ├── NormalPageWrapper.java
│ ├── NPlusOnePageWrapper.java
│ ├── PageParamsWrapper.java
│ ├── NormalPageWrapperBase.java
│ ├── NPlusOnePage.java
│ ├── SimplePage.java
│ └── PagingRowBounds.java
│ ├── cache
│ ├── CountResultCache.java
│ ├── MybatisMethodCache.java
│ └── impl
│ │ ├── CountResultCacheImpl.java
│ │ └── MybatisMethodCacheImpl.java
│ ├── annotations
│ ├── NPlusOnePaginator.java
│ └── NormalPaginator.java
│ ├── utils
│ └── StringUtils.java
│ ├── sqlGenerator
│ ├── DataBaseType.java
│ └── PaginatorSqlGenerator.java
│ ├── Paginator.java
│ └── interceptors
│ ├── PaginatorLimitHandler.java
│ ├── PaginatorExecuteHandler.java
│ ├── BaseInterceptor.java
│ └── PaginatorResultHandler.java
├── .gitignore
├── LICENSE
├── pom.xml
└── README.md
/src/test/resources/mybatis-config.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/main/java/com/xiwh/paginator/wrappers/NormalPageWrapper.java:
--------------------------------------------------------------------------------
1 | package com.xiwh.paginator.wrappers;
2 |
3 | import java.util.List;
4 |
5 | public interface NormalPageWrapper extends Iterable{
6 | void init(List list, int count, int startOffset, int physicalPage, int size);
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/com/xiwh/paginator/wrappers/NPlusOnePageWrapper.java:
--------------------------------------------------------------------------------
1 | package com.xiwh.paginator.wrappers;
2 |
3 | import java.util.List;
4 |
5 | public interface NPlusOnePageWrapper extends Iterable{
6 | void init(List list, boolean hasNext, int pageOffset, int physicalPage, int size);
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/com/xiwh/paginator/cache/CountResultCache.java:
--------------------------------------------------------------------------------
1 | package com.xiwh.paginator.cache;
2 |
3 | import org.apache.ibatis.cache.CacheKey;
4 | import java.util.concurrent.Callable;
5 |
6 | public interface CountResultCache {
7 |
8 | public int size();
9 |
10 | public void startClear();
11 |
12 | public int getCachedCount(CacheKey cacheKey);
13 |
14 | public boolean removeCache(CacheKey cacheKey);
15 |
16 | public int getCacheCount(CacheKey key, Callable countCallable, int expireTime, boolean forceUpdate) throws Exception;
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | HELP.md
2 | target/
3 | !.mvn/wrapper/maven-wrapper.jar
4 | !**/src/main/**/target/
5 | !**/src/test/**/target/
6 | mvnw
7 | mvnw.cmd
8 | springboot-mybatis-paginator.iml
9 | .mvn
10 |
11 | ### STS ###
12 | .apt_generated
13 | .classpath
14 | .factorypath
15 | .project
16 | .settings
17 | .springBeans
18 | .sts4-cache
19 |
20 | ### IntelliJ IDEA ###
21 | .idea
22 | *.iws
23 | *.iml
24 | *.ipr
25 |
26 | ### NetBeans ###
27 | /nbproject/private/
28 | /nbbuild/
29 | /dist/
30 | /nbdist/
31 | /.nb-gradle/
32 | build/
33 | !**/src/main/**/build/
34 | !**/src/test/**/build/
35 |
36 | ### VS Code ###
37 | .vscode/
38 |
--------------------------------------------------------------------------------
/src/main/java/com/xiwh/paginator/annotations/NPlusOnePaginator.java:
--------------------------------------------------------------------------------
1 | package com.xiwh.paginator.annotations;
2 |
3 | import java.lang.annotation.*;
4 |
5 | @Documented
6 | @Retention(RetentionPolicy.RUNTIME)
7 | @Target(ElementType.METHOD)
8 | public @interface NPlusOnePaginator {
9 | /**
10 | * Automatically load parameters through get request
11 | */
12 | boolean auto() default false;
13 |
14 | /**
15 | * Page offset default 0
16 | */
17 | int startOffset() default 0;
18 |
19 | /**
20 | * Custom limit statement
21 | */
22 | boolean customLimit() default false;
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/com/xiwh/paginator/wrappers/PageParamsWrapper.java:
--------------------------------------------------------------------------------
1 | package com.xiwh.paginator.wrappers;
2 |
3 | import org.apache.ibatis.session.RowBounds;
4 |
5 | public class PageParamsWrapper {
6 | private int page;
7 | private int limit;
8 | private boolean forceCounting;
9 | public PageParamsWrapper(int page, int limit, boolean forceCounting){
10 | this.page = page;
11 | this.limit = limit;
12 | this.forceCounting = forceCounting;
13 | }
14 |
15 | public PageParamsWrapper(int page, int limit){
16 | this(page, limit, false);
17 | }
18 |
19 | public boolean isForceCounting() {
20 | return forceCounting;
21 | }
22 |
23 | public int getPage() {
24 | return page;
25 | }
26 |
27 | public int getLimit() {
28 | return limit;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/com/xiwh/paginator/utils/StringUtils.java:
--------------------------------------------------------------------------------
1 | package com.xiwh.paginator.utils;
2 |
3 | import java.util.regex.Pattern;
4 |
5 | public class StringUtils {
6 |
7 | public static boolean isEmpty(String str){
8 | return str == null || str.isEmpty();
9 | }
10 |
11 | public static int safeToInt(String str,int defaultVal){
12 | if(isEmpty(str)){
13 | return defaultVal;
14 | }
15 | if(isNumeric(str)){
16 | int temp = Integer.parseInt(str);
17 | if(temp>=0){
18 | defaultVal = temp;
19 | }
20 | }
21 | return defaultVal;
22 | }
23 |
24 | public static boolean isNumeric(String string){
25 | if(isEmpty(string)){
26 | return false;
27 | }
28 | Pattern pattern = Pattern.compile("[0-9]*");
29 | return pattern.matcher(string).matches();
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/test/java/com/xiwh/paginator/TestApplication.java:
--------------------------------------------------------------------------------
1 | package com.xiwh.paginator;
2 |
3 | import org.mybatis.spring.annotation.MapperScan;
4 | import org.springframework.boot.SpringApplication;
5 | import org.springframework.boot.autoconfigure.SpringBootApplication;
6 | import org.springframework.context.annotation.ComponentScan;
7 | import org.springframework.context.annotation.Configuration;
8 | import org.springframework.http.MediaType;
9 | import org.springframework.http.converter.HttpMessageConverter;
10 | import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
11 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
12 |
13 | import java.util.ArrayList;
14 | import java.util.List;
15 |
16 | @SpringBootApplication
17 | @MapperScan("com.xiwh.paginator")
18 | public class TestApplication {
19 | public static void main(String[] args) throws Exception {
20 | SpringApplication.run(TestApplication.class, args);
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/com/xiwh/paginator/wrappers/NormalPageWrapperBase.java:
--------------------------------------------------------------------------------
1 | package com.xiwh.paginator.wrappers;
2 |
3 | import java.util.*;
4 | import java.util.function.Consumer;
5 |
6 | public abstract class NormalPageWrapperBase implements NormalPageWrapper {
7 |
8 | private transient List list;
9 |
10 | protected abstract void onInit(List list, int count, int startOffset, int physicalPage, int size);
11 |
12 | @Override
13 | public void init(List list, int count, int startOffset, int physicalPage, int size) {
14 | this.list = list;
15 | onInit(list, count, startOffset, physicalPage, size);
16 | }
17 |
18 | @Override
19 | public Iterator iterator() {
20 | return list.iterator();
21 | }
22 |
23 | @Override
24 | public void forEach(Consumer super T> action) {
25 | list.forEach(action);
26 | }
27 |
28 | @Override
29 | public Spliterator spliterator() {
30 | return list.spliterator();
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/com/xiwh/paginator/cache/MybatisMethodCache.java:
--------------------------------------------------------------------------------
1 | package com.xiwh.paginator.cache;
2 |
3 | import com.xiwh.paginator.annotations.NPlusOnePaginator;
4 | import com.xiwh.paginator.annotations.NormalPaginator;
5 | import org.apache.ibatis.mapping.MappedStatement;
6 | import java.lang.reflect.Method;
7 |
8 | public interface MybatisMethodCache {
9 |
10 | public static int TYPE_NONE = 0;
11 | public static int TYPE_NORMAL = 1;
12 | public static int TYPE_N_PLUS_ONE = 2;
13 |
14 | public MethodInfo getValidMethod(MappedStatement mappedStatement);
15 |
16 | public static interface MethodInfo{
17 |
18 | public Method getMethod();
19 |
20 | public int getPaginatorType();
21 |
22 | public NPlusOnePaginator getNPlusOnePaginator();
23 |
24 | public NormalPaginator getNormalPaginator();
25 |
26 | public Class getReturnClass();
27 |
28 | public Class getGenericReturnClass();
29 |
30 | public String getCustomCountMapperId();
31 |
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/test/java/com/xiwh/paginator/demo/simplePaging/SimplePagingXMLMapper.java:
--------------------------------------------------------------------------------
1 | package com.xiwh.paginator.demo.simplePaging;
2 |
3 | import com.xiwh.paginator.annotations.NormalPaginator;
4 | import com.xiwh.paginator.demo.common.ATablePO;
5 | import com.xiwh.paginator.demo.common.BTablePO;
6 | import com.xiwh.paginator.wrappers.PagingRowBounds;
7 | import com.xiwh.paginator.wrappers.SimplePage;
8 | import org.apache.ibatis.annotations.Mapper;
9 | import org.apache.ibatis.session.RowBounds;
10 | import org.springframework.stereotype.Component;
11 |
12 | import java.util.List;
13 |
14 | @Mapper
15 | @Component
16 | public interface SimplePagingXMLMapper {
17 | @NormalPaginator
18 | List customRowBoundsSelect(String aa, Integer bb, RowBounds rowBounds);
19 |
20 | @NormalPaginator
21 | List customPagingRowBoundsSelect(String aa, Integer bb, PagingRowBounds rowBounds);
22 |
23 | @NormalPaginator(startOffset = 1, cache = true)
24 | SimplePage select(String aa, Integer bb);
25 | }
26 |
--------------------------------------------------------------------------------
/src/test/resources/application.yaml:
--------------------------------------------------------------------------------
1 | #全局日志输出等级
2 | logging.level.com.xiwh.paginator: debug
3 | spring:
4 | #数据库配置
5 | datasource:
6 | # mysql
7 | url: jdbc:mysql://127.0.0.1:3306/test2?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT&useSSL=false
8 | username: root
9 | password: 12345678
10 | driverClassName: com.mysql.jdbc.Driver
11 | # sqlserver
12 | # url: jdbc:sqlserver://124.70.7.48:1433;DatabaseName=test2
13 | # username: rdsuser
14 | # password: xxxx
15 | # driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
16 | #posgresql
17 | # url: jdbc:postgresql://127.0.0.1:5432/postgres?useUnicode=true&characterEncoding=utf-8
18 | # username: postgres
19 | # password: 12345678
20 | # driverClassName: org.postgresql.Driver
21 | # type: com.alibaba.druid.pool.DruidDataSource
22 |
23 | mybatis:
24 | config-location: classpath:/mybatis-config.xml
25 | mapper-locations: classpath*:/mapper/*Mapper.xml
26 |
27 | paginator:
28 | size-key: size
29 | page-key: page
30 | default-size: 10
31 |
32 | server:
33 | port: 8080
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 xiwh
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/src/test/java/com/xiwh/paginator/demo/common/ATablePO.java:
--------------------------------------------------------------------------------
1 | package com.xiwh.paginator.demo.common;
2 |
3 | import java.io.Serializable;
4 |
5 | public class ATablePO implements Serializable {
6 | private int id;
7 | private String name;
8 | private String value;
9 |
10 | public int getId() {
11 | return id;
12 | }
13 |
14 | public String getName() {
15 | return name;
16 | }
17 |
18 | public String getValue() {
19 | return value;
20 | }
21 |
22 | public void setId(int id) {
23 | this.id = id;
24 | }
25 |
26 | public void setName(String name) {
27 | this.name = name;
28 | }
29 |
30 | public void setValue(String value) {
31 | this.value = value;
32 | }
33 |
34 | @Override
35 | public String toString() {
36 | final StringBuffer sb = new StringBuffer("ATableDO{");
37 | sb.append("id=").append(id);
38 | sb.append(", name='").append(name).append('\'');
39 | sb.append(", value='").append(value).append('\'');
40 | sb.append('}');
41 | return sb.toString();
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/com/xiwh/paginator/annotations/NormalPaginator.java:
--------------------------------------------------------------------------------
1 | package com.xiwh.paginator.annotations;
2 |
3 | import java.lang.annotation.*;
4 |
5 | @Documented
6 | @Retention(RetentionPolicy.RUNTIME)
7 | @Target(ElementType.METHOD)
8 | public @interface NormalPaginator {
9 |
10 | /**
11 | * Automatically load parameters through get request
12 | */
13 | boolean auto() default false;
14 |
15 | /**
16 | * Cache count results
17 | */
18 | boolean cache() default false;
19 |
20 | /**
21 | * Count SQL optimization
22 | */
23 | boolean countOptimization() default true;
24 |
25 | /**
26 | * Count cache expiry time, in seconds
27 | */
28 | int cacheExpiryTime() default 3600;
29 |
30 | /**
31 | * Page offset default 0
32 | */
33 | int startOffset() default 0;
34 |
35 | /**
36 | * Custom count statement, which is automatically counted by default
37 | */
38 | String countMethod() default "";
39 |
40 | /**
41 | * Custom limit statement
42 | * When customizing the limit, you must also customize the count
43 | */
44 | boolean customLimit() default false;
45 | }
46 |
--------------------------------------------------------------------------------
/src/test/resources/mapper/simplePagingXMLMapper.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
20 |
21 |
24 |
25 |
28 |
--------------------------------------------------------------------------------
/src/test/java/com/xiwh/paginator/demo/nplusonePaging/NPlusonePagingMapper.java:
--------------------------------------------------------------------------------
1 | package com.xiwh.paginator.demo.nplusonePaging;
2 |
3 | import com.xiwh.paginator.annotations.NPlusOnePaginator;
4 | import com.xiwh.paginator.demo.common.ATablePO;
5 | import com.xiwh.paginator.wrappers.NPlusOnePage;
6 | import com.xiwh.paginator.wrappers.PagingRowBounds;
7 | import org.apache.ibatis.annotations.Mapper;
8 | import org.apache.ibatis.annotations.Select;
9 | import org.apache.ibatis.session.RowBounds;
10 | import org.springframework.stereotype.Component;
11 |
12 | import java.util.List;
13 |
14 | @Mapper
15 | @Component
16 | public interface NPlusonePagingMapper {
17 | @Select("SELECT * FROM a where id != ${bb} or id != #{aa}")
18 | @NPlusOnePaginator
19 | List customRowBoundsSelect(String aa, Integer bb, RowBounds rowBounds);
20 |
21 | @Select("SELECT * FROM a where id != ${bb} or id != #{aa}")
22 | @NPlusOnePaginator
23 | List customPagingRowBoundsSelect(String aa, Integer bb, PagingRowBounds rowBounds);
24 |
25 | @Select("SELECT * FROM a")
26 | @NPlusOnePaginator(startOffset = 1)
27 | NPlusOnePage select();
28 |
29 | @Select("SELECT * FROM a where id != ${bb} or id != #{aa}")
30 | @NPlusOnePaginator(auto = true, startOffset = 1)
31 | NPlusOnePage requestPaging(Integer aa, Integer bb);
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/test/java/com/xiwh/paginator/demo/common/BTablePO.java:
--------------------------------------------------------------------------------
1 | package com.xiwh.paginator.demo.common;
2 |
3 | import java.io.Serializable;
4 |
5 | public class BTablePO implements Serializable {
6 | private int id;
7 | private String name;
8 | private String value;
9 | private String value2;
10 |
11 | public int getId() {
12 | return id;
13 | }
14 |
15 | public String getName() {
16 | return name;
17 | }
18 |
19 | public String getValue() {
20 | return value;
21 | }
22 |
23 | public void setId(int id) {
24 | this.id = id;
25 | }
26 |
27 | public void setName(String name) {
28 | this.name = name;
29 | }
30 |
31 | public void setValue(String value) {
32 | this.value = value;
33 | }
34 |
35 | public String getValue2() {
36 | return value2;
37 | }
38 |
39 | public void setValue2(String value2) {
40 | this.value2 = value2;
41 | }
42 |
43 | @Override
44 | public String toString() {
45 | final StringBuffer sb = new StringBuffer("BTableDO{");
46 | sb.append("id=").append(id);
47 | sb.append(", name='").append(name).append('\'');
48 | sb.append(", value='").append(value).append('\'');
49 | sb.append(", value2='").append(value2).append('\'');
50 | sb.append('}');
51 | return sb.toString();
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/java/com/xiwh/paginator/sqlGenerator/DataBaseType.java:
--------------------------------------------------------------------------------
1 | package com.xiwh.paginator.sqlGenerator;
2 |
3 | public enum DataBaseType {
4 | SQLITE,
5 | MYSQL,
6 | ORACLE,
7 | POSGRESQL,
8 | SQLSERVER,
9 | MARIADB;
10 |
11 | public String toDruidDbType(){
12 | switch (this){
13 | case POSGRESQL:
14 | return "postgresql";
15 | case MYSQL:
16 | return "mysql";
17 | case ORACLE:
18 | return "oracle";
19 | case SQLSERVER:
20 | return "sqlserver";
21 | case MARIADB:
22 | return "mariadb";
23 | case SQLITE:
24 | return "sqlite";
25 | }
26 | return null;
27 | }
28 |
29 | public static DataBaseType findByURL(String str){
30 | str = str.toLowerCase();
31 | if(str.contains(":mysql:")){
32 | return MYSQL;
33 | }else if(str.contains(":mariadb:")){
34 | return MARIADB;
35 | }else if(str.contains(":postgresql:")){
36 | return POSGRESQL;
37 | }else if(str.contains(":oracle:")){
38 | return ORACLE;
39 | }else if(str.contains(":sqlserver:")){
40 | return SQLSERVER;
41 | }else if(str.contains(":sqlite:")){
42 | return SQLITE;
43 | }
44 | return null;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/test/java/com/xiwh/paginator/demo/simplePaging/SimplePagingXMLTests.java:
--------------------------------------------------------------------------------
1 | package com.xiwh.paginator.demo.simplePaging;
2 |
3 | import com.xiwh.paginator.Paginator;
4 | import com.xiwh.paginator.TestApplication;
5 | import com.xiwh.paginator.wrappers.PagingRowBounds;
6 | import org.apache.ibatis.session.RowBounds;
7 | import org.junit.jupiter.api.Test;
8 | import org.junit.runner.RunWith;
9 | import org.springframework.beans.factory.annotation.Autowired;
10 | import org.springframework.boot.test.context.SpringBootTest;
11 | import org.springframework.test.context.ContextConfiguration;
12 | import org.springframework.test.context.junit4.SpringRunner;
13 |
14 | @SuppressWarnings("ALL")
15 | @SpringBootTest
16 | @RunWith(SpringRunner.class)
17 | @ContextConfiguration(classes = TestApplication.class)
18 | class SimplePagingXMLTests {
19 |
20 | @Autowired
21 | SimplePagingXMLMapper mapper;
22 |
23 | @Test
24 | void testOriginalPaging() {
25 | RowBounds rowBounds = new RowBounds(10,10);
26 | Object list = mapper.customRowBoundsSelect("\"AA'",0, rowBounds);
27 | System.out.println(list);
28 | }
29 |
30 | @Test
31 | void testOriginalPaging2() {
32 | PagingRowBounds rowBounds = new PagingRowBounds(1,10,1,false);
33 | Object list = mapper.customPagingRowBoundsSelect("\"AA'",0, rowBounds);
34 | System.out.println(rowBounds);
35 | System.out.println(list);
36 | }
37 |
38 | @Test
39 | void testBasicPaging() {
40 | Paginator.paginate(2,10);
41 | Object list = mapper.select("\"AA'",0);
42 | System.out.println(list);
43 | }
44 |
45 | }
--------------------------------------------------------------------------------
/src/test/java/com/xiwh/paginator/demo/nplusonePaging/NPlusOnePagingTests.java:
--------------------------------------------------------------------------------
1 | package com.xiwh.paginator.demo.nplusonePaging;
2 |
3 | import com.xiwh.paginator.Paginator;
4 | import com.xiwh.paginator.TestApplication;
5 | import com.xiwh.paginator.demo.simplePaging.SimplePagingMapper;
6 | import com.xiwh.paginator.wrappers.PagingRowBounds;
7 | import org.apache.ibatis.session.RowBounds;
8 | import org.junit.jupiter.api.Test;
9 | import org.junit.runner.RunWith;
10 | import org.springframework.beans.factory.annotation.Autowired;
11 | import org.springframework.boot.test.context.SpringBootTest;
12 | import org.springframework.test.context.ContextConfiguration;
13 | import org.springframework.test.context.junit4.SpringRunner;
14 |
15 | @SuppressWarnings("ALL")
16 | @SpringBootTest
17 | @RunWith(SpringRunner.class)
18 | @ContextConfiguration(classes = TestApplication.class)
19 | class NPlusOnePagingTests {
20 |
21 | @Autowired
22 | NPlusonePagingMapper mapper;
23 |
24 | @Test
25 | void testOriginalPaging() {
26 | RowBounds rowBounds = new RowBounds(10,10);
27 | Object list = mapper.customRowBoundsSelect("\"AA'",0, rowBounds);
28 | System.out.println(list);
29 | }
30 |
31 | @Test
32 | void testOriginalPaging2() {
33 | PagingRowBounds rowBounds = new PagingRowBounds(1,10,1,false);
34 | Object list = mapper.customPagingRowBoundsSelect("\"AA'",0, rowBounds);
35 | System.out.println(rowBounds);
36 | System.out.println(list);
37 | }
38 |
39 | @Test
40 | void testBasicPaging() {
41 | Paginator.paginate(1,10);
42 | Object list = mapper.select();
43 | System.out.println(list);
44 | }
45 |
46 |
47 | }
--------------------------------------------------------------------------------
/src/test/java/com/xiwh/paginator/demo/simplePaging/SimplePagingMapper.java:
--------------------------------------------------------------------------------
1 | package com.xiwh.paginator.demo.simplePaging;
2 |
3 | import com.xiwh.paginator.annotations.NormalPaginator;
4 | import com.xiwh.paginator.demo.common.ATablePO;
5 | import com.xiwh.paginator.wrappers.PagingRowBounds;
6 | import com.xiwh.paginator.wrappers.SimplePage;
7 | import org.apache.ibatis.annotations.Mapper;
8 | import org.apache.ibatis.annotations.Select;
9 | import org.apache.ibatis.session.RowBounds;
10 | import org.springframework.stereotype.Component;
11 |
12 | import java.util.List;
13 |
14 | @Mapper
15 | public interface SimplePagingMapper {
16 | @Select("SELECT * FROM a where id != ${bb} or id != #{aa} order by id")
17 | @NormalPaginator
18 | List customRowBoundsSelect(Integer aa, Integer bb, RowBounds rowBounds);
19 |
20 | @Select("SELECT * FROM a where id != ${bb} or id != #{aa} order by id")
21 | @NormalPaginator
22 | List customPagingRowBoundsSelect(Integer aa, Integer bb, PagingRowBounds rowBounds);
23 |
24 | @Select("SELECT * FROM a order by id")
25 | @NormalPaginator()
26 | SimplePage select(Integer aa, Integer bb);
27 |
28 | @Select("SELECT * FROM a where id != ${bb} or id != #{aa} order by id")
29 | @NormalPaginator(auto = true, startOffset = 1, cacheExpiryTime = 300)
30 | SimplePage requestPaging(Integer aa, Integer bb);
31 |
32 | @Select("SELECT * FROM a where id != ${bb} or id != #{aa} order by id")
33 | @NormalPaginator(countMethod = "customCount")
34 | SimplePage customCountSelect(Integer aa, Integer bb);
35 |
36 | @Select("SELECT count(1) FROM a where id != ${bb} or id != #{aa}")
37 | Integer customCount(Integer aa, Integer bb);
38 |
39 | @Select("SELECT * FROM a where id != ${bb} or id != #{aa} limit :offset,:limit")
40 | @NormalPaginator(customLimit = true, countMethod = "customCount")
41 | SimplePage customLimitSelect(Integer aa, Integer bb);
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/com/xiwh/paginator/wrappers/NPlusOnePage.java:
--------------------------------------------------------------------------------
1 | package com.xiwh.paginator.wrappers;
2 |
3 | import java.util.*;
4 | import java.util.function.Consumer;
5 |
6 | public class NPlusOnePage implements NPlusOnePageWrapper {
7 |
8 | private List list;
9 | private int page;
10 | private int size;
11 | private boolean hasNext;
12 | private int pageOffset;
13 |
14 | private Map cachedMap = null;
15 |
16 | @Override
17 | public void init(List list, boolean hasNext, int pageOffset, int physicalPage, int size) {
18 | this.list = list;
19 | this.page = physicalPage+pageOffset;
20 | this.size = size;
21 | this.pageOffset = pageOffset;
22 | this.hasNext = hasNext;
23 | }
24 |
25 | public List list() {
26 | return list;
27 | }
28 |
29 | public int getSize() {
30 | return size;
31 | }
32 |
33 | public int getPage() {
34 | return page;
35 | }
36 |
37 | public boolean hasNext() {
38 | return hasNext;
39 | }
40 |
41 | @Override
42 | public Iterator iterator() {
43 | return list.iterator();
44 | }
45 |
46 | @Override
47 | public void forEach(Consumer super T> action) {
48 | list.forEach(action);
49 | }
50 |
51 | @Override
52 | public Spliterator spliterator() {
53 | return list.spliterator();
54 | }
55 |
56 | /**
57 | * If modifiable = false, the map will be cached
58 | * @param modifiable Allow modification?
59 | * @return
60 | */
61 | public Map toMap(boolean modifiable){
62 | if(!modifiable && cachedMap!=null) {
63 | return cachedMap;
64 | }else{
65 | Map map = new HashMap(8);
66 | map.put("list", this.list());
67 | map.put("page", this.page);
68 | map.put("size", this.size);
69 | map.put("has_next", this.hasNext());
70 | if(!modifiable){
71 | map = Collections.unmodifiableMap(map);
72 | cachedMap = map;
73 | }
74 | return map;
75 | }
76 | }
77 |
78 | /**
79 | * toMap == toMap(false)
80 | * @return Unmodifiable map
81 | */
82 | public Map toMap(){
83 | return toMap(false);
84 | }
85 |
86 | @Override
87 | public String toString() {
88 | final StringBuffer sb = new StringBuffer("NPlusOnePage{");
89 | sb.append("list=").append(list);
90 | sb.append(", page=").append(page);
91 | sb.append(", size=").append(size);
92 | sb.append(", hasNext=").append(hasNext);
93 | sb.append('}');
94 | return sb.toString();
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/main/java/com/xiwh/paginator/Paginator.java:
--------------------------------------------------------------------------------
1 | package com.xiwh.paginator;
2 |
3 | import com.xiwh.paginator.interceptors.PaginatorLimitHandler;
4 | import com.xiwh.paginator.utils.StringUtils;
5 | import com.xiwh.paginator.wrappers.PageParamsWrapper;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.context.ApplicationContext;
8 | import org.springframework.core.env.Environment;
9 | import org.springframework.stereotype.Component;
10 |
11 | import javax.annotation.PostConstruct;
12 |
13 | @Component("Springboot-Mybatis-Paginator")
14 | public class Paginator {
15 |
16 | protected static Paginator instance;
17 |
18 | ThreadLocal pageParamsThreadLocal = new ThreadLocal<>();
19 |
20 | @Autowired
21 | PaginatorLimitHandler paginatorInterceptor;
22 | @Autowired
23 | private Environment environment;
24 | @Autowired
25 | private ApplicationContext context;
26 | String queryPageKey;
27 | String querySizeKey;
28 | Integer defaultSize;
29 |
30 | @PostConstruct
31 | private void init(){
32 | querySizeKey = environment.getProperty("paginator.size-key", "size");
33 | queryPageKey = environment.getProperty("paginator.page-key", "page");
34 | defaultSize = environment.getProperty("paginator.default-size", Integer.class, 10);
35 | instance = this;
36 | }
37 |
38 | private void _startPaginate(int page, int size, boolean forceCounting){
39 | pageParamsThreadLocal.set(new PageParamsWrapper(page, size, forceCounting));
40 | }
41 |
42 | protected void _autoInject() throws ClassNotFoundException {
43 | org.springframework.web.context.request.ServletRequestAttributes servletRequestAttributes =
44 | (org.springframework.web.context.request.ServletRequestAttributes)
45 | org.springframework.web.context.request.RequestContextHolder.getRequestAttributes();
46 | javax.servlet.http.HttpServletRequest request = servletRequestAttributes.getRequest();
47 | int page = StringUtils.safeToInt(request.getParameter(this.queryPageKey),0);
48 | int size = StringUtils.safeToInt(request.getParameter(this.querySizeKey),defaultSize);
49 | _startPaginate(page, size, false);
50 | }
51 |
52 | public static PageParamsWrapper currentPageParams(){
53 | return instance.pageParamsThreadLocal.get();
54 | }
55 |
56 | public static PageParamsWrapper readPageParams(){
57 | PageParamsWrapper pagingRowBounds = instance.pageParamsThreadLocal.get();
58 | instance.pageParamsThreadLocal.remove();
59 | return pagingRowBounds;
60 | }
61 |
62 | public static void paginate(int page, int size){
63 | instance._startPaginate(page, size, false);
64 | }
65 |
66 | public static void paginate(int page, int size, boolean forceCounting){
67 | instance._startPaginate(page, size, forceCounting);
68 | }
69 |
70 |
71 | public static void autoInjectFromRequest(){
72 | try {
73 | instance._autoInject();
74 | }catch (Exception e){
75 | throw new RuntimeException(e);
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/test/java/com/xiwh/paginator/demo/sql/SQLCountTests.java:
--------------------------------------------------------------------------------
1 | package com.xiwh.paginator.demo.sql;
2 |
3 | import com.xiwh.paginator.TestApplication;
4 | import com.xiwh.paginator.sqlGenerator.DataBaseType;
5 | import com.xiwh.paginator.sqlGenerator.PaginatorSqlGenerator;
6 | import org.junit.jupiter.api.Test;
7 | import org.junit.runner.RunWith;
8 | import org.springframework.boot.test.context.SpringBootTest;
9 | import org.springframework.test.context.ContextConfiguration;
10 | import org.springframework.test.context.junit4.SpringRunner;
11 |
12 | @SpringBootTest
13 | @RunWith(SpringRunner.class)
14 | @ContextConfiguration(classes = TestApplication.class)
15 | class SQLCountTests {
16 |
17 | @Test
18 | void countSql() {
19 | PaginatorSqlGenerator sqlGenerator = new PaginatorSqlGenerator("SELECT a.id FROM a GROUP BY a.id HAVING id != 3 ORDER BY a.id", DataBaseType.MYSQL);
20 | System.out.println("countSql1:\n"+sqlGenerator.toCountSql());
21 | System.out.println();
22 | sqlGenerator = new PaginatorSqlGenerator("SELECT a.id FROM a where id != 3 ORDER BY a.id", DataBaseType.MYSQL);
23 | System.out.println("countSql2:\r\n"+sqlGenerator.toCountSql());
24 | System.out.println();
25 | sqlGenerator = new PaginatorSqlGenerator("SELECT\n" +
26 | "\ta.id AS aid \n" +
27 | "FROM\n" +
28 | "\ta\n" +
29 | "LEFT JOIN b on a.id = b.id\n" +
30 | "WHERE b.id in (select max(id) FROM a)", DataBaseType.MYSQL);
31 | System.out.println("countSql3:\r\n"+sqlGenerator.toCountSql());
32 | }
33 |
34 | @Test
35 | void optimizedCountSql() {
36 | PaginatorSqlGenerator sqlGenerator = new PaginatorSqlGenerator(
37 | "select a.id,b.name,c.value FROM a\n" +
38 | "left join b on b.id = a.id\n" +
39 | "left join c on c.id = b.id\n" +
40 | "group by b.name,c.value\n",
41 | DataBaseType.POSGRESQL
42 | );
43 | System.out.println("countSql1:\n"+sqlGenerator.toOptimizedCountSql());
44 | System.out.println();
45 | System.out.println("limitSql1:\n"+sqlGenerator.toLimitSql(0,100));
46 | System.out.println("---------------------");
47 | sqlGenerator = new PaginatorSqlGenerator(
48 | "select a.id,b.name,c.value FROM a\n " +
49 | "left join b on b.id = a.id\n " +
50 | "left join c on c.id = b.id\n " +
51 | "group by a.id\n" +
52 | "having id != 0",
53 | DataBaseType.MYSQL
54 | );
55 | System.out.println("countSql2:\n"+sqlGenerator.toOptimizedCountSql());
56 | System.out.println();
57 | System.out.println("limitSql2:\n"+sqlGenerator.toLimitSql(0,100));
58 | System.out.println("---------------------");
59 | sqlGenerator = new PaginatorSqlGenerator(
60 | "SELECT a.a1, a.a2, a.a3 FROM TABLE1 a, TABLE2 b WHERE a.a=t2.a",
61 | DataBaseType.ORACLE);
62 | System.out.println("countSql3:\n"+sqlGenerator.toOptimizedCountSql());
63 | System.out.println();
64 | System.out.println("limitSql3:\n"+sqlGenerator.toLimitSql(0,100));
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/main/java/com/xiwh/paginator/wrappers/SimplePage.java:
--------------------------------------------------------------------------------
1 | package com.xiwh.paginator.wrappers;
2 |
3 | import java.util.*;
4 |
5 | public class SimplePage extends NormalPageWrapperBase {
6 |
7 | private int total;
8 | private transient int startOffset;
9 | private transient int physicalPage;
10 | private int page;
11 | private int size;
12 | private int totalPage;
13 | private List list;
14 |
15 | private Map cachedMap = null;
16 |
17 | public boolean hasNext(){
18 | return list.size()>=size;
19 | }
20 |
21 | public boolean hasLast(){
22 | return physicalPage>0;
23 | }
24 |
25 | public int size(){
26 | return size;
27 | }
28 |
29 | public int page(){
30 | return page;
31 | }
32 |
33 | public int total(){
34 | return total;
35 | }
36 |
37 | public int totalPage(){
38 | return totalPage;
39 | }
40 |
41 | public List list() {
42 | return list;
43 | }
44 |
45 | /**
46 | * Paged callback
47 | * @param list
48 | * @param count
49 | * @param startOffset
50 | * @param physicalPage
51 | * @param size
52 | */
53 | @Override
54 | public void onInit(List list, int count, int startOffset, int physicalPage, int size) {
55 | this.list = list;
56 | this.total = count;
57 | this.physicalPage = physicalPage;
58 | this.startOffset = startOffset;
59 | this.page = physicalPage + startOffset;
60 | this.size = size;
61 | this.totalPage = total/size+(total%size==0?0:1);
62 | }
63 |
64 | /**
65 | * If modifiable = false, the map will be cached
66 | * @param modifiable Allow modification?
67 | * @return
68 | */
69 | public Map toMap(boolean modifiable){
70 | if(!modifiable && cachedMap!=null) {
71 | return cachedMap;
72 | }else{
73 | Map map = new HashMap(8);
74 | map.put("list", this.list());
75 | map.put("page", this.page);
76 | map.put("size", this.size);
77 | map.put("total", this.total);
78 | map.put("total_page", totalPage);
79 | map.put("has_last", this.hasLast());
80 | map.put("has_next", this.hasNext());
81 | if(!modifiable){
82 | map = Collections.unmodifiableMap(map);
83 | cachedMap = map;
84 | }
85 | return map;
86 | }
87 | }
88 |
89 | /**
90 | * toMap == toMap(false)
91 | * @return Unmodifiable map
92 | */
93 | public Map toMap(){
94 | return toMap(false);
95 | }
96 |
97 | @Override
98 | public String toString() {
99 | final StringBuffer sb = new StringBuffer("SimplePage{");
100 | sb.append("total=").append(total);
101 | sb.append(", startOffset=").append(startOffset);
102 | sb.append(", physicalPage=").append(physicalPage);
103 | sb.append(", page=").append(page);
104 | sb.append(", size=").append(size);
105 | sb.append(", hasLast=").append(hasLast());
106 | sb.append(", hasNext=").append(hasNext());
107 | sb.append(", totalPage=").append(totalPage);
108 | sb.append(", list=").append(list);
109 | sb.append('}');
110 | return sb.toString();
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/src/main/java/com/xiwh/paginator/wrappers/PagingRowBounds.java:
--------------------------------------------------------------------------------
1 | package com.xiwh.paginator.wrappers;
2 |
3 | import org.apache.ibatis.session.RowBounds;
4 |
5 | import java.util.List;
6 |
7 | public class PagingRowBounds extends RowBounds {
8 | private boolean forceCounting = false;
9 | private int end = -1;
10 | private int page = -1;
11 | private int pageOffset = -1;
12 | private int count = -1;
13 | private boolean hasNext;
14 | public PagingRowBounds(int page, int size, int pageOffset, boolean forceCounting){
15 | super((page-pageOffset)*size, size);
16 | this.page = page;
17 | this.pageOffset = pageOffset;
18 | this.end = getOffset() + getLimit();
19 | this.forceCounting = forceCounting;
20 | }
21 |
22 | public PagingRowBounds(int page, int size){
23 | super(page*size, size);
24 | this.page = page;
25 | this.pageOffset = 0;
26 | this.forceCounting = false;
27 | this.end = getOffset() + getLimit();
28 | }
29 |
30 | public PagingRowBounds(PageParamsWrapper pageParams, int pageOffset){
31 | super((pageParams.getPage()-pageOffset) * pageParams.getLimit(), pageParams.getLimit());
32 | this.forceCounting = pageParams.isForceCounting();
33 | this.end = getOffset() + getLimit();
34 | this.page = pageParams.getPage() - pageOffset;
35 | this.pageOffset = pageOffset;
36 | }
37 |
38 | public PagingRowBounds(RowBounds rowBounds){
39 | super(rowBounds.getOffset(), rowBounds.getLimit());
40 | this.forceCounting = false;
41 | this.page = getOffset()/getLimit();
42 | this.pageOffset = 0;
43 | this.end = getOffset() + getLimit();
44 | }
45 |
46 | public boolean isForceCounting() {
47 | return forceCounting;
48 | }
49 |
50 | public int getEnd(){
51 | return end;
52 | }
53 |
54 | public int getPage() {
55 | return page;
56 | }
57 |
58 | public int getPageOffset() {
59 | return pageOffset;
60 | }
61 |
62 | public void setCount(int count) {
63 | this.count = count;
64 | }
65 |
66 | public int getCount() {
67 | return count;
68 | }
69 |
70 | public void setHasNext(boolean hasNext) {
71 | this.hasNext = hasNext;
72 | }
73 |
74 | public boolean hasNext() {
75 | return hasNext;
76 | }
77 |
78 | public T toNormalPage(Class clazz, List list){
79 | try {
80 | T obj = clazz.newInstance();
81 | obj.init(list, getCount(), pageOffset, getPage()-pageOffset,getLimit());
82 | return obj;
83 | } catch (Exception e) {
84 | throw new RuntimeException("toNormalPage Error!", e);
85 | }
86 | }
87 |
88 | public T toNPlushOnePage(Class clazz, List list){
89 | try {
90 | T obj = clazz.newInstance();
91 | obj.init(list, hasNext, pageOffset, getPage()-pageOffset, getLimit());
92 | return obj;
93 | } catch (Exception e) {
94 | throw new RuntimeException("toNormalPage Error!", e);
95 | }
96 | }
97 |
98 | @Override
99 | public String toString() {
100 | final StringBuffer sb = new StringBuffer("PagingRowBounds{");
101 | sb.append("forceCounting=").append(forceCounting);
102 | sb.append(", offset=").append(getOffset());
103 | sb.append(", limit=").append(getLimit());
104 | sb.append(", end=").append(end);
105 | sb.append(", page=").append(page);
106 | sb.append(", pageOffset=").append(pageOffset);
107 | sb.append(", count=").append(count);
108 | sb.append(", hasNext=").append(hasNext);
109 | sb.append('}');
110 | return sb.toString();
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/src/test/java/com/xiwh/paginator/demo/simplePaging/SimplePagingTests.java:
--------------------------------------------------------------------------------
1 | package com.xiwh.paginator.demo.simplePaging;
2 |
3 | import com.xiwh.paginator.Paginator;
4 | import com.xiwh.paginator.TestApplication;
5 | import com.xiwh.paginator.demo.common.ATablePO;
6 | import com.xiwh.paginator.wrappers.PagingRowBounds;
7 | import com.xiwh.paginator.wrappers.SimplePage;
8 | import org.apache.ibatis.session.RowBounds;
9 | import org.junit.jupiter.api.Test;
10 | import org.junit.runner.RunWith;
11 | import org.springframework.beans.factory.annotation.Autowired;
12 | import org.springframework.boot.test.context.SpringBootTest;
13 | import org.springframework.http.MediaType;
14 | import org.springframework.test.context.ContextConfiguration;
15 | import org.springframework.test.context.junit4.SpringRunner;
16 | import org.springframework.test.web.servlet.MockMvc;
17 | import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
18 | import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
19 | import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
20 | import org.springframework.test.web.servlet.setup.MockMvcBuilders;
21 | import org.springframework.web.context.WebApplicationContext;
22 |
23 | import javax.annotation.PostConstruct;
24 | import java.util.List;
25 |
26 | @SuppressWarnings("ALL")
27 | @SpringBootTest
28 | @RunWith(SpringRunner.class)
29 | @ContextConfiguration(classes = TestApplication.class)
30 | class SimplePagingTests {
31 |
32 | @Autowired
33 | private WebApplicationContext wac ;
34 | @Autowired
35 | SimplePagingMapper mapper;
36 |
37 | private MockMvc mockMvc;
38 |
39 | @PostConstruct
40 | public void setUp(){
41 | mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
42 | }
43 |
44 | @Test
45 | void testBasicPaging() {
46 | Paginator.paginate(0,10);
47 | SimplePage page = mapper.select(321,0);
48 | System.out.println(page);
49 | System.out.println(page.hasLast());
50 | System.out.println(page.hasNext());
51 | System.out.println(page.totalPage());
52 | System.out.println(page.total());
53 | System.out.println(page.size());
54 | System.out.println(page.list());
55 | System.out.println(page.toMap());
56 | for (ATablePO item:page) {
57 | item.setName(item.getId()+":"+item.getName());
58 | }
59 |
60 | }
61 |
62 | @Test
63 | void testOriginalPaging() {
64 | RowBounds rowBounds = new RowBounds(10,10);
65 | Object list = mapper.customRowBoundsSelect(321,0, rowBounds);
66 | System.out.println(list);
67 | }
68 |
69 | @Test
70 | void testAdvancedPaging() {
71 | PagingRowBounds rowBounds = new PagingRowBounds(3,10,1,false);
72 | List list = mapper.customPagingRowBoundsSelect(321,0, rowBounds);
73 | System.out.println(rowBounds);
74 | System.out.println(rowBounds.toNormalPage(SimplePage.class, list));
75 | }
76 |
77 | @Test
78 | void testCustomCountPaging() {
79 | Paginator.paginate(2,10);
80 | Object list = mapper.customCountSelect(321,0);
81 | System.out.println(list);
82 | }
83 |
84 | @Test
85 | void testCustomLimitPaging() {
86 | Paginator.paginate(0,10);
87 | Object list = mapper.customCountSelect(123,0);
88 | System.out.println(list);
89 | }
90 |
91 | @Test
92 | void testGetRequestPaging() throws Exception {
93 | System.out.println(mockMvc);
94 | mockMvc.perform(MockMvcRequestBuilders.get("/test1?page=3&size=10")
95 | .contentType(MediaType.APPLICATION_FORM_URLENCODED))
96 | .andExpect(MockMvcResultMatchers.status().isOk())
97 | .andDo(MockMvcResultHandlers.print());;
98 | // System.out.println(mvcResult.getAsyncResult());
99 | }
100 |
101 | }
--------------------------------------------------------------------------------
/src/main/java/com/xiwh/paginator/interceptors/PaginatorLimitHandler.java:
--------------------------------------------------------------------------------
1 | package com.xiwh.paginator.interceptors;
2 |
3 | import com.xiwh.paginator.Paginator;
4 | import com.xiwh.paginator.cache.MybatisMethodCache;
5 | import com.xiwh.paginator.cache.impl.MybatisMethodCacheImpl;
6 | import com.xiwh.paginator.sqlGenerator.PaginatorSqlGenerator;
7 | import com.xiwh.paginator.wrappers.PagingRowBounds;
8 | import org.apache.ibatis.executor.statement.StatementHandler;
9 | import org.apache.ibatis.mapping.*;
10 | import org.apache.ibatis.plugin.*;
11 | import org.springframework.beans.factory.annotation.Autowired;
12 | import org.springframework.beans.factory.annotation.Qualifier;
13 | import org.springframework.stereotype.Component;
14 |
15 | import java.lang.reflect.Field;
16 | import java.sql.*;
17 |
18 |
19 | @Intercepts({
20 | @Signature(
21 | type = StatementHandler.class,
22 | method = "prepare",
23 | args = {Connection.class, Integer.class}
24 | )
25 | })
26 | @Component
27 | public class PaginatorLimitHandler extends BaseInterceptor {
28 | @Autowired
29 | Paginator paginator;
30 | @Autowired
31 | @Qualifier("mybatisMethodCacheImpl")
32 | MybatisMethodCache methodCache;
33 |
34 |
35 | @Override
36 | public Object intercept(Invocation invocation) throws Throwable {
37 | StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
38 | PagingInfoWrapper pagingInfoWrapper = getPagingInfoWrapper();
39 | if(pagingInfoWrapper==null){
40 | return invocation.proceed();
41 | }else if(pagingInfoWrapper.counting){
42 | return invocation.proceed();
43 | }
44 | BoundSql boundSql = statementHandler.getBoundSql();
45 | pagingInfoWrapper.boundSql = boundSql;
46 | pagingInfoWrapper.statementHandler = statementHandler;
47 | //Get raw SQL
48 | String sql = boundSql.getSql();
49 | //Generate paging statement
50 | PaginatorSqlGenerator sqlGenerator = new PaginatorSqlGenerator(sql, pagingInfoWrapper.dataBaseType);
51 | Field field = boundSql.getClass().getDeclaredField("sql");
52 | field.setAccessible(true);
53 | PagingRowBounds pagingRowBounds = pagingInfoWrapper.pagingRowBounds;
54 | pagingInfoWrapper.sqlGenerator = sqlGenerator;
55 | MybatisMethodCache.MethodInfo methodInfo = pagingInfoWrapper.methodInfo;
56 |
57 | if (methodInfo.getPaginatorType() == MybatisMethodCache.TYPE_NORMAL) {
58 | if(!methodInfo.getNormalPaginator().customLimit()) {
59 | //Modify limit sql
60 | String limitSql = sqlGenerator.toLimitSql(pagingRowBounds.getOffset(), pagingRowBounds.getLimit());
61 | field.set(boundSql, limitSql);
62 | }else{
63 | //Custom limit
64 | sql = sql.replaceAll(
65 | ":limit", String.valueOf(pagingRowBounds.getLimit())
66 | ).replaceAll(":offset", String.valueOf(pagingRowBounds.getOffset())
67 | ).replaceAll(":end", String.valueOf(pagingRowBounds.getEnd()));
68 | field.set(boundSql, sql);
69 | }
70 |
71 | }else if(methodInfo.getPaginatorType() == MybatisMethodCache.TYPE_N_PLUS_ONE) {
72 | if (!methodInfo.getNPlusOnePaginator().customLimit()) {
73 | String limitSql = sqlGenerator.toLimitSql(pagingRowBounds.getOffset(), pagingRowBounds.getLimit()+1);
74 | field.set(boundSql, limitSql);
75 | }else{
76 | //Custom limit
77 | sql = sql.replaceAll(
78 | ":limit", String.valueOf(pagingRowBounds.getLimit()+1)
79 | ).replaceAll(":offset", String.valueOf(pagingRowBounds.getOffset())
80 | ).replaceAll(":end", String.valueOf(pagingRowBounds.getEnd()+1));
81 | field.set(boundSql, sql);
82 | }
83 | }
84 | return invocation.proceed();
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/test/java/com/xiwh/paginator/demo/common/PagingController.java:
--------------------------------------------------------------------------------
1 | package com.xiwh.paginator.demo.common;
2 |
3 | import com.xiwh.paginator.demo.nplusonePaging.NPlusonePagingMapper;
4 | import com.xiwh.paginator.demo.simplePaging.SimplePagingMapper;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.web.bind.annotation.RequestMapping;
7 | import org.springframework.web.bind.annotation.RestController;
8 |
9 | import javax.servlet.http.HttpServletRequest;
10 | import java.util.Map;
11 |
12 | @SuppressWarnings("ALL")
13 | @RestController
14 | class PagingController {
15 |
16 | @Autowired
17 | SimplePagingMapper simplePagingMapper;
18 | @Autowired
19 | NPlusonePagingMapper nPlusonePagingMapper;
20 |
21 | /**
22 | * http://127.0.0.1:8080/test1?page=1&size=20
23 | * {
24 | * "total": 16,
25 | * "size": 10,
26 | * "total_page": 2,
27 | * "has_last": false,
28 | * "has_next": true,
29 | * "page": 1,
30 | * "list": [{
31 | * "id": 1,
32 | * "name": "a",
33 | * "value": "v1"
34 | * }, {
35 | * "id": 2,
36 | * "name": "a",
37 | * "value": "v1"
38 | * }, {
39 | * "id": 3,
40 | * "name": "a",
41 | * "value": "v1"
42 | * }, {
43 | * "id": 4,
44 | * "name": "d",
45 | * "value": "v4"
46 | * }, {
47 | * "id": 5,
48 | * "name": "e",
49 | * "value": "v5"
50 | * }, {
51 | * "id": 6,
52 | * "name": "f",
53 | * "value": "v6"
54 | * }, {
55 | * "id": 7,
56 | * "name": "g",
57 | * "value": "v7"
58 | * }, {
59 | * "id": 8,
60 | * "name": "h",
61 | * "value": "v8"
62 | * }, {
63 | * "id": 9,
64 | * "name": "i",
65 | * "value": "v9"
66 | * }, {
67 | * "id": 10,
68 | * "name": "j",
69 | * "value": "v10"
70 | * }]
71 | * }
72 | */
73 | @RequestMapping("/test1")
74 | public Map requestPaging(HttpServletRequest request){
75 | System.out.println("[[["+Thread.currentThread().getName());
76 | Map map = simplePagingMapper.requestPaging(321,123).toMap();
77 | System.out.println("]]]");
78 | return map;
79 | }
80 |
81 | /**
82 | * http://127.0.0.1:8080/test2?page=1&size=20
83 | * {
84 | * "has_next": true,
85 | * "page": 1,
86 | * "list": [{
87 | * "id": 1,
88 | * "name": "a",
89 | * "value": "v1"
90 | * }, {
91 | * "id": 2,
92 | * "name": "a",
93 | * "value": "v1"
94 | * }, {
95 | * "id": 3,
96 | * "name": "a",
97 | * "value": "v1"
98 | * }, {
99 | * "id": 4,
100 | * "name": "d",
101 | * "value": "v4"
102 | * }, {
103 | * "id": 5,
104 | * "name": "e",
105 | * "value": "v5"
106 | * }, {
107 | * "id": 6,
108 | * "name": "f",
109 | * "value": "v6"
110 | * }, {
111 | * "id": 7,
112 | * "name": "g",
113 | * "value": "v7"
114 | * }, {
115 | * "id": 8,
116 | * "name": "h",
117 | * "value": "v8"
118 | * }, {
119 | * "id": 9,
120 | * "name": "i",
121 | * "value": "v9"
122 | * }, {
123 | * "id": 10,
124 | * "name": "j",
125 | * "value": "v10"
126 | * }, {
127 | * "id": 11,
128 | * "name": "k",
129 | * "value": "v11"
130 | * }, {
131 | * "id": 12,
132 | * "name": "l",
133 | * "value": "v12"
134 | * }, {
135 | * "id": 13,
136 | * "name": "m",
137 | * "value": "v13"
138 | * }, {
139 | * "id": 14,
140 | * "name": "n",
141 | * "value": "v14"
142 | * }, {
143 | * "id": 15,
144 | * "name": "o",
145 | * "value": "v15"
146 | * }, {
147 | * "id": 16,
148 | * "name": "p",
149 | * "value": "v16"
150 | * }, {
151 | * "id": 17,
152 | * "name": "q",
153 | * "value": "v17"
154 | * }, {
155 | * "id": 18,
156 | * "name": "r",
157 | * "value": "v18"
158 | * }, {
159 | * "id": 19,
160 | * "name": "s",
161 | * "value": "v19"
162 | * }, {
163 | * "id": 20,
164 | * "name": "t",
165 | * "value": "v20"
166 | * }],
167 | * "size": 20
168 | * }
169 | */
170 | @RequestMapping("/test2")
171 | public Map requestNplusOnePaging(HttpServletRequest request){
172 | System.out.println("[[["+Thread.currentThread().getId());
173 | Map map = nPlusonePagingMapper.requestPaging(321,123).toMap();
174 | System.out.println("]]]");
175 | return map;
176 | }
177 |
178 | }
--------------------------------------------------------------------------------
/src/test/resources/test2.sql:
--------------------------------------------------------------------------------
1 | /*
2 | Navicat Premium Data Transfer
3 |
4 | Source Server : loc5.6
5 | Source Server Type : MySQL
6 | Source Server Version : 50728
7 | Source Host : localhost:3306
8 | Source Schema : test2
9 |
10 | Target Server Type : MySQL
11 | Target Server Version : 50728
12 | File Encoding : 65001
13 |
14 | Date: 25/09/2020 14:22:46
15 | */
16 |
17 | SET NAMES utf8mb4;
18 | SET FOREIGN_KEY_CHECKS = 0;
19 |
20 | -- ----------------------------
21 | -- Table structure for a
22 | -- ----------------------------
23 | DROP TABLE IF EXISTS `a`;
24 | CREATE TABLE `a` (
25 | `id` int(11) NOT NULL AUTO_INCREMENT,
26 | `name` varchar(255) NOT NULL,
27 | `value` varchar(255) DEFAULT NULL,
28 | PRIMARY KEY (`id`) USING BTREE
29 | ) ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=latin1;
30 |
31 | -- ----------------------------
32 | -- Records of a
33 | -- ----------------------------
34 | BEGIN;
35 | INSERT INTO `a` VALUES (1, 'a', 'v1');
36 | INSERT INTO `a` VALUES (2, 'a', 'v1');
37 | INSERT INTO `a` VALUES (3, 'a', 'v1');
38 | INSERT INTO `a` VALUES (4, 'd', 'v4');
39 | INSERT INTO `a` VALUES (5, 'e', 'v5');
40 | INSERT INTO `a` VALUES (6, 'f', 'v6');
41 | INSERT INTO `a` VALUES (7, 'g', 'v7');
42 | INSERT INTO `a` VALUES (8, 'h', 'v8');
43 | INSERT INTO `a` VALUES (9, 'i', 'v9');
44 | INSERT INTO `a` VALUES (10, 'j', 'v10');
45 | INSERT INTO `a` VALUES (11, 'k', 'v11');
46 | INSERT INTO `a` VALUES (12, 'l', 'v12');
47 | INSERT INTO `a` VALUES (13, 'm', 'v13');
48 | INSERT INTO `a` VALUES (14, 'n', 'v14');
49 | INSERT INTO `a` VALUES (15, 'o', 'v15');
50 | INSERT INTO `a` VALUES (16, 'p', 'v16');
51 | INSERT INTO `a` VALUES (17, 'q', 'v17');
52 | INSERT INTO `a` VALUES (18, 'r', 'v18');
53 | INSERT INTO `a` VALUES (19, 's', 'v19');
54 | INSERT INTO `a` VALUES (20, 't', 'v20');
55 | INSERT INTO `a` VALUES (21, 'u', 'v21');
56 | INSERT INTO `a` VALUES (22, 'v', 'v22');
57 | INSERT INTO `a` VALUES (23, 'w', 'v23');
58 | INSERT INTO `a` VALUES (24, 'x', 'v24');
59 | INSERT INTO `a` VALUES (25, 'y', 'v25');
60 | INSERT INTO `a` VALUES (26, 'z', 'v26');
61 | COMMIT;
62 |
63 | -- ----------------------------
64 | -- Table structure for b
65 | -- ----------------------------
66 | DROP TABLE IF EXISTS `b`;
67 | CREATE TABLE `b` (
68 | `id` int(11) NOT NULL AUTO_INCREMENT,
69 | `name` varchar(255) NOT NULL,
70 | `value` varchar(255) DEFAULT NULL,
71 | PRIMARY KEY (`id`) USING BTREE
72 | ) ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=latin1;
73 |
74 | -- ----------------------------
75 | -- Records of b
76 | -- ----------------------------
77 | BEGIN;
78 | INSERT INTO `b` VALUES (1, 'a', 'v1');
79 | INSERT INTO `b` VALUES (2, 'a', 'v1');
80 | INSERT INTO `b` VALUES (3, 'a', 'v1');
81 | INSERT INTO `b` VALUES (4, 'd', 'v4');
82 | INSERT INTO `b` VALUES (5, 'e', 'v5');
83 | INSERT INTO `b` VALUES (6, 'f', 'v6');
84 | INSERT INTO `b` VALUES (7, 'g', 'v7');
85 | INSERT INTO `b` VALUES (8, 'h', 'v8');
86 | INSERT INTO `b` VALUES (9, 'i', 'v9');
87 | INSERT INTO `b` VALUES (10, 'j', 'v10');
88 | INSERT INTO `b` VALUES (11, 'k', 'v11');
89 | INSERT INTO `b` VALUES (12, 'l', 'v12');
90 | INSERT INTO `b` VALUES (13, 'm', 'v13');
91 | INSERT INTO `b` VALUES (14, 'n', 'v14');
92 | INSERT INTO `b` VALUES (15, 'o', 'v15');
93 | INSERT INTO `b` VALUES (16, 'p', 'v16');
94 | INSERT INTO `b` VALUES (17, 'q', 'v17');
95 | INSERT INTO `b` VALUES (18, 'r', 'v18');
96 | INSERT INTO `b` VALUES (19, 's', 'v19');
97 | INSERT INTO `b` VALUES (20, 't', 'v20');
98 | INSERT INTO `b` VALUES (21, 'u', 'v21');
99 | INSERT INTO `b` VALUES (22, 'v', 'v22');
100 | INSERT INTO `b` VALUES (23, 'w', 'v23');
101 | INSERT INTO `b` VALUES (24, 'x', 'v24');
102 | INSERT INTO `b` VALUES (25, 'y', 'v25');
103 | INSERT INTO `b` VALUES (26, 'z', 'v26');
104 | COMMIT;
105 |
106 | -- ----------------------------
107 | -- Table structure for c
108 | -- ----------------------------
109 | DROP TABLE IF EXISTS `c`;
110 | CREATE TABLE `c` (
111 | `id` int(11) NOT NULL AUTO_INCREMENT,
112 | `name` varchar(255) NOT NULL,
113 | `value` varchar(255) DEFAULT NULL,
114 | PRIMARY KEY (`id`) USING BTREE
115 | ) ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=latin1;
116 |
117 | -- ----------------------------
118 | -- Records of c
119 | -- ----------------------------
120 | BEGIN;
121 | INSERT INTO `c` VALUES (1, 'a', 'v1');
122 | INSERT INTO `c` VALUES (2, 'a', 'v1');
123 | INSERT INTO `c` VALUES (3, 'a', 'v1');
124 | INSERT INTO `c` VALUES (4, 'd', 'v4');
125 | INSERT INTO `c` VALUES (5, 'e', 'v5');
126 | INSERT INTO `c` VALUES (6, 'f', 'v6');
127 | INSERT INTO `c` VALUES (7, 'g', 'v7');
128 | INSERT INTO `c` VALUES (8, 'h', 'v8');
129 | INSERT INTO `c` VALUES (9, 'i', 'v9');
130 | INSERT INTO `c` VALUES (10, 'j', 'v10');
131 | INSERT INTO `c` VALUES (11, 'k', 'v11');
132 | INSERT INTO `c` VALUES (12, 'l', 'v12');
133 | INSERT INTO `c` VALUES (13, 'm', 'v13');
134 | INSERT INTO `c` VALUES (14, 'n', 'v14');
135 | INSERT INTO `c` VALUES (15, 'o', 'v15');
136 | INSERT INTO `c` VALUES (16, 'p', 'v16');
137 | INSERT INTO `c` VALUES (17, 'q', 'v17');
138 | INSERT INTO `c` VALUES (18, 'r', 'v18');
139 | INSERT INTO `c` VALUES (19, 's', 'v19');
140 | INSERT INTO `c` VALUES (20, 't', 'v20');
141 | INSERT INTO `c` VALUES (21, 'u', 'v21');
142 | INSERT INTO `c` VALUES (22, 'v', 'v22');
143 | INSERT INTO `c` VALUES (23, 'w', 'v23');
144 | INSERT INTO `c` VALUES (24, 'x', 'v24');
145 | INSERT INTO `c` VALUES (25, 'y', 'v25');
146 | INSERT INTO `c` VALUES (26, 'z', 'v26');
147 | COMMIT;
148 |
149 | SET FOREIGN_KEY_CHECKS = 1;
150 |
--------------------------------------------------------------------------------
/src/main/java/com/xiwh/paginator/interceptors/PaginatorExecuteHandler.java:
--------------------------------------------------------------------------------
1 | package com.xiwh.paginator.interceptors;
2 |
3 | import com.xiwh.paginator.Paginator;
4 | import com.xiwh.paginator.cache.MybatisMethodCache;
5 | import com.xiwh.paginator.sqlGenerator.DataBaseType;
6 | import com.xiwh.paginator.wrappers.NPlusOnePageWrapper;
7 | import com.xiwh.paginator.wrappers.NormalPageWrapper;
8 | import com.xiwh.paginator.wrappers.PageParamsWrapper;
9 | import com.xiwh.paginator.wrappers.PagingRowBounds;
10 | import org.apache.ibatis.cache.CacheKey;
11 | import org.apache.ibatis.executor.Executor;
12 | import org.apache.ibatis.mapping.BoundSql;
13 | import org.apache.ibatis.mapping.MappedStatement;
14 | import org.apache.ibatis.mapping.ResultMap;
15 | import org.apache.ibatis.mapping.SqlCommandType;
16 | import org.apache.ibatis.plugin.*;
17 | import org.apache.ibatis.session.ResultHandler;
18 | import org.apache.ibatis.session.RowBounds;
19 | import org.springframework.beans.factory.annotation.Autowired;
20 | import org.springframework.beans.factory.annotation.Qualifier;
21 | import org.springframework.stereotype.Component;
22 |
23 | import java.lang.reflect.Field;
24 | import java.util.List;
25 |
26 |
27 | @Intercepts({
28 | @Signature(
29 | type = Executor.class,
30 | method = "query",
31 | args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
32 | ),
33 | @Signature(
34 | type = Executor.class,
35 | method = "query",
36 | args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
37 | })
38 | @Component
39 | public class PaginatorExecuteHandler extends BaseInterceptor {
40 |
41 | @Autowired
42 | @Qualifier("mybatisMethodCacheImpl")
43 | MybatisMethodCache methodCache;
44 |
45 |
46 | @Override
47 | public Object intercept(Invocation invocation) throws Throwable {
48 | MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
49 | removePagingInfoWrapper();
50 | if (mappedStatement.getSqlCommandType() != SqlCommandType.SELECT)
51 | return invocation.proceed();
52 | MybatisMethodCache.MethodInfo methodInfo = methodCache.getValidMethod(mappedStatement);
53 | if (methodInfo == null) {
54 | return invocation.proceed();
55 | }
56 |
57 | RowBounds rowBounds = (RowBounds) invocation.getArgs()[2];
58 | PageParamsWrapper pageParams = null;
59 | PagingRowBounds pagingRowBounds = null;
60 | // When no RowBounds parameter is passed in
61 | if (rowBounds.getOffset() == RowBounds.NO_ROW_OFFSET && rowBounds.getLimit() == RowBounds.NO_ROW_LIMIT) {
62 | if (methodInfo.getPaginatorType() == MybatisMethodCache.TYPE_NORMAL) {
63 | if (methodInfo.getNormalPaginator().auto()) {
64 | Paginator.autoInjectFromRequest();
65 | }
66 | pageParams = Paginator.readPageParams();
67 | if (pageParams == null) {
68 | throw new RuntimeException("Please actively set paging parameters!");
69 | }
70 | pagingRowBounds = new PagingRowBounds(
71 | pageParams,
72 | methodInfo.getNormalPaginator().startOffset()
73 | );
74 | } else if (methodInfo.getPaginatorType() == MybatisMethodCache.TYPE_N_PLUS_ONE) {
75 | if (methodInfo.getNPlusOnePaginator().auto()) {
76 | Paginator.autoInjectFromRequest();
77 | }
78 | pageParams = Paginator.readPageParams();
79 | if (pageParams == null) {
80 | throw new RuntimeException("Please actively set paging parameters!");
81 | }
82 | pagingRowBounds = new PagingRowBounds(
83 | pageParams,
84 | methodInfo.getNPlusOnePaginator().startOffset()
85 | );
86 | }
87 | } else {
88 | if (rowBounds instanceof PagingRowBounds) {
89 | pagingRowBounds = (PagingRowBounds) rowBounds;
90 | } else {
91 | pagingRowBounds = new PagingRowBounds(rowBounds);
92 | }
93 | }
94 |
95 | //Modify rowBounds, For compatibility with mybatis caching mechanism
96 | //Compatible with mybatis logical paging
97 | invocation.getArgs()[2] = createFixedLimitRowBounds(pagingRowBounds.getLimit());
98 | PagingInfoWrapper pagingInfoWrapper = createPaging();
99 | pagingInfoWrapper.methodInfo = methodInfo;
100 | pagingInfoWrapper.mappedStatement = (MappedStatement) invocation.getArgs()[0];
101 | pagingInfoWrapper.executor = (Executor) invocation.getTarget();
102 | pagingInfoWrapper.pagingRowBounds = pagingRowBounds;
103 |
104 | //Get database type
105 | DataBaseType dbType = getDataBaseType(mappedStatement);
106 | // DataBaseType dbType = DataBaseType.MYSQL;
107 | pagingInfoWrapper.dataBaseType = dbType;
108 |
109 | List resultMaps = mappedStatement.getResultMaps();
110 | ResultMap resultMap = resultMaps.get(0);
111 | if(
112 | NormalPageWrapper.class.isAssignableFrom(resultMap.getType()) ||
113 | NPlusOnePageWrapper.class.isAssignableFrom(resultMap.getType())){
114 | Field field = ResultMap.class.getDeclaredField("type");
115 | field.setAccessible(true);
116 | field.set(resultMap, methodInfo.getGenericReturnClass());
117 | }
118 |
119 |
120 | Object proceed = invocation.proceed();
121 | return proceed;
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/src/main/java/com/xiwh/paginator/cache/impl/CountResultCacheImpl.java:
--------------------------------------------------------------------------------
1 | package com.xiwh.paginator.cache.impl;
2 |
3 | import com.xiwh.paginator.cache.CountResultCache;
4 | import org.apache.ibatis.cache.CacheKey;
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 | import org.springframework.stereotype.Component;
8 |
9 | import javax.annotation.PostConstruct;
10 | import java.util.*;
11 | import java.util.concurrent.Callable;
12 | import java.util.concurrent.locks.Lock;
13 | import java.util.concurrent.locks.ReentrantReadWriteLock;
14 |
15 | @Component("countResultCacheImpl")
16 | public class CountResultCacheImpl implements CountResultCache {
17 |
18 | private static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
19 | private static Lock r = lock.readLock();
20 | private static Lock w = lock.writeLock();
21 | private Map cacheMap = new HashMap<>();
22 | private PriorityQueue cacheQueue;
23 |
24 | @PostConstruct
25 | private void init(){
26 | cacheQueue = new PriorityQueue<>();
27 | }
28 |
29 | @Override
30 | public int size(){
31 | return cacheMap.size();
32 | }
33 |
34 | @Override
35 | public void startClear(){
36 | w.lock();
37 | long time = System.currentTimeMillis();
38 | try {
39 | //Clear expired cache
40 | CacheInfo oldCacheInfo = cacheQueue.peek();
41 | while (oldCacheInfo != null) {
42 | if (time >= oldCacheInfo.expireTime) {
43 | cacheMap.remove(oldCacheInfo.cacheKey);
44 | cacheQueue.poll();
45 | oldCacheInfo = cacheQueue.peek();
46 | } else {
47 | break;
48 | }
49 | }
50 | }finally {
51 | w.unlock();
52 | }
53 | }
54 |
55 | @Override
56 | public int getCachedCount(CacheKey cacheKey){
57 | r.lock();
58 | long time = System.currentTimeMillis();
59 | try {
60 | CacheInfo cacheInfo = cacheMap.get(cacheKey);
61 | //If there is a cache and within the valid time, return directly
62 | if(cacheInfo != null && time < cacheInfo.expireTime){
63 | return cacheInfo.count;
64 | }else{
65 | return -1;
66 | }
67 | }finally {
68 | r.unlock();
69 | }
70 | }
71 |
72 | @Override
73 | public boolean removeCache(CacheKey cacheKey) {
74 | w.lock();
75 | try{
76 | cacheMap.remove(cacheKey);
77 | return cacheQueue.remove(cacheKey);
78 | }finally {
79 | w.unlock();
80 | }
81 | }
82 |
83 | @Override
84 | public int getCacheCount(CacheKey key, Callable countCallable, int expireTime, boolean forceUpdate) throws Exception {
85 | if(forceUpdate){
86 | w.lock();
87 | try{
88 | long time = System.currentTimeMillis();
89 | int count = countCallable.call();
90 | CacheInfo cacheInfo = new CacheInfo(count, key, time+expireTime);
91 | CacheInfo oldCache = cacheMap.remove(key);
92 | if(oldCache!=null){
93 | cacheQueue.remove(oldCache);
94 | }
95 | cacheMap.put(key, cacheInfo);
96 | cacheQueue.offer(cacheInfo);
97 | return count;
98 | }finally {
99 | w.unlock();
100 | }
101 | }
102 | r.lock();
103 | long time = System.currentTimeMillis();
104 | try {
105 | CacheInfo cacheInfo = cacheMap.get(key);
106 | //If there is a cache and within the valid time, return directly
107 | if(cacheInfo != null && time < cacheInfo.expireTime){
108 | return cacheInfo.count;
109 | }
110 | }finally {
111 | r.unlock();
112 | }
113 | w.lock();
114 | time = System.currentTimeMillis();
115 | try{
116 | //Double check
117 | CacheInfo cacheInfo = cacheMap.get(key);
118 | if(cacheInfo != null && time >= cacheInfo.expireTime){
119 | startClear();
120 | cacheInfo = null;
121 | }
122 | if(cacheInfo != null){
123 | return cacheInfo.count;
124 | }
125 |
126 | int count = countCallable.call();
127 | cacheInfo = new CacheInfo(count, key, time+expireTime);
128 | cacheMap.put(key, cacheInfo);
129 | cacheQueue.offer(cacheInfo);
130 |
131 | return count;
132 | }finally {
133 | w.unlock();
134 | }
135 | }
136 |
137 | public void printCaches(){
138 | synchronized (this){
139 | StringBuilder builder = new StringBuilder("{CountCache["+cacheQueue.size()+"]}[");
140 | PriorityQueue queue = new PriorityQueue(this.cacheQueue);
141 | for(int i=0;!queue.isEmpty();i++){
142 | if(i!=0){
143 | builder.append(",");
144 | }
145 | CacheInfo cacheInfo = queue.poll();
146 | builder.append("\r\n\t");
147 | builder.append(cacheInfo);
148 | }
149 | builder.append("\r\n]");
150 | System.out.println(builder);
151 | }
152 | }
153 |
154 | private static class CacheInfo implements Comparable{
155 | private int count;
156 | private CacheKey cacheKey;
157 | private long expireTime;
158 |
159 | public CacheInfo(int count, CacheKey cacheKey, long expireTime){
160 | this.count = count;
161 | this.cacheKey = cacheKey;
162 | this.expireTime = expireTime;
163 | }
164 |
165 | @Override
166 | public String toString() {
167 | final StringBuffer sb = new StringBuffer("CacheInfo{");
168 | sb.append("count=").append(count);
169 | sb.append(", cacheKey=").append(cacheKey);
170 | sb.append(", expireTime=").append(expireTime);
171 | sb.append('}');
172 | return sb.toString();
173 | }
174 |
175 | @Override
176 | public int compareTo(Object o) {
177 | CacheInfo cacheInfo = (CacheInfo)o;
178 | return (int) (this.expireTime - cacheInfo.expireTime);
179 | }
180 | }
181 | }
182 |
--------------------------------------------------------------------------------
/src/main/java/com/xiwh/paginator/cache/impl/MybatisMethodCacheImpl.java:
--------------------------------------------------------------------------------
1 | package com.xiwh.paginator.cache.impl;
2 |
3 | import com.xiwh.paginator.annotations.NPlusOnePaginator;
4 | import com.xiwh.paginator.annotations.NormalPaginator;
5 | import com.xiwh.paginator.cache.MybatisMethodCache;
6 | import com.xiwh.paginator.utils.StringUtils;
7 | import org.apache.ibatis.mapping.MappedStatement;
8 | import org.slf4j.Logger;
9 | import org.slf4j.LoggerFactory;
10 | import org.springframework.stereotype.Component;
11 |
12 | import java.lang.annotation.Annotation;
13 | import java.lang.reflect.Method;
14 | import java.lang.reflect.ParameterizedType;
15 | import java.util.Map;
16 | import java.util.concurrent.ConcurrentHashMap;
17 |
18 | @Component("mybatisMethodCacheImpl")
19 | public class MybatisMethodCacheImpl implements MybatisMethodCache {
20 |
21 | private final static MethodInfoImpl INVALID_METHOD = new MethodInfoImpl(null);
22 |
23 | private static final Logger logger = LoggerFactory.getLogger(MybatisMethodCacheImpl.class);
24 |
25 | private Map methodInfoCache = new ConcurrentHashMap<>();
26 |
27 | public MybatisMethodCache.MethodInfo getValidMethod(MappedStatement mappedStatement) {
28 | String id = mappedStatement.getId();
29 | MethodInfo methodInfo = methodInfoCache.get(id);
30 | if(methodInfo==null){
31 | int temp = id.lastIndexOf(".");
32 | String className = id.substring(0, temp);
33 | String methodName = id.substring(temp+1);
34 | Method method = _getMethod(className, methodName);
35 | if(method==null){
36 | methodInfoCache.put(id, INVALID_METHOD);
37 | }else{
38 | //Double check
39 | synchronized (method) {
40 | methodInfo = methodInfoCache.get(id);
41 | if(methodInfo==null) {
42 | methodInfo = _collectInfo(className, method);
43 | methodInfoCache.put(id, methodInfo);
44 | }
45 | }
46 | }
47 | }
48 |
49 | if(methodInfo.getMethod()==null){
50 | return null;
51 | }
52 | return methodInfo;
53 | }
54 |
55 |
56 | private MethodInfo _collectInfo(String className, Method method){
57 | Annotation[] annotations = method.getAnnotations();
58 | MethodInfoImpl methodInfo = INVALID_METHOD;
59 | for(int i=0;i
2 |
4 | 4.0.0
5 |
6 |
7 | org.springframework.boot
8 | spring-boot-starter-parent
9 | 2.3.3.RELEASE
10 |
11 |
12 |
13 |
14 | com.github.xiwh
15 | mybatis-paginator
16 | 1.0.2
17 | springboot-mybatis-paginator
18 | jar
19 | A powerful and easy-to-use mybatis physical paginator
20 | https://github.com/xiwh/springboot-mybatis-paginator
21 |
22 |
23 |
24 | 1.8
25 |
26 |
27 |
28 | scm:git:git://github.com/xiwh/springboot-mybatis-paginator.git
29 | scm:git:ssh://github.com/xiwh/springboot-mybatis-paginator.git
30 | https://github.com/xiwh/springboot-mybatis-paginator
31 |
32 |
33 |
34 | ossrh
35 |
36 | true
37 |
38 |
39 |
40 |
41 | org.apache.maven.plugins
42 | maven-source-plugin
43 | 2.2.1
44 |
45 |
46 | attach-sources
47 |
48 | jar-no-fork
49 |
50 |
51 |
52 |
53 |
54 | org.apache.maven.plugins
55 | maven-javadoc-plugin
56 | 2.9.1
57 |
58 |
59 | attach-javadocs
60 |
61 | jar
62 |
63 |
64 |
65 |
66 |
67 | org.apache.maven.plugins
68 | maven-gpg-plugin
69 | 1.5
70 |
71 |
72 | sign-artifacts
73 | verify
74 |
75 | sign
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | ossrh
85 | https://oss.sonatype.org/content/repositories/snapshots
86 |
87 |
88 | ossrh
89 | https://oss.sonatype.org/service/local/staging/deploy/maven2/
90 |
91 |
92 |
93 |
94 |
95 |
96 | org.mybatis.spring.boot
97 | mybatis-spring-boot-starter
98 | 2.1.3
99 | provided
100 |
101 |
102 | com.alibaba
103 | druid
104 | 1.1.23
105 |
106 |
107 |
108 |
109 | org.springframework.boot
110 | spring-boot-starter-web
111 | 2.3.3.RELEASE
112 | true
113 |
114 |
115 | org.springframework.boot
116 | spring-boot-starter-tomcat
117 | 2.3.3.RELEASE
118 | true
119 |
120 |
121 |
122 |
123 |
124 | com.microsoft.sqlserver
125 | mssql-jdbc
126 | test
127 |
128 |
129 | mysql
130 | mysql-connector-java
131 | test
132 |
133 |
134 | org.postgresql
135 | postgresql
136 | test
137 |
138 |
139 | org.springframework.boot
140 | spring-boot-starter-test
141 | test
142 |
143 |
144 | org.junit.vintage
145 | junit-vintage-engine
146 |
147 |
148 |
149 |
150 | junit
151 | junit
152 | 4.12
153 | test
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 | maven-compiler-plugin
165 | 2.3.2
166 |
167 | 1.8
168 | 1.8
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
--------------------------------------------------------------------------------
/src/main/java/com/xiwh/paginator/interceptors/BaseInterceptor.java:
--------------------------------------------------------------------------------
1 | package com.xiwh.paginator.interceptors;
2 |
3 | import com.xiwh.paginator.cache.MybatisMethodCache;
4 | import com.xiwh.paginator.sqlGenerator.DataBaseType;
5 | import com.xiwh.paginator.sqlGenerator.PaginatorSqlGenerator;
6 | import com.xiwh.paginator.wrappers.PagingRowBounds;
7 | import org.apache.ibatis.cache.CacheKey;
8 | import org.apache.ibatis.executor.Executor;
9 | import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator;
10 | import org.apache.ibatis.executor.statement.StatementHandler;
11 | import org.apache.ibatis.logging.Log;
12 | import org.apache.ibatis.logging.jdbc.ConnectionLogger;
13 | import org.apache.ibatis.mapping.*;
14 | import org.apache.ibatis.plugin.Interceptor;
15 | import org.apache.ibatis.plugin.Plugin;
16 | import org.apache.ibatis.reflection.MetaObject;
17 | import org.apache.ibatis.session.Configuration;
18 | import org.apache.ibatis.session.RowBounds;
19 | import org.apache.ibatis.type.TypeHandlerRegistry;
20 | import org.slf4j.Logger;
21 | import org.slf4j.LoggerFactory;
22 |
23 | import java.sql.*;
24 | import java.util.List;
25 | import java.util.Map;
26 | import java.util.Properties;
27 | import java.util.concurrent.ConcurrentHashMap;
28 |
29 | public abstract class BaseInterceptor implements Interceptor {
30 |
31 | private static final ThreadLocal mThreadLocal = new ThreadLocal();
32 | private static final ThreadLocal mThreadLocal2 = new ThreadLocal();
33 | private static final Map dbTypeMap = new ConcurrentHashMap();
34 |
35 | private static final Logger logger = LoggerFactory.getLogger(BaseInterceptor.class);
36 |
37 | public PagingInfoWrapper createPaging(){
38 | PagingInfoWrapper pagingInfoWrapper = mThreadLocal.get();
39 | if(pagingInfoWrapper==null){
40 | pagingInfoWrapper = new PagingInfoWrapper();
41 | mThreadLocal.set(pagingInfoWrapper);
42 | }else{
43 | pagingInfoWrapper.reset();
44 | }
45 | return pagingInfoWrapper;
46 | }
47 |
48 | public PagingInfoWrapper getPagingInfoWrapper(){
49 | PagingInfoWrapper pagingInfoWrapper = mThreadLocal.get();
50 | return pagingInfoWrapper;
51 | }
52 |
53 | public void removePagingInfoWrapper(){
54 | mThreadLocal.remove();
55 | }
56 |
57 | public DataBaseType getDataBaseType(MappedStatement mappedStatement){
58 | String id = mappedStatement.getDatabaseId();
59 | id = id==null?"default":id;
60 | DataBaseType dataBaseType = dbTypeMap.get(id);
61 | if (dataBaseType == null) {
62 | synchronized (dbTypeMap) {
63 | dataBaseType = dbTypeMap.get(id);
64 | if (dataBaseType == null) {
65 | try {
66 | dataBaseType = DataBaseType.findByURL(
67 | getConnection().getMetaData().getURL()
68 | );
69 | dbTypeMap.put(id, dataBaseType);
70 | } catch (Exception e) {
71 | throw new RuntimeException("Failed to get database type!", e);
72 | }
73 | }
74 | }
75 | }
76 | return dataBaseType;
77 | }
78 |
79 | /**
80 | * Compatible with mybatis logical paging
81 | */
82 | public RowBounds createFixedLimitRowBounds(int limit){
83 | FixedLimitRowBounds fixedLimitRowBounds = mThreadLocal2.get();
84 | if(fixedLimitRowBounds ==null){
85 | fixedLimitRowBounds = new FixedLimitRowBounds(limit+1);
86 | mThreadLocal2.set(fixedLimitRowBounds);
87 | }else{
88 | fixedLimitRowBounds.setLimit(limit+1);
89 | }
90 | return fixedLimitRowBounds;
91 | }
92 |
93 | private static class FixedLimitRowBounds extends RowBounds{
94 | private int limit;
95 |
96 | private FixedLimitRowBounds(int limit){
97 | this.limit = limit;
98 | }
99 |
100 | public void setLimit(int limit) {
101 | this.limit = limit;
102 | }
103 |
104 | @Override
105 | public int getLimit() {
106 | return limit;
107 | }
108 |
109 | @Override
110 | public int getOffset() {
111 | return 0;
112 | }
113 | }
114 |
115 | public static class PagingInfoWrapper{
116 | MybatisMethodCache.MethodInfo methodInfo;
117 | PagingRowBounds pagingRowBounds;
118 | PaginatorSqlGenerator sqlGenerator;
119 | int count;
120 | MappedStatement mappedStatement;
121 | Executor executor;
122 | DataBaseType dataBaseType;
123 | BoundSql boundSql;
124 | StatementHandler statementHandler;
125 | boolean counting = false;
126 |
127 | public void reset(){
128 | methodInfo = null;
129 | pagingRowBounds = null;
130 | sqlGenerator = null;
131 | count = 0;
132 | mappedStatement = null;
133 | executor = null;
134 | dataBaseType = null;
135 | boundSql = null;
136 | statementHandler = null;
137 | counting = false;
138 | }
139 | }
140 |
141 | protected Connection getConnection() throws SQLException {
142 | PagingInfoWrapper pagingInfoWrapper = getPagingInfoWrapper();
143 | Log statementLog = pagingInfoWrapper.mappedStatement.getStatementLog();
144 | Connection connection = pagingInfoWrapper.executor.getTransaction().getConnection();
145 | if (statementLog.isDebugEnabled()) {
146 | return ConnectionLogger.newInstance(connection, statementLog, 0);
147 | } else {
148 | return connection;
149 | }
150 | }
151 |
152 | public PreparedStatement createCountStatement(boolean optimization) throws SQLException
153 | {
154 | PagingInfoWrapper pagingInfoWrapper = getPagingInfoWrapper();
155 | String sql = optimization ?
156 | pagingInfoWrapper.sqlGenerator.toOptimizedCountSql()
157 | : pagingInfoWrapper.sqlGenerator.toCountSql();
158 |
159 | if(logger.isDebugEnabled()) {
160 | logger.debug(String.format("Custom count preparing: %s", sql));
161 | }
162 |
163 | Connection connection = getConnection();
164 | if (pagingInfoWrapper.mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
165 | String[] keyColumnNames = pagingInfoWrapper.mappedStatement.getKeyColumns();
166 | if (keyColumnNames == null) {
167 | return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
168 | } else {
169 | return connection.prepareStatement(sql, keyColumnNames);
170 | }
171 | } else if (pagingInfoWrapper.mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
172 | return connection.prepareStatement(sql);
173 | } else {
174 | return connection.prepareStatement(
175 | sql,
176 | pagingInfoWrapper.mappedStatement.getResultSetType().getValue(),
177 | ResultSet.CONCUR_READ_ONLY
178 | );
179 | }
180 | }
181 |
182 | public CacheKey createCountCacheKey(){
183 | PagingInfoWrapper pagingInfoWrapper = getPagingInfoWrapper();
184 | CacheKey cacheKey = new CacheKey();
185 | cacheKey.update(pagingInfoWrapper.mappedStatement.getId());
186 | cacheKey.update(pagingInfoWrapper.boundSql.getSql());
187 | List parameterMappings = pagingInfoWrapper.boundSql.getParameterMappings();
188 | TypeHandlerRegistry typeHandlerRegistry = pagingInfoWrapper.mappedStatement.getConfiguration().getTypeHandlerRegistry();
189 | Object parameterObject = pagingInfoWrapper.boundSql.getParameterObject();
190 | Configuration configuration = pagingInfoWrapper.mappedStatement.getConfiguration();
191 | // mimic DefaultParameterHandler logic
192 | for (ParameterMapping parameterMapping : parameterMappings) {
193 | if (parameterMapping.getMode() != ParameterMode.OUT) {
194 | Object value;
195 | String propertyName = parameterMapping.getProperty();
196 | if (pagingInfoWrapper.boundSql.hasAdditionalParameter(propertyName)) {
197 | value = pagingInfoWrapper.boundSql.getAdditionalParameter(propertyName);
198 | } else if (parameterObject == null) {
199 | value = null;
200 | } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
201 | value = parameterObject;
202 | } else {
203 | MetaObject metaObject = configuration.newMetaObject(parameterObject);
204 | value = metaObject.getValue(propertyName);
205 | }
206 | cacheKey.update(value);
207 | }
208 | }
209 | return cacheKey;
210 | }
211 |
212 | @Override
213 | public Object plugin(Object target) {
214 | return Plugin.wrap(target, this);
215 | }
216 |
217 | @Override
218 | public void setProperties(Properties properties) {
219 | }
220 | }
221 |
--------------------------------------------------------------------------------
/src/main/java/com/xiwh/paginator/interceptors/PaginatorResultHandler.java:
--------------------------------------------------------------------------------
1 | package com.xiwh.paginator.interceptors;
2 |
3 | import com.xiwh.paginator.Paginator;
4 | import com.xiwh.paginator.annotations.NormalPaginator;
5 | import com.xiwh.paginator.cache.CountResultCache;
6 | import com.xiwh.paginator.cache.MybatisMethodCache;
7 | import com.xiwh.paginator.cache.impl.CountResultCacheImpl;
8 | import com.xiwh.paginator.cache.impl.MybatisMethodCacheImpl;
9 | import com.xiwh.paginator.wrappers.NPlusOnePage;
10 | import com.xiwh.paginator.wrappers.NPlusOnePageWrapper;
11 | import com.xiwh.paginator.wrappers.NormalPageWrapper;
12 | import com.xiwh.paginator.sqlGenerator.PaginatorSqlGenerator;
13 | import com.xiwh.paginator.utils.StringUtils;
14 | import com.xiwh.paginator.wrappers.PagingRowBounds;
15 | import org.apache.ibatis.cache.CacheKey;
16 | import org.apache.ibatis.executor.Executor;
17 | import org.apache.ibatis.executor.resultset.ResultSetHandler;
18 | import org.apache.ibatis.executor.statement.StatementHandler;
19 | import org.apache.ibatis.mapping.BoundSql;
20 | import org.apache.ibatis.mapping.MappedStatement;
21 | import org.apache.ibatis.plugin.*;
22 | import org.apache.ibatis.session.RowBounds;
23 | import org.slf4j.Logger;
24 | import org.slf4j.LoggerFactory;
25 | import org.springframework.beans.factory.annotation.Autowired;
26 | import org.springframework.beans.factory.annotation.Qualifier;
27 | import org.springframework.stereotype.Component;
28 |
29 | import java.sql.*;
30 | import java.util.ArrayList;
31 | import java.util.Iterator;
32 | import java.util.List;
33 | import java.util.concurrent.Callable;
34 |
35 |
36 | @Intercepts({
37 | @Signature(
38 | type = ResultSetHandler.class,
39 | method = "handleResultSets",
40 | args = {Statement.class}
41 | )
42 | })
43 | @Component
44 | public class PaginatorResultHandler extends BaseInterceptor {
45 |
46 | private static final Logger logger = LoggerFactory.getLogger(PaginatorLimitHandler.class);
47 |
48 | @Autowired
49 | @Qualifier("mybatisMethodCacheImpl")
50 | MybatisMethodCache methodCache;
51 | @Autowired
52 | @Qualifier("countResultCacheImpl")
53 | CountResultCache countResultCache;
54 |
55 | private ThreadLocal listThreadLocal = new ThreadLocal<>();
56 |
57 | @Override
58 | public Object intercept(Invocation invocation) throws Throwable {
59 | PagingInfoWrapper pagingInfoWrapper = getPagingInfoWrapper();
60 | if(pagingInfoWrapper==null){
61 | return invocation.proceed();
62 | }else if(pagingInfoWrapper.counting){
63 | return invocation.proceed();
64 | }
65 |
66 | BoundSql boundSql = pagingInfoWrapper.boundSql;
67 | StatementHandler statementHandler = pagingInfoWrapper.statementHandler;
68 | MybatisMethodCache.MethodInfo methodInfo = pagingInfoWrapper.methodInfo;
69 | List result = (List) invocation.proceed();
70 | PagingRowBounds pagingRowBounds = pagingInfoWrapper.pagingRowBounds;
71 | if(methodInfo.getPaginatorType() == MybatisMethodCache.TYPE_NORMAL) {
72 | NormalPaginator normalPaginator = methodInfo.getNormalPaginator();
73 | // The count result can be calculated on the last page
74 | // If the count result can be calculated directly, it is forced to update
75 | boolean canGetCountResult = result.size()!=0 && result.size() < pagingRowBounds.getLimit();
76 | Callable countCall = () -> {
77 | // The count result can be calculated on the last page
78 | if(canGetCountResult){
79 | int count = pagingRowBounds.getOffset() + result.size();
80 | if(logger.isDebugEnabled()) {
81 | logger.debug(String.format("Enable last page optimization"));
82 | }
83 | return count;
84 | }
85 | String customCountMapperId = pagingInfoWrapper.methodInfo.getCustomCountMapperId();
86 | pagingInfoWrapper.counting = true;
87 | try {
88 | //Auto count
89 | if (StringUtils.isEmpty(customCountMapperId)) {
90 | PreparedStatement preparedStatement = createCountStatement(methodInfo.getNormalPaginator().countOptimization());
91 | statementHandler.parameterize(preparedStatement);
92 | ResultSet executeQuery = preparedStatement.executeQuery();
93 | executeQuery.next();
94 | int tempCount = executeQuery.getInt(PaginatorSqlGenerator.COUNT_ALIAS);
95 | executeQuery.close();
96 | preparedStatement.close();
97 | return tempCount;
98 | }
99 | //Custom Count statement
100 | else {
101 | Executor executor = pagingInfoWrapper.executor;
102 | MappedStatement customCountMappedStatement = pagingInfoWrapper.mappedStatement
103 | .getConfiguration().getMappedStatement(
104 | customCountMapperId, false
105 | );
106 |
107 | if(logger.isDebugEnabled()) {
108 | logger.debug(String.format("Count preparing:: %s", boundSql));
109 | }
110 | List results = executor.query(
111 | customCountMappedStatement,
112 | boundSql.getParameterObject(),
113 | RowBounds.DEFAULT,
114 | Executor.NO_RESULT_HANDLER
115 | );
116 | return results.get(0);
117 | }
118 | }finally {
119 | pagingInfoWrapper.counting = false;
120 | }
121 | };
122 | if(methodInfo.getNormalPaginator().cache()) {
123 | //Create count cache key
124 | CacheKey cacheKey = createCountCacheKey();
125 |
126 | //Get count result
127 | int count = countResultCache.getCacheCount(
128 | cacheKey,
129 | countCall,
130 | pagingInfoWrapper.methodInfo.getNormalPaginator().cacheExpiryTime() * 1000,
131 | canGetCountResult || pagingRowBounds.isForceCounting()
132 | );
133 | pagingInfoWrapper.count = count;
134 | }else{
135 | pagingInfoWrapper.count = countCall.call();
136 | }
137 |
138 | if(logger.isInfoEnabled()) {
139 | logger.info(String.format("Paging count: %s", pagingInfoWrapper.count));
140 | }
141 |
142 | pagingRowBounds.setCount(pagingInfoWrapper.count);
143 | pagingRowBounds.setHasNext(pagingRowBounds.getCount()>pagingRowBounds.getEnd());
144 |
145 | Class returnClass = methodInfo.getReturnClass();
146 | if(NormalPageWrapper.class.isAssignableFrom(returnClass)) {
147 | NormalPageWrapper pageWrapper = (NormalPageWrapper) returnClass.newInstance();
148 | pageWrapper.init(
149 | result,
150 | pagingInfoWrapper.count,
151 | methodInfo.getNormalPaginator().startOffset(),
152 | pagingRowBounds.getPage(),
153 | pagingRowBounds.getLimit()
154 | );
155 | ArrayList arrayList = getArrayList();
156 | arrayList.add(pageWrapper);
157 | return arrayList;
158 | }else{
159 | return result;
160 | }
161 | }else if(methodInfo.getPaginatorType() == MybatisMethodCache.TYPE_N_PLUS_ONE){
162 | boolean hasNext = result.size() > pagingRowBounds.getLimit();
163 | if (hasNext) {
164 | result.remove(result.size() - 1);
165 | }
166 | pagingRowBounds.setHasNext(hasNext);
167 | Class returnClass = methodInfo.getReturnClass();
168 | if(NPlusOnePageWrapper.class.isAssignableFrom(returnClass)) {
169 | NPlusOnePage pageWrapper = (NPlusOnePage) methodInfo.getReturnClass().newInstance();
170 | pageWrapper.init(
171 | result,
172 | hasNext,
173 | pagingRowBounds.getPageOffset(),
174 | pagingRowBounds.getPage(), pagingRowBounds.getLimit()
175 | );
176 | ArrayList arrayList = getArrayList();
177 | arrayList.add(pageWrapper);
178 | return arrayList;
179 | }else{
180 | return result;
181 | }
182 | }else{
183 | return result;
184 | }
185 | }
186 |
187 | private ArrayList getArrayList(){
188 | ArrayList arrayList = listThreadLocal.get();
189 | if(arrayList==null){
190 | arrayList = new ArrayList(1);
191 | listThreadLocal.set(arrayList);
192 | }else{
193 | arrayList.clear();
194 | }
195 | return arrayList;
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/src/main/java/com/xiwh/paginator/sqlGenerator/PaginatorSqlGenerator.java:
--------------------------------------------------------------------------------
1 | package com.xiwh.paginator.sqlGenerator;
2 |
3 | import com.alibaba.druid.sql.SQLUtils;
4 | import com.alibaba.druid.sql.ast.SQLExpr;
5 | import com.alibaba.druid.sql.ast.SQLObject;
6 | import com.alibaba.druid.sql.ast.SQLStatement;
7 | import com.alibaba.druid.sql.ast.expr.*;
8 | import com.alibaba.druid.sql.ast.statement.*;
9 | import com.alibaba.druid.sql.dialect.mysql.parser.MySqlStatementParser;
10 | import com.alibaba.druid.sql.dialect.oracle.parser.OracleStatementParser;
11 | import com.alibaba.druid.sql.dialect.postgresql.parser.PGSQLStatementParser;
12 | import com.alibaba.druid.sql.dialect.sqlserver.parser.SQLServerStatementParser;
13 | import com.alibaba.druid.sql.parser.SQLStatementParser;
14 | import com.alibaba.druid.sql.visitor.SQLASTOutputVisitor;
15 | import com.xiwh.paginator.utils.StringUtils;
16 |
17 | import java.util.Iterator;
18 | import java.util.List;
19 |
20 | public class PaginatorSqlGenerator {
21 | public static final String COUNT_ALIAS = "p_count";
22 | public static final String TEMP_VAR_ALIAS = "p_temp_v";
23 | public static final String TEMP_TABLE_ALIAS = "p_temp_t";
24 | public static final String TEMP_TABLE2_ALIAS = "p_temp_t2";
25 | public static final String TEMP_ROWNUM_ALIAS = "p_temp_rn";
26 |
27 |
28 | SQLSelect rawSelect;
29 | DataBaseType dataBaseType;
30 |
31 | /**
32 | * @param sql query SQL
33 | * @param dataBaseType
34 | */
35 | public PaginatorSqlGenerator(String sql, DataBaseType dataBaseType){
36 | rawSelect = parserSelectSql(sql, dataBaseType);
37 | this.dataBaseType = dataBaseType;
38 | }
39 |
40 | /**
41 | * Analysis of SQL structure
42 | * @param sql
43 | * @param dataBaseType
44 | * @return
45 | */
46 | private static SQLSelect parserSelectSql(String sql, DataBaseType dataBaseType){
47 | SQLStatementParser sqlStatementParser = null;
48 | switch (dataBaseType){
49 | case MYSQL:
50 | sqlStatementParser = new MySqlStatementParser(sql);
51 | break;
52 | case ORACLE:
53 | sqlStatementParser = new OracleStatementParser(sql);
54 | break;
55 | case POSGRESQL:
56 | sqlStatementParser = new PGSQLStatementParser(sql);
57 | break;
58 | case SQLSERVER:
59 | sqlStatementParser = new SQLServerStatementParser(sql);
60 | break;
61 | }
62 | List statementList = sqlStatementParser.parseStatementList();
63 | SQLStatement sqlStatement = statementList.get(0);
64 | List sqls = sqlStatement.getChildren();
65 | SQLSelect sqlSelect = (SQLSelect) sqls.get(0);
66 | return sqlSelect;
67 | }
68 |
69 | public String toLimitSql(int offset, int limit){
70 | SQLSelect sqlSelect = rawSelect.clone();
71 | SQLSelectQuery selectQuery = sqlSelect.getQuery();
72 | SQLSelectQueryBlock selectQueryBlock = (SQLSelectQueryBlock) selectQuery;
73 | // Fuckkkkkkkkk oracle
74 | if(dataBaseType == DataBaseType.ORACLE){
75 | return String.format(
76 | "select * from ( select rownum %s,%s.* from ( %s ) %s where rownum <= %s ) %s where %s.%s > %s",
77 | TEMP_ROWNUM_ALIAS,
78 | TEMP_TABLE_ALIAS,
79 | toSQLString(sqlSelect, dataBaseType),
80 | TEMP_TABLE_ALIAS,
81 | offset+limit,
82 | TEMP_TABLE2_ALIAS,
83 | TEMP_TABLE2_ALIAS,
84 | TEMP_ROWNUM_ALIAS,
85 | offset
86 | );
87 | }else if(dataBaseType == DataBaseType.SQLSERVER){
88 | String sql = toSQLString(sqlSelect, dataBaseType);
89 | return String.format("%s OFFSET %s ROWS FETCH NEXT %s ROWS ONLY", sql, offset, limit);
90 | }else {
91 | selectQueryBlock.limit(limit, offset);
92 | return toSQLString(sqlSelect, dataBaseType);
93 | }
94 | }
95 |
96 |
97 | /**
98 | * Convert query SQL to count SQL
99 | * @return counted SQL
100 | */
101 | public String toCountSql(){
102 | SQLSelect sqlSelect = rawSelect.clone();
103 | SQLSelectQuery selectQuery = sqlSelect.getQuery();
104 | SQLSelectQueryBlock selectQueryBlock = (SQLSelectQueryBlock) selectQuery;
105 | //Remove order by
106 | selectQueryBlock.setOrderBy(null);
107 | SQLSelectGroupByClause groupByClause = selectQueryBlock.getGroupBy();
108 | //Group by needs to be wrapped
109 | if(groupByClause!=null&&!groupByClause.getItems().isEmpty()){
110 | //Replace all select with count
111 | List selectItems = selectQueryBlock.getSelectList();
112 | selectItems.clear();
113 | selectItems.add(new SQLSelectItem(new SQLIntegerExpr(1),TEMP_VAR_ALIAS));
114 | //Wrapping group SQL
115 | return String.format(
116 | "SELECT count(%s.%s) as %s FROM (%s) as %s",
117 | TEMP_TABLE_ALIAS,
118 | TEMP_VAR_ALIAS,
119 | COUNT_ALIAS,
120 | toSQLString(sqlSelect, dataBaseType),
121 | TEMP_TABLE_ALIAS
122 | );
123 | }
124 | else{
125 | //Replace all select with count
126 | List selectItems = selectQueryBlock.getSelectList();
127 | selectItems.clear();
128 | selectItems.add(new SQLSelectItem(new SQLAggregateExpr("count", null, new SQLIntegerExpr(1)),COUNT_ALIAS));
129 | return toSQLString(sqlSelect, dataBaseType);
130 | }
131 | }
132 |
133 | /**
134 | * Convert query SQL to optimized count SQL
135 | * What are the optimization cases:
136 | * 1.Clean invalid order by
137 | * 2.Clean invalid select
138 | * @return Optimized count SQL
139 | */
140 | public String toOptimizedCountSql(){
141 | SQLSelect sqlSelect = rawSelect.clone();
142 | SQLSelectQuery selectQuery = sqlSelect.getQuery();
143 | SQLSelectQueryBlock selectQueryBlock = (SQLSelectQueryBlock) selectQuery;
144 | String havingStr = null;
145 | //Remove order by
146 | selectQueryBlock.setOrderBy(null);
147 | List selectItems = selectQueryBlock.getSelectList();
148 | SQLSelectGroupByClause groupByClause = selectQueryBlock.getGroupBy();
149 | boolean needWrapUp = false;
150 | //Has groupBy or having
151 | if(groupByClause!=null){
152 | SQLExpr having = groupByClause.getHaving();
153 | if(!groupByClause.getItems().isEmpty()) {
154 | needWrapUp = true;
155 | }
156 | // If there is "having", "select" will be cleaned up based on the "having" reference
157 | if(having!=null){
158 | havingStr = having.toString();
159 | Iterator iterator = selectItems.iterator();
160 | while (iterator.hasNext()) {
161 | SQLSelectItem item = iterator.next();
162 | // Get field name
163 | String fieldName = item.getAlias();
164 | if(StringUtils.isEmpty(fieldName)){
165 | String fieldBody = item.toString();
166 | int dotIndex = fieldBody.lastIndexOf('.');
167 | if(dotIndex==-1){
168 | fieldName = fieldBody;
169 | }else{
170 | fieldName = fieldBody.substring(dotIndex+1);
171 | }
172 | }
173 | if (havingStr.contains(fieldName)) {
174 | continue;
175 | }
176 | iterator.remove();
177 | }
178 | selectItems.add(new SQLSelectItem(new SQLIntegerExpr(1),TEMP_VAR_ALIAS));
179 | }
180 | //Replace the group without having with count(distinct xxx)
181 | else{
182 | selectItems.clear();
183 | selectItems.add(
184 | new SQLSelectItem(
185 | new SQLIntegerExpr(1),TEMP_VAR_ALIAS
186 | )
187 | );
188 | selectQueryBlock.setGroupBy(null);
189 | }
190 | }else{
191 | // When there is no "having" or "group", directly replace "select"
192 | selectItems.clear();
193 | selectItems.add(new SQLSelectItem(new SQLAggregateExpr("count", null, new SQLIntegerExpr(1)),COUNT_ALIAS));
194 | }
195 | String newSql = toSQLString(sqlSelect, dataBaseType);
196 | //Wrapping group SQL
197 | if(needWrapUp){
198 | return String.format(
199 | "SELECT count(%s.%s) as %s FROM (%s) as %s",
200 | TEMP_TABLE_ALIAS,
201 | TEMP_VAR_ALIAS,
202 | COUNT_ALIAS,
203 | newSql,
204 | TEMP_TABLE_ALIAS
205 | );
206 | }else{
207 | return newSql;
208 | }
209 | }
210 |
211 | public static String toSQLString(SQLObject sqlObject, DataBaseType dataBaseType) {
212 | String sql = sqlObject.toString();
213 | return sql.replaceAll("[\\t\\n\\r]"," ");
214 | }
215 |
216 | }
217 |
--------------------------------------------------------------------------------
/src/test/java/com/xiwh/paginator/demo/cache/CountCacheTests.java:
--------------------------------------------------------------------------------
1 | package com.xiwh.paginator.demo.cache;
2 |
3 | import com.xiwh.paginator.TestApplication;
4 | import com.xiwh.paginator.cache.CountResultCache;
5 | import com.xiwh.paginator.cache.impl.CountResultCacheImpl;
6 | import org.apache.ibatis.cache.CacheKey;
7 | import org.junit.jupiter.api.Test;
8 | import org.junit.runner.RunWith;
9 | import org.springframework.beans.factory.annotation.Autowired;
10 | import org.springframework.boot.test.context.SpringBootTest;
11 | import org.springframework.test.context.ContextConfiguration;
12 | import org.springframework.test.context.junit4.SpringRunner;
13 |
14 | import java.util.concurrent.Callable;
15 | import java.util.concurrent.atomic.AtomicInteger;
16 |
17 | @SpringBootTest
18 | @RunWith(SpringRunner.class)
19 | @ContextConfiguration(classes = TestApplication.class)
20 | class CountCacheTests {
21 |
22 | @Autowired
23 | CountResultCacheImpl cache;
24 |
25 | @Test
26 | void cacheTest() throws Exception {
27 | int count = cache.getCacheCount(new CacheKey(new Object[]{"1"}), new Callable() {
28 |
29 | @Override
30 | public Integer call() throws Exception {
31 | return 1;
32 | }
33 | },1000, false);
34 | System.out.println("count1:"+count);
35 |
36 | count = cache.getCacheCount(new CacheKey(new Object[]{"1"}), new Callable() {
37 |
38 | @Override
39 | public Integer call() throws Exception {
40 | return 11;
41 | }
42 | },1000, false);
43 | System.out.println("count2:"+count);
44 | Thread.sleep(2000);
45 | count = cache.getCacheCount(new CacheKey(new Object[]{"1"}), new Callable() {
46 |
47 | @Override
48 | public Integer call() throws Exception {
49 | return 111;
50 | }
51 | },2000, false);
52 | System.out.println("count3:"+count);
53 | Thread.sleep(2100);
54 | count = cache.getCacheCount(new CacheKey(new Object[]{"1"}), new Callable() {
55 |
56 | @Override
57 | public Integer call() throws Exception {
58 | return 1111;
59 | }
60 | },2000, false);
61 | System.out.println("count4:"+count);
62 | }
63 |
64 | @Test
65 | void cacheTest2() throws Exception {
66 | int countA = cache.getCacheCount(new CacheKey(new Object[]{"a"}), new Callable() {
67 |
68 | @Override
69 | public Integer call() throws Exception {
70 | return 1;
71 | }
72 | },1000, false);
73 |
74 | int countB = cache.getCacheCount(new CacheKey(new Object[]{"b"}), new Callable() {
75 |
76 | @Override
77 | public Integer call() throws Exception {
78 | return 2;
79 | }
80 | },5000, false);
81 |
82 | int countC = cache.getCacheCount(new CacheKey(new Object[]{"c"}), new Callable() {
83 |
84 | @Override
85 | public Integer call() throws Exception {
86 | return 3;
87 | }
88 | },2999,false);
89 |
90 | int countD = cache.getCacheCount(new CacheKey(new Object[]{"d"}), new Callable() {
91 |
92 | @Override
93 | public Integer call() throws Exception {
94 | return 4;
95 | }
96 | },9999,false);
97 |
98 | cache.printCaches();
99 | }
100 |
101 | private Integer finishedThreads = 0;
102 |
103 | @Test
104 | void cacheTest3() throws Exception {
105 |
106 | for(int i=0;i<100;i++){
107 | Thread thread = new Thread(new Runnable() {
108 | @Override
109 | public void run() {
110 | try {
111 | for(int i = 0;i<10;i++) {
112 | final int temp= i;
113 | int countA = cache.getCacheCount(new CacheKey(new Object[]{"i-"+i}), new Callable() {
114 |
115 | @Override
116 | public Integer call() throws Exception {
117 | System.out.println("i-"+temp+" Executed,thread:"+Thread.currentThread().getName());
118 | return temp;
119 | }
120 | }, (temp+1)*1000, false);
121 | }
122 | synchronized (finishedThreads){
123 | finishedThreads++;
124 | if(finishedThreads==10){
125 | System.out.println("Finished:");
126 | cache.printCaches();
127 | }
128 | }
129 | } catch (Exception e) {
130 | e.printStackTrace();
131 | }
132 | }
133 | });
134 | thread.setName("thread-"+i);
135 | thread.start();
136 | }
137 |
138 | try{
139 | Thread.sleep(100);
140 | for(int j=0;j<10;j++){
141 | Thread.sleep(1000);
142 | cache.getCacheCount(new CacheKey(new Object[]{"i-"+j}), new Callable() {
143 |
144 | @Override
145 | public Integer call() throws Exception {
146 | return -1;
147 | }
148 | }, 1000, false);
149 | System.out.println((j+1)+" second later:");
150 | cache.printCaches();
151 |
152 | }
153 | }catch (Exception e){
154 | e.printStackTrace();
155 | }
156 |
157 | }
158 |
159 | @Test
160 | void cacheTest4() throws Exception {
161 |
162 | for(int i=0;i<10;i++){
163 | new Thread(new Runnable() {
164 | @Override
165 | public void run() {
166 | try {
167 | for(int i = 0;i<10;i++) {
168 | final int temp= i*1000;
169 | int countA = cache.getCacheCount(new CacheKey(new Object[]{"i-"+i}), new Callable() {
170 |
171 | @Override
172 | public Integer call() throws Exception {
173 | return temp;
174 | }
175 | }, temp+1000, false);
176 | }
177 | synchronized (finishedThreads){
178 | finishedThreads++;
179 | if(finishedThreads==10){
180 | System.out.println("Finished:");
181 | cache.printCaches();
182 | }
183 | }
184 | } catch (Exception e) {
185 | e.printStackTrace();
186 | }
187 | }
188 | }).start();
189 | }
190 |
191 | try{
192 | Thread.sleep(100);
193 | for(int j=0;j<10;j++){
194 | Thread.sleep(1000);
195 | cache.startClear();
196 | System.out.println((j+1)+" second later:");
197 | cache.printCaches();
198 |
199 | }
200 | }catch (Exception e){
201 | e.printStackTrace();
202 | }
203 |
204 | }
205 |
206 | AtomicInteger finishedNum = new AtomicInteger();
207 |
208 | @Test
209 | void cacheTest5() throws Exception {
210 | Object lock = new Object();
211 | System.out.println("Begin");
212 | long startTime = System.currentTimeMillis();
213 | for(int i=0;i<8;i++){
214 | long finalStartTime = startTime;
215 | Thread thread = new Thread(new Runnable() {
216 | @Override
217 | public void run() {
218 | for(int i=0;i<1000000;i++){
219 | final int num = finishedNum.addAndGet(1);
220 | try {
221 | cache.getCacheCount(new CacheKey(new Object[]{"i-" + num}), new Callable() {
222 |
223 | @Override
224 | public Integer call() throws Exception {
225 | // System.out.println("i-"+num+" Executed,thread:"+Thread.currentThread().getName());
226 | return num;
227 | }
228 | }, (int) (finalStartTime -System.currentTimeMillis()), false);
229 | }catch (Exception e){
230 | e.printStackTrace();
231 | }
232 | if(num == 8000000){
233 | System.out.println(String.format("Finished %s:", num));
234 | synchronized (lock){
235 | lock.notify();
236 | }
237 | }
238 | }
239 | }
240 | });
241 | thread.setName("thread-"+i);
242 | thread.start();
243 | }
244 | synchronized (lock) {
245 | lock.wait();
246 | System.out.println("Insert "+((System.currentTimeMillis()-startTime)/1000)+"s");
247 | startTime = System.currentTimeMillis();
248 | System.out.println("Size " + cache.size());
249 | cache.startClear();
250 | System.out.println("Clear "+((System.currentTimeMillis()-startTime)/1000)+"s");
251 | cache.printCaches();
252 |
253 | }
254 | }
255 | }
256 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Springboot-Mybatis-Paginator
2 | ============
3 | 
4 | 
5 |
6 | 它是一款基于Springboot的高效易用易扩展的Mybatis全自动物理分页插件,提供基础分页、N+1分页、无侵入式物理分页、微侵入式易用分页等方式
7 |
8 | Features
9 | --------
10 | * 即插即用,您甚至不需要添加任何配置,只需引入jar包即可直接使用
11 | * 普通分页(基于Count聚合统计的传统管理后台分页方式)
12 | * N+1分页(适用于移动端上拉加载的高效分页方式)
13 | * 全自动化生成Count和Limit语句,支持主流数据库方言,并提供自动优化Count的可选项(默认开启),支持包括GroupBy在内的Count语句自动生成,实现简单场景下的高效开发
14 | * 提供完全自定义Limit和Count语句的方案,覆盖一些复杂SQL需要手动写的场景
15 | * 提供Count结果缓存的可选项,适用对Count结果无强一致要求并且有一定性能要求的场景
16 | * 提供微侵入和无侵入两种使用方式
17 | * 提供完全自定义分页结构方案,可以做到快速无缝衔接到前端或已有项目中
18 |
19 | Supported databases
20 | --------
21 | * `mysql`
22 | * `mariadb`
23 | * `oracle`
24 | * `sqlserver>=2012`
25 | * `posgresql`
26 | * `sqlite`
27 |
28 | Setup
29 | --------
30 |
31 | Gradle:
32 | ```groovy
33 | compile 'com.github.xiwh:mybatis-paginator:1.0.2'
34 | ```
35 | Maven:
36 | ```xml
37 |
38 | com.github.xiwh
39 | mybatis-paginator
40 | 1.0.2
41 |
42 | ```
43 |
44 | 快速使用
45 | --------
46 | ***(必须)将"com.xiwh.paginator"加入到自动扫描路径下,如下***
47 | ```java
48 | @SpringBootApplication
49 | @ComponentScan({"com.xiwh.paginator","com.example.demo"})
50 | public class DemoApplication {
51 | public static void main(String[] args) {
52 | SpringApplication.run(DemoApplication.class, args);
53 | }
54 | }
55 | ```
56 | ***普通分页***
57 | ```java
58 | public interface SimplePagingMapper {
59 | @Select("SELECT * FROM a")
60 | @NormalPaginator()
61 | SimplePage select();
62 |
63 | public static void main(String[] args){
64 | SimplePagingMapper mapper = xxx;
65 | int page = 0;
66 | int size = 10;
67 | Paginator.paginate(page,size);
68 | SimplePage page = mapper.select();
69 | System.out.println(page);
70 | System.out.println(page.hasLast());
71 | System.out.println(page.hasNext());
72 | System.out.println(page.totalPage());
73 | System.out.println(page.total());
74 | System.out.println(page.size());
75 | System.out.println(page.list());
76 | System.out.println(page.toMap());
77 | for (Bean item:page) {
78 | item.setName(item.getId()+":"+item.getName());
79 | }
80 | }
81 | }
82 |
83 | ```
84 |
85 | ***N+1分页***
86 | ```java
87 | public interface NPlusOnePagingMapper {
88 | @Select("SELECT * FROM a")
89 | @NPlusOnePaginator
90 | NPlusOnePage select();
91 |
92 | public static void main(String[] args){
93 | NPlusOnePagingMapper mapper = xxx;
94 | Paginator.paginate(0,10);
95 | NPlusOnePage page = mapper.select();
96 | System.out.println(page);
97 | System.out.println(page.hasNext());
98 | System.out.println(page.toMap());
99 | for (Bean item:page) {
100 | item.setName(item.getId()+":"+item.getName());
101 | }
102 | }
103 | }
104 |
105 | ```
106 |
107 | 进阶
108 | --------
109 | ***无侵入物理分页***
110 | ```java
111 | public interface SimplePagingMapper {
112 | @Select("SELECT * FROM a")
113 | @NormalPaginator(cache=true,cacheExpiryTime=3600)
114 | List select(RowBounds rowBounds);
115 | }
116 | ```
117 | ***无侵入物理分页进阶(取分页结构)***
118 | ```java
119 | public interface SimplePagingMapper {
120 | @Select("SELECT * FROM a")
121 | @NormalPaginator(cache=true,cacheExpiryTime=3600)
122 | List select(PagingRowBounds rowBounds);
123 |
124 | public static void main(String[] args){
125 | NPlusOnePagingMapper mapper = xxx;
126 | List list = mapper.select();
127 | int page = 0;
128 | int size = 10;
129 | PagingRowBounds rowBounds = new PagingRowBounds(page,size);
130 | NormalPaginator page = rowBounds.toNormalPage(NormalPaginator.class, list);
131 | }
132 | }
133 | ```
134 | ***设置起始页偏移量(用以兼容前端分页参数,通常前端分页从1开始)***
135 | ```java
136 | public interface SimplePagingMapper {
137 | //第一种方式
138 | @Select("SELECT * FROM a")
139 | @NormalPaginator(pageOffset=1)
140 | List select();
141 |
142 | //第二种方式(通过RowBounds 的传参方式,注解内pageOffset将不会生效)
143 | @Select("SELECT * FROM a")
144 | @NormalPaginator()
145 | List selectRowBounds(PagingRowBounds rowBounds);
146 | public static void main(String[] args){
147 | NPlusOnePagingMapper mapper = xxx;
148 | List list = mapper.select();
149 | int page = 0;
150 | int size = 10;
151 | int pageOffset = 1;
152 | boolean forceCounting = true;
153 | PagingRowBounds rowBounds = new PagingRowBounds(page,size,pageOffset, forceCounting);
154 | NormalPaginator page = rowBounds.toNormalPage(NormalPaginator.class, list);
155 | }
156 | }
157 | ```
158 | ***通过当前GET请求自动注入分页参数***
159 | ```java
160 | @Mapper
161 | public interface SimplePagingMapper {
162 | @Select("SELECT * FROM a")
163 | @NormalPaginator(auto = true,pageOffset = 1)
164 | SimplePage select();
165 | }
166 | @RestController
167 | public class PagingController{
168 | @Autowired
169 | SimplePagingMapper simplePagingMapper;
170 | /**
171 | * http://127.0.0.1:8080/test?page=1&size=20
172 | * {
173 | * "total": 16,
174 | * "size": 10,
175 | * "total_page": 2,
176 | * "has_last": false,
177 | * "has_next": true,
178 | * "page": 1,
179 | * "list": [{
180 | * "id": 1,
181 | * "name": "a",
182 | * "value": "v1"
183 | * }, {
184 | * "id": 2,
185 | * "name": "a",
186 | * "value": "v1"
187 | * }, {
188 | * "id": 3,
189 | * "name": "a",
190 | * "value": "v1"
191 | * }, {
192 | * "id": 4,
193 | * "name": "d",
194 | * "value": "v4"
195 | * }, {
196 | * "id": 5,
197 | * "name": "e",
198 | * "value": "v5"
199 | * }, {
200 | * "id": 6,
201 | * "name": "f",
202 | * "value": "v6"
203 | * }, {
204 | * "id": 7,
205 | * "name": "g",
206 | * "value": "v7"
207 | * }, {
208 | * "id": 8,
209 | * "name": "h",
210 | * "value": "v8"
211 | * }, {
212 | * "id": 9,
213 | * "name": "i",
214 | * "value": "v9"
215 | * }, {
216 | * "id": 10,
217 | * "name": "j",
218 | * "value": "v10"
219 | * }]
220 | * }
221 | */
222 | @RequestMapping("/test")
223 | public Map test(HttpServletRequest request){
224 | //默认自动读取page 和 size参数,如不存在则取默认值, 参数key和默认值可在配置修改
225 | Map map = simplePagingMapper.select().toMap();
226 | return map;
227 | }
228 | }
229 | ```
230 | ***启用Count结果缓存***
231 | ```java
232 | public interface SimplePagingMapper {
233 | @Select("SELECT * FROM a")
234 | //缓存有效时间3600秒
235 | @NormalPaginator(cache=true,cacheExpiryTime=3600)
236 | SimplePage select();
237 | }
238 | ```
239 |
240 | ***自定义Count方法***
241 | ```java
242 | public interface SimplePagingMapper {
243 | @Select("SELECT count(1) FROM a where id != ${bb} or id != #{aa}")
244 | Integer customCount(String aa, Integer bb);
245 |
246 | //与Count方法参数必须要完全一致!
247 | @Select("SELECT * FROM a where id != ${bb} or id != #{aa}")
248 | @NormalPaginator(countMethod = "customCount")
249 | SimplePage customCountSelect(String aa, Integer bb);
250 | }
251 | ```
252 |
253 | ***自定义Limit语句(兼容XML Mapper)***
254 | ```java
255 | public interface SimplePagingMapper {
256 | @Select("SELECT count(1) FROM a where id != ${bb} or id != #{aa}")
257 | Integer customCount(String aa, Integer bb);
258 |
259 | //开启自定义limit后,必须同时自定义count方法
260 | //提供:offset :limit :end(与oracle等数据库配套使用)三个内置变量
261 | @Select("SELECT * FROM a WHERE id != ${bb} OR id != #{aa} LIMIT :offset,:limit")
262 | @NormalPaginator(customLimit = true, countMethod = "customCount")
263 | SimplePage customLimitSelect(String aa, Integer bb);
264 | }
265 | ```
266 |
267 | ***自定义分页返回数据结构***
268 | ```java
269 | public class MyPagingResult extends NormalPageWrapperBase {
270 |
271 | private int total;
272 | private int startOffset;
273 | private int physicalPage;
274 | private int page;
275 | private int size;
276 | private int totalPage;
277 | private List list;
278 |
279 | public boolean hasNext(){
280 | return list.size()>=size;
281 | }
282 |
283 | public boolean hasLast(){
284 | return physicalPage>0;
285 | }
286 |
287 | public int size(){
288 | return size;
289 | }
290 |
291 | public int page(){
292 | return page;
293 | }
294 |
295 | public int total(){
296 | return total;
297 | }
298 |
299 | public int totalPage(){
300 | return totalPage;
301 | }
302 |
303 | public List list() {
304 | return list;
305 | }
306 |
307 | /**
308 | * Paged callback
309 | * @param list
310 | * @param count
311 | * @param startOffset
312 | * @param physicalPage
313 | * @param size
314 | */
315 | @Override
316 | public void onInit(List list, int count, int startOffset, int physicalPage, int size) {
317 | this.list = list;
318 | this.total = count;
319 | this.physicalPage = physicalPage;
320 | this.startOffset = startOffset;
321 | this.page = physicalPage + startOffset;
322 | this.size = size;
323 | this.totalPage = total/size+(total%size==0?0:1);
324 | }
325 | }
326 | public interface SimplePagingMapper {
327 | @Select("SELECT * FROM a")
328 | @NormalPaginator()
329 | MyPagingResult select();
330 | }
331 | ```
332 | 配置
333 | --------
334 | ```yaml
335 | #显示分页SQL
336 | logging.level.com.xiwh.paginator: debug
337 | paginator:
338 | # 根据Get请求自动完成参数注入Key
339 | size-key: size
340 | page-key: page
341 | # 根据Get请求自动注入默认页大小
342 | default-size: 10
343 | ```
344 | License
345 | --------
346 | MIT License
347 |
348 | Copyright (c) 2020 xiwh
349 |
350 | Permission is hereby granted, free of charge, to any person obtaining a copy
351 | of this software and associated documentation files (the "Software"), to deal
352 | in the Software without restriction, including without limitation the rights
353 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
354 | copies of the Software, and to permit persons to whom the Software is
355 | furnished to do so, subject to the following conditions:
356 |
357 | The above copyright notice and this permission notice shall be included in all
358 | copies or substantial portions of the Software.
359 |
360 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
361 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
362 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
363 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
364 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
365 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
366 | SOFTWARE.
--------------------------------------------------------------------------------