文章目錄
  1. 1. 一、MybatisPlugin的实现
    1. 1.1. 1.1 Mybatis Plugin的作用
      1. 1.1.1. 1.1.1 传统的查询分页
      2. 1.1.2. 1.1.2 改进的MybatisPlugin插件分页功能
    2. 1.2. 1.2 拦截器
      1. 1.2.1. 1.2.1 拦截器的作用
      2. 1.2.2. 1.2.2 Mybatis插件原理
        1. 1.2.2.1. 1.2.2.1 代理链生成
        2. 1.2.2.2. 1.2.2.2 Plugin Wrap方法
        3. 1.2.2.3. 1.2.2.3 代理链上拦截
        4. 1.2.2.4. 1.2.2.3 Mybatis插件原理学习总结
        5. 1.2.2.5. 1.2.2.4 注:Set.contains(Object o)方法学习、详解
    3. 1.3. 1.3 拦截器的实现
      1. 1.3.1. 1.3.1 Mybatis执行流程
      2. 1.3.2. 1.3.2 MetaObject学习
      3. 1.3.3. 1.3.3 拦截器注解的使用
      4. 1.3.4. 1.3.4 Interceptor的实现
      5. 1.3.5. 1.3.5 拦截器的注册
      6. 1.3.6. 1.3.6 Page实体类
  2. 2. 二、SpringMVC中的分页应用
    1. 2.1. 2.1 Controller实现分页
    2. 2.2. 2.2 Service层
    3. 2.3. 2.3 Dao层
  3. 3. 三、前端JS分页操作
    1. 3.1. 3.1 前端分页插件选择
    2. 3.2. 3.2 使用思路
    3. 3.3. 3.2 Js前端代码实现
    4. 3.4. 3.3 View层使用

摘要:
 最近毕设项目无可厚非地进入到实现业务逻辑的阶段(蛤,前期配置得有点慢~),目前实现的是最简单的列表分页显示,就碰到了Mybatis分页的问题,同时前端使用Ajax来进行数据的处理后台的Json数据。故在这篇里面写一下我这个学习实践应用到项目的的过程。

一、MybatisPlugin的实现

1.1 Mybatis Plugin的作用

1.1.1 传统的查询分页

无论使用什么分页都需要这么几个步骤:

  • 在页面中使用链接或其他形式的分页请求;
  • 后台控制器获取URL中的当前页码和查询条件进行查询数据库;
  • 数据库查询语句拼接查询条件,查询当前查询后的条数,查询结果limit分页,查询的排序orderby,将查询结果返回给控制器;
  • 控制器将其渲染在页面当中,完成分页。

1.1.2 改进的MybatisPlugin插件分页功能

通过Mybatis插件实现的分页需求:

  • 不能影响现有的SQL,类似AOP的效果
  • Mybatis提供了通用的拦截接口,要选择适当的拦截方式和时点
  • 尽量少的影响现有service等接口
  • 使用前端JS分页插件,控制分页参数,使用Ajax获取列表信息,接受Controller分页的结果,并将其渲染到前端页面

1.2 拦截器

1.2.1 拦截器的作用

 Mybatis的分页功能很弱,它是基于内存的分页(查出所有记录再按偏移量和limit取结果),在大数据量的情况下这样的分页基本上是没有用的。此处基于自己实现的拦截器,通过拦截StatementHandler重写sql语句,实现数据库的物理分页。

1.2.2 Mybatis插件原理

 Mybatis采用责任链模式,通过动态代理组织多个拦截器(插件),通过这些拦截器可以改变Mybatis的默认行为(诸如SQL重写之类的)

1.2.2.1 代理链生成

 Mybatis支持对Executor、StatementHandler、PameterHandler和ResultSetHandler进行拦截,也就是说会对这4种对象进行代理。下面以Executor为例。Mybatis在创建Executor对象时会执行下面一行代码:

executor =(Executor) interceptorChain.pluginAll(executor);

 InterceptorChain里保存了所有的拦截器,它在mybatis初始化的时候创建。上面这句代码的含义是调用拦截器链里的每个拦截器依次对executor进行plugin(插入?)代码如下:

1
2
3
4
5
6
7
8
9
10
11

/* 每一个拦截器对目标类都进行一次代理
* @paramtarget
* @return 层层代理后的对象
*/

