而sql你需要 *** 作一大堆!
www.cao055.com
一般设置 jdbc 或者 数据库连接池,跟具体的数据库 关系不大,只是更改方言等具体细节而已下载个jdbc的包,放到lib路径里面
#b2bplatform.dialect=org.hibernate.dialect.MySQLDialect此处改为sqlserver的方言即可
b2bplatform.show_sql=true
b2bplatform.generateDdl=true
b2bplatform.minPoolSize=1
b2bplatform.maxPoolSize=10
b2bplatform.connection.url=jdbc:mysql://127.0.0.1:3306/b2b?characterEncoding=UTF-8这个地方要改下端口什么的
b2bplatform.connection.username=root
b2bplatform.connection.password=123456
b2bplatform.connection.driver_class=com.mysql.jdbc.Driver //这个地方改下驱动名称
今天和大家分享下mybatis的一个分页插件PageHelper,在讲解PageHelper之前我们需要先了解下mybatis的插件原理。PageHelper的官方网站:https://github.com/pagehelper/Mybatis-PageHelper
一、Plugin接口
mybatis定义了一个插件接口org.apache.ibatis.plugin.Interceptor,任何自定义插件都需要实现这个接口PageHelper就实现了改接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package org.apache.ibatis.plugin
import java.util.Properties
/**
* @author Clinton Begin
*/
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable
Object plugin(Object target)
void setProperties(Properties properties)
}
1:intercept 拦截器,它将直接覆盖掉你真实拦截对象的方法。
2:plugin方法它是一个生成动态代理对象的方法
3:setProperties它是允许你在使用插件的时候设置参数值。
看下com.github.pagehelper.PageHelper分页的实现了那些
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* Mybatis拦截器方法
*
* @param invocation 拦截器入参
* @return 返回执行结果
* @throws Throwable 抛出异常
*/
public Object intercept(Invocation invocation) throws Throwable {
if (autoRuntimeDialect) {
SqlUtil sqlUtil = getSqlUtil(invocation)
return sqlUtil.processPage(invocation)
} else {
if (autoDialect) {
initSqlUtil(invocation)
}
return sqlUtil.processPage(invocation)
}
}
这个方法获取了是分页核心代码,重新构建了BoundSql对象下面会详细分析
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 只拦截Executor
*
* @param target
* @return
*/
public Object plugin(Object target) {
if (target instanceof Executor) {
return Plugin.wrap(target, this)
} else {
return target
}
}
这个方法是正对Executor进行拦截
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
/**
* 设置属性值
*
* @param p 属性值
*/
public void setProperties(Properties p) {
checkVersion()
//多数据源时,获取jdbcurl后是否关闭数据源
String closeConn = p.getProperty("closeConn")
//解决#97
if(StringUtil.isNotEmpty(closeConn)){
this.closeConn = Boolean.parseBoolean(closeConn)
}
//初始化SqlUtil的PARAMS
SqlUtil.setParams(p.getProperty("params"))
//数据库方言
String dialect = p.getProperty("dialect")
String runtimeDialect = p.getProperty("autoRuntimeDialect")
if (StringUtil.isNotEmpty(runtimeDialect) &&runtimeDialect.equalsIgnoreCase("TRUE")) {
this.autoRuntimeDialect = true
this.autoDialect = false
this.properties = p
} else if (StringUtil.isEmpty(dialect)) {
autoDialect = true
this.properties = p
} else {
autoDialect = false
sqlUtil = new SqlUtil(dialect)
sqlUtil.setProperties(p)
}
}
基本的属性设置
二、Plugin初始化
初始化和所有mybatis的初始化一样的在之前的文章里面已经分析了 《Mybatis源码分析之SqlSessionFactory(一)》
1
2
3
4
5
6
7
8
9
10
11
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
String interceptor = child.getStringAttribute("interceptor")
Properties properties = child.getChildrenAsProperties()
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance()
interceptorInstance.setProperties(properties)
configuration.addInterceptor(interceptorInstance)
}
}
}
这里是讲多个实例化的插件对象放入configuration,addInterceptor最终存放到一个list里面的,以为这可以同时存放多个Plugin
三、Plugin拦截
插件可以拦截mybatis的4大对象ParameterHandler、ResultSetHandler、StatementHandler、Executor,源码如下图
在Configuration类里面可以找到
PageHelper使用了Executor进行拦截,上面的的源码里面已经可以看到了。
我看下上图newExecutor方法
1
executor = (Executor) interceptorChain.pluginAll(executor)
这个是生产一个代理对象,生产了代理对象就运行带invoke方法
四、Plugin运行
mybatis自己带了Plugin方法,源码如下
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
public class Plugin implements InvocationHandler {
private Object target
private Interceptor interceptor
private Map<Class<?>, Set<Method>>signatureMap
private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>>signatureMap) {
this.target = target
this.interceptor = interceptor
this.signatureMap = signatureMap
}
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) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap))
}
return target
}
@Override
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)) {
return interceptor.intercept(new Invocation(target, method, args))
}
return method.invoke(target, args)
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e)
}
}
private static Map<Class<?>, Set<Method>>getSignatureMap(Interceptor interceptor) {
Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class)
// issue #251
if (interceptsAnnotation == null) {
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
}
private static Class<?>[] getAllInterfaces(Class<?>type, Map<Class<?>, Set<Method>>signatureMap) {
Set<Class<?>>interfaces = new HashSet<Class<?>>()
while (type != null) {
for (Class<?>c : type.getInterfaces()) {
if (signatureMap.containsKey(c)) {
interfaces.add(c)
}
}
type = type.getSuperclass()
}
return interfaces.toArray(new Class<?>[interfaces.size()])
}
}
wrap方法是为了生成一个动态代理类。
invoke方法是代理绑定的方法,该方法首先判定签名类和方法是否存在,如果不存在则直接反射调度被拦截对象的方法,如果存在则调度插件的interceptor方法,这时候会初始化一个Invocation对象
我们在具体看下PageHelper,当执行到invoke后程序将跳转到PageHelper.intercept
1
2
3
4
5
6
7
8
9
10
11
public Object intercept(Invocation invocation) throws Throwable {
if (autoRuntimeDialect) {
SqlUtil sqlUtil = getSqlUtil(invocation)
return sqlUtil.processPage(invocation)
} else {
if (autoDialect) {
initSqlUtil(invocation)
}
return sqlUtil.processPage(invocation)
}
}
我们在来看sqlUtil.processPage方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* Mybatis拦截器方法,这一步嵌套为了在出现异常时也可以清空Threadlocal
*
* @param invocation 拦截器入参
* @return 返回执行结果
* @throws Throwable 抛出异常
*/
public Object processPage(Invocation invocation) throws Throwable {
try {
Object result = _processPage(invocation)
return result
} finally {
clearLocalPage()
}
}
继续跟进
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
/**
* Mybatis拦截器方法
*
* @param invocation 拦截器入参
* @return 返回执行结果
* @throws Throwable 抛出异常
*/
private Object _processPage(Invocation invocation) throws Throwable {
final Object[] args = invocation.getArgs()
Page page = null
//支持方法参数时,会先尝试获取Page
if (supportMethodsArguments) {
page = getPage(args)
}
//分页信息
RowBounds rowBounds = (RowBounds) args[2]
//支持方法参数时,如果page == null就说明没有分页条件,不需要分页查询
if ((supportMethodsArguments &&page == null)
//当不支持分页参数时,判断LocalPage和RowBounds判断是否需要分页
|| (!supportMethodsArguments &&SqlUtil.getLocalPage() == null &&rowBounds == RowBounds.DEFAULT)) {
return invocation.proceed()
} else {
//不支持分页参数时,page==null,这里需要获取
if (!supportMethodsArguments &&page == null) {
page = getPage(args)
}
return doProcessPage(invocation, page, args)
}
}
这些都只是分装page方法,真正的核心是doProcessPage
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
/**
* Mybatis拦截器方法
*
* @param invocation 拦截器入参
* @return 返回执行结果
* @throws Throwable 抛出异常
*/
private Page doProcessPage(Invocation invocation, Page page, Object[] args) throws Throwable {
//保存RowBounds状态
RowBounds rowBounds = (RowBounds) args[2]
//获取原始的ms
MappedStatement ms = (MappedStatement) args[0]
//判断并处理为PageSqlSource
if (!isPageSqlSource(ms)) {
processMappedStatement(ms)
}
//设置当前的parser,后面每次使用前都会set,ThreadLocal的值不会产生不良影响
((PageSqlSource)ms.getSqlSource()).setParser(parser)
try {
//忽略RowBounds-否则会进行Mybatis自带的内存分页
args[2] = RowBounds.DEFAULT
//如果只进行排序 或 pageSizeZero的判断
if (isQueryOnly(page)) {
return doQueryOnly(page, invocation)
}
//简单的通过total的值来判断是否进行count查询
if (page.isCount()) {
page.setCountSignal(Boolean.TRUE)
//替换MS
args[0] = msCountMap.get(ms.getId())
//查询总数
Object result = invocation.proceed()
//还原ms
args[0] = ms
//设置总数
page.setTotal((Integer) ((List) result).get(0))
if (page.getTotal() == 0) {
return page
}
} else {
page.setTotal(-1l)
}
//pageSize>0的时候执行分页查询,pageSize<=0的时候不执行相当于可能只返回了一个count
if (page.getPageSize() >0 &&
((rowBounds == RowBounds.DEFAULT &&page.getPageNum() >0)
|| rowBounds != RowBounds.DEFAULT)) {
//将参数中的MappedStatement替换为新的qs
page.setCountSignal(null)
BoundSql boundSql = ms.getBoundSql(args[1])
args[1] = parser.setPageParameter(ms, args[1], boundSql, page)
page.setCountSignal(Boolean.FALSE)
//执行分页查询
Object result = invocation.proceed()
//得到处理结果
page.addAll((List) result)
}
} finally {
((PageSqlSource)ms.getSqlSource()).removeParser()
}
//返回结果
return page
}
上面的有两个 Object result = invocation.proceed()执行,第一个是执行统计总条数,第二个是执行执行分页的查询的数据
里面用到了代理。最终第一回返回一个总条数,第二个把分页的数据得到。
五:PageHelper使用
以上讲解了Mybatis的插件原理和PageHelper相关的内部实现,下面具体讲讲PageHelper使用
1:先增加maven依赖:
1
2
3
4
5
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>4.1.6</version>
</dependency
2:配置configuration.xml文件加入如下配置(plugins应该在environments的上面 )
1
2
3
4
5
6
7
8
9
10
11
12
<plugins>
<!-- PageHelper4.1.6 -->
<plugin interceptor="com.github.pagehelper.PageHelper">
<property name="dialect" value="mysql"/>
<property name="offsetAsPageNum" value="false"/>
<property name="rowBoundsWithCount" value="false"/>
<property name="pageSizeZero" value="true"/>
<property name="reasonable" value="false"/>
<property name="supportMethodsArguments" value="false"/>
<property name="returnPageInfo" value="none"/>
</plugin>
</plugins>
相关字段说明可以查看SqlUtilConfig源码里面都用说明
注意配置的时候顺序不能乱了否则报错
1
2
3
4
5
6
Caused by: org.apache.ibatis.builder.BuilderException: Error creating document instance. Cause: org.xml.sax.SAXParseExceptionlineNumber: 57columnNumber: 17元素类型为 "configuration" 的内容必须匹配 "(properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,plugins?,environments?,databaseIdProvider?,mappers?)"。
at org.apache.ibatis.parsing.XPathParser.createDocument(XPathParser.java:259)
at org.apache.ibatis.parsing.XPathParser.<init>(XPathParser.java:120)
at org.apache.ibatis.builder.xml.XMLConfigBuilder.<init>(XMLConfigBuilder.java:66)
at org.apache.ibatis.session.SqlSessionFactoryBuilder.build(SqlSessionFactoryBuilder.java:49)
... 2 more
意思是配置里面的节点顺序是properties->settings->typeAliases->typeHandlers->objectFactory->objectWrapperFactory->plugins->environments->databaseIdProvider->mappers plugins应该在environments之前objectWrapperFactory之后 这个顺序不能乱了
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)