- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章Mybatis拦截器实现分页由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
最终dao层结果:
1
2
3
|
public
interface
ModelMapper {
Page<Model> pageByConditions(RowBounds rowBounds, Model record);
}
|
接下来一步一步来实现分页.
一.创建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
|
public
class
Page<T>
extends
PageList<T> {
private
int
pageNo =
1
;
// 页码,默认是第一页
private
int
pageSize =
15
;
// 每页显示的记录数,默认是15
private
int
totalRecord;
// 总记录数
private
int
totalPage;
// 总页数
public
Page() {
}
public
Page(
int
pageNo,
int
pageSize,
int
totalRecord,
List<T> results) {
this
.pageNo = pageNo;
this
.pageSize = pageSize;
this
.totalRecord = totalRecord;
this
.setResult(results);
int
totalPage = totalRecord % pageSize ==
0
? totalRecord / pageSize : totalRecord / pageSize +
1
;
this
.setTotalPage(totalPage);
}
public
int
getPageNo() {
return
pageNo;
}
public
void
setPageNo(
int
pageNo) {
this
.pageNo = pageNo;
}
public
int
getPageSize() {
return
pageSize;
}
public
void
setPageSize(
int
pageSize) {
this
.pageSize = pageSize;
}
public
int
getTotalRecord() {
return
totalRecord;
}
public
void
setTotalRecord(
int
totalRecord) {
this
.totalRecord = totalRecord;
// 在设置总页数的时候计算出对应的总页数,在下面的三目运算中加法拥有更高的优先级,所以最后可以不加括号。
int
totalPage = totalRecord % pageSize ==
0
? totalRecord / pageSize : totalRecord / pageSize +
1
;
this
.setTotalPage(totalPage);
}
public
int
getTotalPage() {
return
totalPage;
}
public
void
setTotalPage(
int
totalPage) {
this
.totalPage = totalPage;
}
@Override
public
String toString() {
StringBuilder builder =
new
StringBuilder();
builder.append(
"Page [pageNo="
).append(pageNo).append(
", pageSize="
).append(pageSize).append(
", results="
)
.append(getResult()).append(
", totalPage="
).append(totalPage).append(
", totalRecord="
).append(totalRecord)
.append(
"]"
);
return
builder.toString();
}
}
|
可以发现,这里继承了一个PageList类;这个类也是自己创建的一个类,实现List接口。为什么要PageList这个类,是因为Page需要实现List接口,而接口中的抽象方法,需要逐一实现,所以提供PageList在统一的地方写实现List接口的方法.
为什么Page需要实现List接口,这个会在稍后的代码中做解释.
PageList类:
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
|
public
class
PageList<T>
implements
List<T> {
private
List<T> result;
public
List<T> getResult() {
return
result;
}
public
void
setResult(List<T> result) {
this
.result = result;
}
@Override
public
int
size() {
return
result.size();
}
@Override
public
boolean
isEmpty() {
return
result.isEmpty();
}
@Override
public
boolean
contains(Object o) {
return
result.contains(o);
}
@Override
public
Iterator<T> iterator() {
return
result.iterator();
}
@Override
public
Object[] toArray() {
return
result.toArray();
}
@Override
public
<E> E[] toArray(E[] a) {
return
result.toArray(a);
}
@Override
public
boolean
add(T e) {
return
result.add(e);
}
@Override
public
boolean
remove(Object o) {
return
result.remove(o);
}
@Override
public
boolean
containsAll(Collection<?> c) {
return
result.containsAll(c);
}
@Override
public
boolean
addAll(Collection<?
extends
T> c) {
return
result.addAll(c);
}
@Override
public
boolean
addAll(
int
index, Collection<?
extends
T> c) {
return
result.addAll(index, c);
}
@Override
public
boolean
removeAll(Collection<?> c) {
return
result.removeAll(c);
}
@Override
public
boolean
retainAll(Collection<?> c) {
return
result.retainAll(c);
}
@Override
public
void
clear() {
result.clear();
}
@Override
public
T get(
int
index) {
return
result.get(index);
}
@Override
public
T set(
int
index, T element) {
return
result.set(index, element);
}
@Override
public
void
add(
int
index, T element) {
result.add(index, element);
}
@Override
public
T remove(
int
index) {
return
result.remove(index);
}
@Override
public
int
indexOf(Object o) {
return
result.indexOf(o);
}
@Override
public
int
lastIndexOf(Object o) {
return
result.lastIndexOf(o);
}
@Override
public
ListIterator<T> listIterator() {
return
result.listIterator();
}
@Override
public
ListIterator<T> listIterator(
int
index) {
return
result.listIterator(index);
}
@Override
public
List<T> subList(
int
fromIndex,
int
toIndex) {
return
result.subList(fromIndex, toIndex);
}
}
|
二.提供Dao以及mapper.xml 。
dao的写法:
Page<Model> pageByConditions(RowBounds rowBounds, Model record),
mapper.xml
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
|
<!-- 表名 -->
<
sql
id
=
"tableName"
>
model
</
sql
>
<!-- 数据表所有列名 -->
<
sql
id
=
"Base_Column_List"
>
id,
name
</
sql
>
<!-- 查询字段 -->
<
sql
id
=
"Base_Search_Param"
>
<
if
test
=
"id != null"
>
and id = #{id,jdbcType=INTEGER}
</
if
>
<
if
test
=
"name != null"
>
and name = #{name,jdbcType=VARCHAR}
</
if
>
</
sql
>
<!-- 分页查询语句 -->
<
select
id
=
"pageByConditions"
resultMap
=
"BaseResultMap"
>
SELECT
<
include
refid
=
"Base_Column_List"
/>
FROM
<
include
refid
=
"tableName"
/>
WHERE 1=1
<
include
refid
=
"Base_Search_Param"
/>
</
select
>
|
ok,以上都是mybatis的基本操作,就不做多余解释.
三.创建拦截器:
我们需要做的是创建一个拦截器(PageInterceptor)、一个执行者(PageExecutor).
1.PageInteceptor:实现Inteceptor接口,将PageExecutor进行执行,拦截sql添加分页sql(limit xx,xx) 。
2.PageExecutor:实现Executor接口,在查询时,添加查询总数并修改返回值类型。因为要做的是分页,是查询操作,所以里边的非查询方法都使用基本的实现,只修改两个query方法.
PageInteceptor完整代码:
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
|
import
java.lang.reflect.InvocationTargetException;
import
java.sql.Connection;
import
java.util.Properties;
import
org.apache.ibatis.executor.Executor;
import
org.apache.ibatis.executor.statement.StatementHandler;
import
org.apache.ibatis.mapping.BoundSql;
import
org.apache.ibatis.mapping.MappedStatement;
import
org.apache.ibatis.plugin.Interceptor;
import
org.apache.ibatis.plugin.Intercepts;
import
org.apache.ibatis.plugin.Invocation;
import
org.apache.ibatis.plugin.Plugin;
import
org.apache.ibatis.plugin.Signature;
import
org.apache.ibatis.reflection.MetaObject;
import
org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import
org.apache.ibatis.reflection.factory.ObjectFactory;
import
org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import
org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import
org.apache.ibatis.session.ResultHandler;
import
org.apache.ibatis.session.RowBounds;
@Intercepts
({
@Signature
(method =
"query"
, type = Executor.
class
, args = { MappedStatement.
class
, Object.
class
,
RowBounds.
class
, ResultHandler.
class
}),
@Signature
(method =
"prepare"
, type = StatementHandler.
class
, args = { Connection.
class
}) })
public
class
PageInterceptor
implements
Interceptor {
private
static
final
ObjectFactory DEFAULT_OBJECT_FACTORY =
new
DefaultObjectFactory();
private
static
final
ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY =
new
DefaultObjectWrapperFactory();
private
String pattern =
"^.*page.*$"
;
// 需要进行分页操作的字符串正则表达式
public
String getPattern() {
return
pattern;
}
public
void
setPattern(String pattern) {
this
.pattern = pattern;
}
@Override
public
Object intercept(Invocation invocation)
throws
Throwable {
if
(invocation.getTarget()
instanceof
StatementHandler) {
return
handleStatementHandler(invocation);
}
return
invocation.proceed();
}
/**
* @param invocation
* @return
* @throws IllegalAccessException
* @throws InvocationTargetException
*/
private
Object handleStatementHandler(Invocation invocation)
throws
InvocationTargetException, IllegalAccessException {
StatementHandler statementHandler = (StatementHandler) invocation
.getTarget();
MetaObject metaStatementHandler = MetaObject.forObject(
statementHandler, DEFAULT_OBJECT_FACTORY,
DEFAULT_OBJECT_WRAPPER_FACTORY);
RowBounds rowBounds = (RowBounds) metaStatementHandler
.getValue(
"delegate.rowBounds"
);
if
(rowBounds ==
null
|| (rowBounds.getOffset() == RowBounds.NO_ROW_OFFSET && rowBounds
.getLimit() == RowBounds.NO_ROW_LIMIT)) {
return
invocation.proceed();
}
// 分离代理对象链(由于目标类可能被多个拦截器拦截,从而形成多次代理,通过下面的两次循环可以分离出最原始的的目标类)
while
(metaStatementHandler.hasGetter(
"h"
)) {
Object object = metaStatementHandler.getValue(
"h"
);
metaStatementHandler = MetaObject.forObject(object,
DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY);
}
// 分离最后一个代理对象的目标类
while
(metaStatementHandler.hasGetter(
"target"
)) {
Object object = metaStatementHandler.getValue(
"target"
);
metaStatementHandler = MetaObject.forObject(object,
DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY);
}
// 将mybatis的内存分页,调整为物理分页
BoundSql boundSql = (BoundSql) metaStatementHandler.getValue(
"delegate.boundSql"
);
String sql = boundSql.getSql();
// 重写sql
String pageSql = sql +
" LIMIT "
+ rowBounds.getOffset() +
","
+ rowBounds.getLimit();
metaStatementHandler.setValue(
"delegate.boundSql.sql"
, pageSql);
// 采用物理分页后,就不需要mybatis的内存分页了,所以重置下面的两个参数
metaStatementHandler.setValue(
"delegate.rowBounds.offset"
, RowBounds.NO_ROW_OFFSET);
metaStatementHandler.setValue(
"delegate.rowBounds.limit"
, RowBounds.NO_ROW_LIMIT);
// 将执行权交给下一个拦截器
return
invocation.proceed();
}
@Override
public
Object plugin(Object o) {
if
(Executor.
class
.isAssignableFrom(o.getClass())) {
PageExecutor executor =
new
PageExecutor((Executor)o, pattern);
return
Plugin.wrap(executor,
this
);
}
else
if
(o
instanceof
StatementHandler) {
return
Plugin.wrap(o,
this
);
}
return
o;
}
@Override
public
void
setProperties(Properties properties) {
}
}
|
PageExecutor完整代码:
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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
|
import
java.sql.Connection;
import
java.sql.PreparedStatement;
import
java.sql.ResultSet;
import
java.sql.SQLException;
import
java.util.List;
import
java.util.regex.Matcher;
import
java.util.regex.Pattern;
import
org.apache.ibatis.cache.CacheKey;
import
org.apache.ibatis.executor.BatchResult;
import
org.apache.ibatis.executor.Executor;
import
org.apache.ibatis.mapping.BoundSql;
import
org.apache.ibatis.mapping.MappedStatement;
import
org.apache.ibatis.reflection.MetaObject;
import
org.apache.ibatis.session.ResultHandler;
import
org.apache.ibatis.session.RowBounds;
import
org.apache.ibatis.transaction.Transaction;
public
class
PageExecutor
implements
Executor {
private
Executor executor;
private
String pattern;
public
PageExecutor(Executor executor, String pattern) {
this
.executor = executor;
this
.pattern = pattern;
}
@Override
public
int
update(MappedStatement ms, Object parameter)
throws
SQLException {
return
executor.update(ms, parameter);
}
@Override
public
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
CacheKey cacheKey, BoundSql boundSql)
throws
SQLException {
RowBounds rb =
new
RowBounds(rowBounds.getOffset(), rowBounds.getLimit());
List<E> rows = executor.query(ms, parameter, rowBounds, resultHandler,
cacheKey, boundSql);
return
pageResolver(rows, ms, parameter, rb);
}
/**
* 修改返回值类型
* @param rows
* @param ms
* @param parameter
* @param rowBounds
* @return
*/
private
<E> List<E> pageResolver(List<E> rows, MappedStatement ms,
Object parameter, RowBounds rowBounds) {
String msid = ms.getId();
// 如果需要分页查询,修改返回类型为Page对象
if
(msid.matches(pattern)) {
int
count = getCount(ms, parameter);
int
offset = rowBounds.getOffset();
int
pagesize = rowBounds.getLimit();
return
new
Page<E>(offset/pagesize +
1
, pagesize, count, rows);
}
return
rows;
}
/**
* 获取总数
* @param ms
* @param parameter
* @return
*/
private
int
getCount(MappedStatement ms, Object parameter) {
BoundSql bsql = ms.getBoundSql(parameter);
String sql = bsql.getSql();
String countSql = getCountSql(sql);
Connection connection =
null
;
PreparedStatement stmt =
null
;
ResultSet rs =
null
;
try
{
connection = ms.getConfiguration().getEnvironment().getDataSource()
.getConnection();
stmt = connection.prepareStatement(countSql);
rs = stmt.executeQuery();
if
(rs.next())
return
rs.getInt(
1
);
}
catch
(SQLException e) {
e.printStackTrace();
}
finally
{
try
{
if
(connection !=
null
&& !connection.isClosed()) {
connection.close();
}
}
catch
(SQLException e) {
e.printStackTrace();
}
}
return
0
;
}
private
String getCountSql(String sql) {
String countHql =
" SELECT count(*) "
+ removeSelect(removeOrders(sql));
return
countHql;
}
protected
String removeOrders(String sql) {
Pattern p = Pattern.compile(
"ORDER\\s*by[\\w|\\W|\\s|\\S]*"
, Pattern.CASE_INSENSITIVE);
Matcher m = p.matcher(sql);
StringBuffer sb =
new
StringBuffer();
while
(m.find()) {
m.appendReplacement(sb,
""
);
}
m.appendTail(sb);
return
sb.toString();
}
// 去除sql语句中select子句
private
static
String removeSelect(String hql) {
int
beginPos = hql.toLowerCase().indexOf(
"from"
);
if
(beginPos <
0
) {
throw
new
IllegalArgumentException(
" hql : "
+ hql +
" must has a keyword 'from'"
);
}
return
hql.substring(beginPos);
}
@Override
public
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler)
throws
SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
return
query(ms, parameter, rowBounds, resultHandler,
executor.createCacheKey(ms, parameter, rowBounds, boundSql),
boundSql);
}
@Override
public
List<BatchResult> flushStatements()
throws
SQLException {
return
executor.flushStatements();
}
@Override
public
void
commit(
boolean
required)
throws
SQLException {
executor.commit(required);
}
@Override
public
void
rollback(
boolean
required)
throws
SQLException {
executor.rollback(required);
}
@Override
public
CacheKey createCacheKey(MappedStatement ms, Object parameterObject,
RowBounds rowBounds, BoundSql boundSql) {
return
executor
.createCacheKey(ms, parameterObject, rowBounds, boundSql);
}
@Override
public
boolean
isCached(MappedStatement ms, CacheKey key) {
return
executor.isCached(ms, key);
}
@Override
public
void
clearLocalCache() {
executor.clearLocalCache();
}
@Override
public
void
deferLoad(MappedStatement ms, MetaObject resultObject,
String property, CacheKey key, Class<?> targetType) {
executor.deferLoad(ms, resultObject, property, key, targetType);
}
@Override
public
Transaction getTransaction() {
return
executor.getTransaction();
}
@Override
public
void
close(
boolean
forceRollback) {
executor.close(forceRollback);
}
@Override
public
boolean
isClosed() {
return
executor.isClosed();
}
}
|
关于Page需要实现List接口的原因:可以看到,query方法返回值是List<E>,而我们现在要在dao中使用Page<E>对象来接收mybatis返回的结果,所以需要让Page实现List接口.
分页查询执行顺序:进入PageInterceptor的plugin方法,拦截到执行者,进入PageExecutor的query方法,执行executor.query()时,又再次回到PageInterceptor的plugin方法,这次会执行 。
进入intercept方法,将执行的sql拼接上分页限制语句,然后查询出数据结果集合。executor.query()执行完成后,继续执行pageResolver,如果方法名称和配置的需要执行分页操作的字符串匹配时,查询数据总量,并返回Page对象;如果不匹配,直接返回List对象.
四.xml配置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
<
bean
id
=
"sqlSessionFactory"
class
=
"org.mybatis.spring.SqlSessionFactoryBean"
>
<
property
name
=
"dataSource"
ref
=
"dataSource"
/>
<
property
name
=
"configLocation"
value
=
"classpath:/conf/mybatis/mybaties-config.xml"
></
property
>
<
property
name
=
"mapperLocations"
>
<
list
>
<
value
>classpath:/conf/mybatis/**/*-mapper.xml</
value
>
</
list
>
</
property
>
<
property
name
=
"plugins"
>
<
list
>
<
ref
bean
=
"pageInterceptor"
/>
</
list
>
</
property
>
</
bean
>
<
bean
id
=
"pageInterceptor"
class
=
"cn.com.common.PageInterceptor"
>
<
property
name
=
"pattern"
value
=
"^.*page.*$"
></
property
>
</
bean
>
|
五.测试代码:
1
2
3
4
5
6
7
8
|
@Test
public
void
testPage() {
int
pageNo =
1
;
int
pageSize =
10
;
RowBounds bounds =
new
RowBounds((pageNo -
1
) * pageSize, pageSize);
Model record =
new
Model();
Page<Model> list = modelMapper.pageByConditions(bounds, record);
}
|
本文主要介绍了Mybatis拦截器实现分页的步骤与方法。具有很好的参考价值,下面跟着小编一起来看下吧 。
原文链接:http://www.cnblogs.com/taocong/p/6346663.html 。
最后此篇关于Mybatis拦截器实现分页的文章就讲到这里了,如果你想了解更多关于Mybatis拦截器实现分页的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我之前让 dll 注入(inject)器变得简单,但我有 Windows 7,我用 C# 和 C++ 做了它,它工作得很好!但是现在当我在 Windows 8 中尝试相同的代码时,它似乎没有以正确的方
我正在尝试制作一个名为 core-splitter 的元素,该元素在 1.0 中已弃用,因为它在我们的项目中起着关键作用。 如果您不知道 core-splitter 的作用,我可以提供一个简短的描述。
我有几个不同的蜘蛛,想一次运行所有它们。基于 this和 this ,我可以在同一个进程中运行多个蜘蛛。但是,我不知道如何设计一个信号系统来在所有蜘蛛都完成后停止 react 器。 我试过了: cra
有没有办法在达到特定条件时停止扭曲 react 器。例如,如果一个变量被设置为某个值,那么 react 器应该停止吗? 最佳答案 理想情况下,您不会将变量设置为一个值并停止 react 器,而是调用
https://code.angularjs.org/1.0.0rc9/angular-1.0.0rc9.js 上面的链接定义了外部js文件,我不知道Angular-1.0.0rc9.js的注入(in
我正在尝试运行一个函数并将服务注入(inject)其中。我认为这可以使用 $injector 轻松完成.所以我尝试了以下(简化示例): angular.injector().invoke( [ "$q
在 google Guice 中,我可以使用函数 createInjector 创建基于多个模块的注入(inject)器。 因为我使用 GWT.create 在 GoogleGin 中实例化注入(in
我在 ASP.NET Core 1.1 解决方案中使用配置绑定(bind)。基本上,我在“ConfigureServices Startup”部分中有一些用于绑定(bind)的简单代码,如下所示: s
我在 Spring MVC 中设置 initBinder 时遇到一些问题。我有一个 ModelAttribute,它有一个有时会显示的字段。 public class Model { privat
我正在尝试通过jquery post发布knockoutjs View 模型 var $form = $('#barcodeTemplate form'); var data = ko.toJS(vm
如何为包含多态对象集合的复杂模型编写自定义模型绑定(bind)程序? 我有下一个模型结构: public class CustomAttributeValueViewModel { publi
您好,我正在尝试实现我在 this article 中找到的扩展方法对于简单的注入(inject)器,因为它不支持开箱即用的特定构造函数的注册。 根据这篇文章,我需要用一个假的委托(delegate)
你好,我想自动注册我的依赖项。 我现在拥有的是: public interface IRepository where T : class public interface IFolderReposi
我正在使用 Jasmine 测试一些 Angular.js 代码。为此,我需要一个 Angular 注入(inject)器: var injector = angular.injector(['ng'
我正在使用 Matlab 代码生成器。不可能包含代码风格指南。这就是为什么我正在寻找一个工具来“ reshape ”、重命名和重新格式化生成的代码,根据我的: 功能横幅约定 文件横幅约定 命名约定 等
这个问题在这里已经有了答案: Where and why do I have to put the "template" and "typename" keywords? (8 个答案) 关闭 8
我开发了一种工具,可以更改某些程序的外观。为此,我需要在某些进程中注入(inject)一个 dll。 现在我基本上使用这个 approach .问题通常是人们无法注入(inject) dll,因为他们
我想使用 swing、spring 和 hibernate 编写一个 java 应用程序。 我想使用数据绑定(bind)器用 bean 的值填充 gui,并且我还希望它反射(reflect) gui
我有这段代码,当两个蜘蛛完成后,程序仍在运行。 #!C:\Python27\python.exe from twisted.internet import reactor from scrapy.cr
要点是 Spring Batch (v2) 测试框架具有带有 @Autowired 注释的 JobLauncherTestUtils.setJob。我们的测试套件有多个 Job 类提供者。因为这个类不
我是一名优秀的程序员,十分优秀!