public Object pluginAll(Object target) {
for(Interceptor interceptor : interceptors) {
target= interceptor.plugin(target);
}
return target;
}

一个简单的例子来看看这个plugin方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Intercepts({@Signature(type = Executor.class, method ="prepare", args = {MappedStatement.class, Object.class})})  
public class ExamplePlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
return invocation.proceed();
}

@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}

@Override
public void setProperties(Properties properties) {
}
}

  • 1、Object intercept(Invocation invocation)是实现拦截逻辑的地方,内部要通过invocation.proceed()显式地推进责任链前进,也就是调用下一个拦截器拦截目标方法。
  • 2、Object plugin(Object target) 就是用当前这个拦截器生成对目标target的代理,实际是通过Plugin.wrap(target,this) 来完成的,把目标target和拦截器this传给了包装函数。
  • 3、 setProperties(Properties properties)用于设置额外的参数,参数配置在拦截器的Properties节点里。
  • 4、注解里描述的是指定拦截方法的签名 [type,method,args] (即对哪种对象的哪种方法进行拦截),它在拦截前用于决断。

1.2.2.2 Plugin Wrap方法

 每个拦截器的plugin方法是通过调用Plugin.wrap方法来实现。每个方法都要经过拦截器,wrap方法中判断该方法是否是需要被代理的方法,如果不需要直接返回该方法。没有经过代理的方法不会经过interceptor()方法。

1
2
3
4
5
6
7
8
9
10
11
12
public static Object wrap(Object target, Interceptor interceptor) {  
//从拦截器的注解中获取拦截的类名和方法信息
Map<Class<?>, Set<Method>> signatureMap =getSignatureMap(interceptor);
Class<?> type = target.getClass();
//解析被拦截对象的所有接口(注意是接口)
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if(interfaces.length > 0) {
//生成代理对象, Plugin对象为该代理对象的InvocationHandler (InvocationHandler属于java代理的一个重要概念,不熟悉的请参考相关概念)
return Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target,interceptor,signatureMap));
}
return target;
}

Alt text

通过getSignatureMap(Interceptor interceptor)获取拦截器上的注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);//获取拦截器上的注解
if (interceptsAnnotation == null) { // issue #251
throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
}
Signature[] sigs = interceptsAnnotation.value();
Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();
for (Signature sig : sigs) {
Set<Method> methods = signatureMap.get(sig.type());
if (methods == null) {
methods = new HashSet<Method>();
signatureMap.put(sig.type(), methods);
}
try {
Method method = sig.type().getMethod(sig.method(), sig.args());
methods.add(method);
} catch (NoSuchMethodException e) {
throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
}
}
return signatureMap;
}

Plugin类有三个属性:

  • private Object target;//被代理的目标类
  • private Interceptor interceptor;//对应的拦截器
  • private Map<Class<?>, Set<Method>> signatureMap;//拦截器拦截的方法缓存

 结合(Executor)interceptorChain.pluginAll(executor)这个语句来看,这个语句内部对executor执行了多次plugin,第一次plugin后通过Plugin.wrap方法生成了第一个代理类,姑且就叫executorProxy1,这个代理类的target属性是该executor对象。第二次plugin后通过Plugin.wrap方法生成了第二个代理类,姑且叫executorProxy2,这个代理类的target属性是executorProxy1…这样通过每个代理类的target属性就构成了一个代理链(从最后一个executorProxyN往前查找,通过target属性可以找到最原始的executor类)。

1.2.2.3 代理链上拦截

 代理链生成后,对原始目标的方法调用都转移到代理者的invoke方法上来了。Plugin作为InvocationHandler的实现类,以下是invoke方法在Mybatis中的代码:

1
2
3
4
5
6
7
8
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set e = (Set)this.signatureMap.get(method.getDeclaringClass());
return e != null && e.contains(method)?this.interceptor.intercept(new Invocation(this.target, method, args)):method.invoke(this.target, args);
} catch (Exception var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
}

我们稍做修改,便于阅读:

1
2
3
4
5
6
7
8
9
10
11
12
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
try {
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
if(methods != null && methods.contains(method)) {
//调用代理类所属拦截器的intercept方法,
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch(Exception e) {
throwExceptionUtil.unwrapThrowable(e);
}
}

 从上代码可知,在invoke里,如果方法签名和拦截中的签名一致,就调用拦截器的拦截方法。我们看到传递给拦截器的是一个Invocation对象,以下是Invocation对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Invocation {  
private Object target;
private Method method;
private Object[] args;
public Invocation(Object target, Method method, Object[] args) {
this.target =target;
this.method =method;
this.args =args;
}
...

public Object proceed() throws InvocationTargetException, IllegalAccessException {
return method.invoke(target, args);
}
}

 Invocation类保存了代理对象的目标类,执行的目标类方法以及传递给它的参数。
 在每个拦截器的intercept方法内,最后一个语句一定是return invocation.proceed()(不这么做的话拦截器链就断了,你的mybatis基本上就不能正常工作了)。invocation.proceed()只是简单的调用了下target的对应方法,如果target还是个代理,就又回到了上面的Plugin.invoke方法了。这样就形成了拦截器的调用链推进。

1.2.2.3 Mybatis插件原理学习总结

配置Mybatis插件之后

  • 1、对所有可能被拦截的处理类都会通过Plugin.Wrap生成一个代理;
  • 2、在Plugin.Wrap中对拦截器的需要拦截拦截的类名和方法信息 ,生成代理对象, Plugin对象为该代理对象的InvocationHandler (使用的是JDK的动态代理);
  • 3、在Plugin中的invoke()方法中,如果方法签名和拦截中的签名一致,就调用拦截器的拦截方法。并向拦截器传递一个Invocation对象;
  • 4、在Invocation对象中,保存了代理对象的目标类,执行的目标类方法以及传递给它的参数;
  • 5、执行拦截器中的拦截方法后,都要使用interceptor()方法,推进拦截器的调用链推进。

1.2.2.4 注:Set.contains(Object o)方法学习、详解

 主要是集合Set 中存储对象的 hashCode 与 equals 方法应遵循的约束关系
 Java的API文档指出: 当且仅当 本set包含一个元素 e,并且满足(o==null ? e==null : o.equals(e))条件时,contains()方法才返回true. 因此 contains()方法 必定使用equals方法来检查是否相等。
 Java的根类Object定义了 public boolean equals(Object obj) 方法.因此所有的对象,包括数组(array,[]),都实现了此方法。
1、Object.equals(Object object)
 在自定义类里,如果没有明确地重写(override)此方法,那么就会使用Object类的默认实现.即只有两个对象(引用)指向同一块内存地址(即同一个实际对象, x==y为true)时,才会返回true。
2、Object.hashCode()
 问题的关键在于 hashCode() 是Object类定义的另一个基础方法。
 如果两个对象相等(使用equals()方法),那么必须拥有相同的哈希码(使用hashCode()方法),即使两个对象有相同的哈希值(hash code),他们不一定相等.意思就是: 多个不同的对象,可以返回同一个hash值。
 hashCode()的默认实现是为不同的对象返回不同的整数.有一个设计原则是,hashCode对于同一个对象,不管内部怎么改变,应该都返回相同的整数值,因为未定义自己的hashCode()实现,因此默认实现对两个对象返回两个不同的整数,这种情况破坏了约定原则。

1.3 拦截器的实现

1.3.1 Mybatis执行流程

 要想改变mybatis内部的分页行为,理论上只要把最终要执行的sql转变成对应的分页语句就行了。首先,我们熟悉下mybatis内部执行查询的动态交互图:
Alt text
 可以很清楚的看到,真正生成Statement并执行sql的语句是StatementHandler接口的某个实现,这样就可以写个插件对StatementHandler的行为进行拦截。  

1.3.2 MetaObject学习

 MetaObject是Mybatis提供的一个的工具类,通过它包装一个对象后可以获取或设置该对象的原本不可访问的属性(比如那些私有属性)。它有个三个重要方法经常用到:

  • 1、MetaObject forObject(Object object,ObjectFactory objectFactory,ObjectWrapperFactory objectWrapperFactory)//用于包装对象;
  • 2、Object getValue(String name)//用于获取属性的值(支持OGNL的方法);
  • 3、void setValue(String name, Object value)//用于设置属性的值(支持OGNL的方法);
    通过MetaObject可以拿到StatementHandler中的属性.(还需补充)

1.3.3 拦截器注解的使用

1
2
3
4
@Intercepts({@Signature(type = StatementHandler.class,method = "prepare",args = {Connection.class})})
public class PageInterceptor implements Interceptor {
.........
}

拦截器注解作用:

  • type要拦截的目标类型是StatementHandler接口(注意:type只能配置成接口类型)
  • 拦截的方法是名称为prepare参数为Connection类型的方法。该方法在StateHandler接口中的prepare(),如下:

    1
    2
    3
    4
    5
    public interface StatementHandler {
    Statement prepare(Connection connection){
    throws SQLException;
    }
    }
  • args[]表示需要使用的参数

 通过配置该拦截器的注解,在执行StatementHandler的实现接口PreparedStatementHandler的prepare方法前,就能将传入的Sql语句进行修改成分页语句,进行查询。PreparedStatementHandler的prepare()方法实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();//对该Sql进行修改,之后在进行之后操作
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
return connection.prepareStatement(sql, keyColumnNames);
}
} else if (mappedStatement.getResultSetType() != null) {
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
} else {
return connection.prepareStatement(sql);
}
}

1.3.4 Interceptor的实现

拦截器插件的实现,需要继承Mybatis的Interceptor接口,并实现三个方法,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import com.nist.targetsystem.bean.entity.Page;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;
import java.util.Properties;

/**
* 实现Mybatis分页插件拦截器
* Created by huangguoxin on 16/4/11.
*/

@Intercepts({@Signature(type = StatementHandler.class,method = "prepare",args = {Connection.class})})
public class PageInterceptor implements Interceptor {
private static Logger logger = LoggerFactory.getLogger(PageInterceptor.class);
//使用正则匹配查找xxxMapper.xml中的查询语句的ID
private static final String idMatcher = ".+ByPage$";
/**
*
* @param invocation 拦截的对象
* @return
* @throws Throwable
*/

public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler)invocation.getTarget();//invoction中具有被拦截下来的对象,在注解中拦截到了就是StatementHandler对象
//MetaObject就是StatementHandler,通过MetaObject可以拿到StatementHandler中的属性
MetaObject metaObject = MetaObject.forObject(statementHandler,
SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY);
//获取Mapper.xml配置文件的对象,后面的字符串参数是用OGNL表达式
MappedStatement mappedStatement = (MappedStatement)metaObject.getValue("delegate.mappedStatement");
// 配置文件中SQL语句的ID
String id = mappedStatement.getId();
//拦截配置文件中的Sql语句的ID进行匹配
if(id.matches(idMatcher)){
BoundSql boundSql = statementHandler.getBoundSql();
// 原始的SQL语句
String sql = boundSql.getSql();
//获取查询后总条数
int count = getTotalCount(sql,invocation,metaObject);
//获取绑定到的sql语句中的参数
Map<?,?> parameter = (Map<?, ?>) boundSql.getParameterObject();
Page page = (Page)parameter.get("page");
page.setTotalNumber(count);
// 改造后带分页查询的SQL语句
metaObject.setValue("delegate.boundSql.sql", getPageCount(sql,page));
}
return invocation.proceed();
}
/**
* 获取数据库中这次查询的总条数
* 普通的JDBC查询
* @param sql 原始Sql语句
* @param invocation
* @param metaObject
* @return
*/

private int getTotalCount(String sql, Invocation invocation, MetaObject metaObject){
String countSql = getCountSql(sql);
Connection connection = (Connection) invocation.getArgs()[0];
PreparedStatement countStatement = null;
ResultSet rs = null;
int count = 0;
try {
countStatement = connection.prepareStatement(countSql);
ParameterHandler parameterHandler = (ParameterHandler)metaObject.getValue("delegate.parameterHandler");
parameterHandler.setParameters(countStatement);
rs = countStatement.executeQuery();
if (rs.next()){
count = rs.getInt(1);
}
} catch (SQLException e) {
logger.error("Ignore this exception :{}", e);
} finally {
try {
rs.close();
} catch (SQLException e) {
logger.error("Ignore this exception :{}", e);
}
try {
countStatement.close();
} catch (SQLException e) {
logger.error("Ignore this exception :{}", e);
}
}

return count;
}
/**
* 使用StringBuilder构造countSql语句
* @param sql 原始sql语句
* @return 构造后的countSql语句
*/

private String getCountSql(String sql) {
String countSql = "select count(*) from (" + sql + ")a";
return countSql;
}
/**
* 构造查询语句,将其改造成分页
* @param sql
* @param page
* @return
*/

private String getPageCount(String sql, Page page) {
StringBuilder pageSql = new StringBuilder(sql);
pageSql.append(" limit ");
pageSql.append(page.getDbIndex());
pageSql.append(",");
pageSql.append(page.getDbNumber());
return pageSql.toString();
}
/**
* 判断所有经过拦截的对象是否需要拦截
* 如果不是需要拦截的对象,将返回原对象本身
* @param o
* @return
*/

public Object plugin(Object o) {
return Plugin.wrap(o, this);
}
public void setProperties(Properties properties) {
}
}

  • StatementHandler的默认实现类是RoutingStatementHandler,因此拦截的实际对象是它。RoutingStatementHandler的主要功能是分发,它根据配置Statement类型创建真正执行数据库操作的StatementHandler,并将其保存到delegate属性里。由于delegate是一个私有属性并且没有提供访问它的方法,因此需要借助MetaObject的帮忙。通过MetaObject的封装后我们可以轻易的获得想要的属性。
  • 使用OGNL表达式delegate.mappedStatement获取到RoutingStatementHandler中的私有属性delegate,并获取到delegate所属的StatementHandler接口中属性mapperStatement:
    Alt text

    1
    2
    3
    4
    5
    public class RoutingStatementHandler implements StatementHandler {

    private final StatementHandler delegate;
    .......
    }
  • 主机中匹配的是要拦截的目标类型是StatementHandler拦截的方法是名称为prepare参数为Connection类型的方法,而这个方法是每次数据库访问都要执行的。因为我是通过重写sql的方式实现分页,为了不影响其他sql(update或不需要分页的query),我采用了通过ID匹配的方式过滤。默认的正则匹配过滤方式只对id以ByPage结尾的进行拦截(注意区分大小写);

  • 当匹配了Sql语句的ID之后,使用BoundSql boundSql = statementHandler.getBoundSql();String sql = boundSql.getSql();从statementHandler中获取配置文件绑定的原始Sql语句;
  • 之后获取到分页参数,并将其原始的Sql与语句修改修改:
    1
    2
    3
    4
    5
    6
    //获取绑定到的sql语句中的参数
    Map<?,?> parameter = (Map<?, ?>) boundSql.getParameterObject();
    Page page = (Page)parameter.get("page");
    .......
    // 改造后带分页查询的SQL语句
    metaObject.setValue("delegate.boundSql.sql", getPageCount(sql,page));

上述代码的metaObject.setValue("delegate.boundSql.sql", getPageCount(sql,page)); 的意义为:
1、将原始Sql语句设置为新修改的分页Sql语句
2、 参数delegate.boundSql.sql 的意思是从RoutingStatementHandler找到StatementHandler中的boundSql属性,再找到BounderSql类中的sql属性,具体流程入下列图的顺序:
Alt text
Alt text
Alt text
3、参数getPageCount(sql,page)为该拦截器的方法,根据Page参数修改原始的Sql语句
4、在获取结果集条数的方法getCountSql()中,需要获取传入参数的值将其放入预编译的Sql语句中,进而查询以获取结果集总数,所以使用以下方法进行获取参数,实现获取总数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
private int getTotalCount(String sql, Invocation invocation, MetaObject metaObject){
String countSql = getCountSql(sql);
Connection connection = (Connection) invocation.getArgs()[0];
PreparedStatement countStatement = null;
ResultSet rs = null;
int count = 0;
try {
countStatement = connection.prepareStatement(countSql);
ParameterHandler parameterHandler = (ParameterHandler)metaObject.getValue("delegate.parameterHandler");//获取参数Handler
parameterHandler.setParameters(countStatement);//对语句进行设置参数
rs = countStatement.executeQuery();
if (rs.next()){
count = rs.getInt(1);
}
} catch (SQLException e) {
logger.error("Ignore this exception :{}", e);
} finally {
try {
rs.close();
} catch (SQLException e) {
logger.error("Ignore this exception :{}", e);
}
try {
countStatement.close();
} catch (SQLException e) {
logger.error("Ignore this exception :{}", e);
}
}
return count;
}

1.3.5 拦截器的注册

在Spring-Mybatis配置文件中使用如下配置:

1
<bean id="pageInterceptor" class="com.nist.targetsystem.interceptor.PageInterceptor"/>

在sqlSessionFactory中使用该分页拦截器(or插件)

1
2
3
4
5
6
7
8
<!-- 配置sqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 实例化sqlSessionFactory时需要使用上述配置好的数据源以及SQL映射文件 -->
<property name="dataSource" ref="dataSource" />
<!-- 自动扫描mybatisConfig/*Mapper.xml目录下的所有SQL映射的xml文件-->
<property name="mapperLocations" value="classpath:mybatisConfig/*Mapper.xml" />
<property name="plugins" ref="pageInterceptor"/>
</bean>

1.3.6 Page实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
package com.nist.targetsystem.bean.entity;

/**
* 分页对应实体类
* Created by huangguoxin on 16/4/12.
*/

public class Page {
/**
* 总条数
*/

private int totalNumber;
/**
* 当前第几页
*/

private int currentPage;
/**
* 总页数
*/

private int totalPage;
/**
* 每页显示条数
*/

private int pageNumber;
/**
* 数据库中limit的参数,从第几条开始取
*/

private int dbIndex;
/**
* 数据库中limit的参数,一共取多少条
*/

private int dbNumber;

/**
* 根据当前对象中属性值计算并设置相关属性值
*/

public void count() {
// 计算总页数
int totalPageTemp = this.totalNumber / this.pageNumber;
int plus = (this.totalNumber % this.pageNumber) == 0 ? 0 : 1;
totalPageTemp = totalPageTemp + plus;
if(totalPageTemp <= 0) {
totalPageTemp = 1;
}
this.totalPage = totalPageTemp;

// 设置当前页数
// 总页数小于当前页数,应将当前页数设置为总页数
if(this.totalPage < this.currentPage) {
this.currentPage = this.totalPage;
}
// 当前页数小于1设置为1
if(this.currentPage < 1) {
this.currentPage = 1;
}

// 设置limit的参数
this.dbIndex = (this.currentPage - 1) * this.pageNumber;
this.dbNumber = this.pageNumber;
}

public int getTotalNumber() {
return totalNumber;
}

public void setTotalNumber(int totalNumber) {
this.totalNumber = totalNumber;
this.count();
}

public int getCurrentPage() {
return currentPage;
}

public void setCurrentPage(int currentPage) {
this.currentPage = currentPage;
}

public int getTotalPage() {
return totalPage;
}

public void setTotalPage(int totalPage) {
this.totalPage = totalPage;
}

public int getPageNumber() {
return pageNumber;
}

public void setPageNumber(int pageNumber) {
this.pageNumber = pageNumber;
this.count();
}

public int getDbIndex() {
return dbIndex;
}

public void setDbIndex(int dbIndex) {
this.dbIndex = dbIndex;
}

public int getDbNumber() {
return dbNumber;
}

public void setDbNumber(int dbNumber) {
this.dbNumber = dbNumber;
}
}

二、SpringMVC中的分页应用

2.1 Controller实现分页

 因为不同页面列表可能显示的条数不同,所以我将其也设置为参数形式又前端JS传递进入Controller中的方法加以处理,并用RESTful风格的URl形式传递,以代码为例,不做过多解释,如下:

1
2
3
4
5
6
7
8
9
10
@RequestMapping(value = "/getNetTemplateList/pageNumber/{pageNumber}/currentPage/{currentPage}",method = RequestMethod.POST)
public @ResponseBody Map<String,Object> getNetTemplateList(@PathVariable String pageNumber,@PathVariable String currentPage, HttpServletRequest request){
Map<String,Object> templateMap = new HashMap<String, Object>();
Page page = new Page();
page.setPageNumber(Integer.valueOf(pageNumber));
page.setCurrentPage(Integer.valueOf(currentPage));
templateMap.put("list",templateService.getNetTemplateByPage(page));
templateMap.put("page",page);
return templateMap;
}

2.2 Service层

 该层即简单调用Dao层函数,并将前台输入的数据进行处理,封装进入参数表-ParameterMap

1
2
3
4
5
6
7
8
9
10
/**
* 根据Page类中的分页条件查询
* @param page Page类
* @return
*/

public List<NetTemplate> getNetTemplateByPage(Page page) {
Map<String,Object> parameter = new HashMap<String, Object>();//分页参数表
parameter.put("page",page); //将page对象参数传入Map中
return netTemplateDao.getNetTemplateByPage(parameter);
}

2.3 Dao层

 该层中的方法只要以ByPage结尾即可对列表进行分页,毋需过多解释,已在拦截器一节说明。

三、前端JS分页操作

3.1 前端分页插件选择

 对于一个前端弱的人来说,使用已经封装好的前端分页插件是极为方便的。我使用的是easy-paging.js 下载地址:http://plugins.jquery.com/paging/

3.2 使用思路

使用该插件思路:

  • 首次加载要显示列表的页面时,需要首次通过AJAX请求展示的第一页,获取后端的Json,该Json为Map,包括列表List信息和分页信息类Page,所有分页信息都在Page内
  • 使用Javascript将List信息渲染在表格中
  • 通过前端分页插件,自动将List其分页,并自动设置请求的URL,重复上述步骤。

3.2 Js前端代码实现

以下为Javascript代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/**
* 加载时获取网络模版列表
* Created by huangguoxin on 16/4/12.
*/

$(document).ready(function(){

var pageNumber = 8; //设置每页几条信息
var pageInfo; //保存全局分页信息
var url = '/Template/getNetTemplateList/pageNumber/'+pageNumber+'/currentPage/'+1;
$.ajax({
url: url,
data:{
},
type: 'post',
cache: false,
async : false, //第一次渲染页面采用同步方式,将获取的Page信息赋予全局变量
dataType: 'json',
success:function(data){
var dataHtml;
var listData = data["list"];
pageInfo = data["page"];
for (var temp in listData){
dataHtml += getData(listData[temp]);
}
$("#template-table").html(dataHtml);

},
error:function(){
alert("异常!");
}
});
$("#paging").easyPaging(pageInfo["totalNumber"], {
perpage: pageNumber,
onSelect: function(page) {
$.ajax({
url: '/Template/getNetTemplateList/pageNumber/'+pageNumber+'/currentPage/'+page,
data:{
},
type: 'post',
cache: false,
dataType: 'json',
success:function(data){
var dataHtml;
var listData = data["list"];
for (var temp in listData){
dataHtml += getData(listData[temp]);
}
$("#template-table").html(dataHtml);

},
error:function(){
alert("异常!");
}
});
}
});
})

渲染表格方法略去不表。

3.3 View层使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div class="pagination pagination-centered">
<ul id="paging">
<li>上一页</li>
<li>#n</li>
<li>#n</li>
<li>#n</li>
<li>#n</li>
<li>#c</li>
<li>#n</li>
<li>#n</li>
<li>下一页</li>
</ul>
</div>
.........
<script type="text/javascript" src="../../../media/js/template/jquery.paging.min.js"></script>
<script dtype="text/javascript" src="../../../media/js/template/jquery.easy-paging.js"></script>
文章目錄
  1. 1. 一、MybatisPlugin的实现
    1. 1.1. 1.1 Mybatis Plugin的作用
      1. 1.1.1. 1.1.1 传统的查询分页
      2. 1.1.2. 1.1.2 改进的MybatisPlugin插件分页功能
    2. 1.2. 1.2 拦截器
      1. 1.2.1. 1.2.1 拦截器的作用
      2. 1.2.2. 1.2.2 Mybatis插件原理
        1. 1.2.2.1. 1.2.2.1 代理链生成
        2. 1.2.2.2. 1.2.2.2 Plugin Wrap方法
        3. 1.2.2.3. 1.2.2.3 代理链上拦截
        4. 1.2.2.4. 1.2.2.3 Mybatis插件原理学习总结
        5. 1.2.2.5. 1.2.2.4 注:Set.contains(Object o)方法学习、详解
    3. 1.3. 1.3 拦截器的实现
      1. 1.3.1. 1.3.1 Mybatis执行流程
      2. 1.3.2. 1.3.2 MetaObject学习
      3. 1.3.3. 1.3.3 拦截器注解的使用
      4. 1.3.4. 1.3.4 Interceptor的实现
      5. 1.3.5. 1.3.5 拦截器的注册
      6. 1.3.6. 1.3.6 Page实体类
  2. 2. 二、SpringMVC中的分页应用
    1. 2.1. 2.1 Controller实现分页
    2. 2.2. 2.2 Service层
    3. 2.3. 2.3 Dao层
  3. 3. 三、前端JS分页操作
    1. 3.1. 3.1 前端分页插件选择
    2. 3.2. 3.2 使用思路
    3. 3.3. 3.2 Js前端代码实现
    4. 3.4. 3.3 View层使用