- 浏览: 991663 次
- 性别:
- 来自: 上海
文章分类
- 全部博客 (1441)
- 软件思想&演讲 (9)
- 行业常识 (250)
- 时时疑问 (5)
- java/guava/python/php/ruby/R/scala/groovy (213)
- struct/spring/springmvc (37)
- mybatis/hibernate/JPA (10)
- mysql/oracle/sqlserver/db2/mongdb/redis/neo4j/GreenPlum/Teradata/hsqldb/Derby/sakila (268)
- js/jquery/jqueryUi/jqueryEaseyUI/extjs/angulrJs/react/es6/grunt/zepto/raphael (81)
- ZMQ/RabbitMQ/ActiveMQ/JMS/kafka (17)
- lucene/solr/nuth/elasticsearch/MG4J (167)
- html/css/ionic/nodejs/bootstrap (19)
- Linux/shell/centos (56)
- cvs/svn/git/sourceTree/gradle/ant/maven/mantis/docker/Kubernetes (26)
- sonatype nexus (1)
- tomcat/jetty/netty/jboss (9)
- 工具 (17)
- ETL/SPASS/MATLAB/RapidMiner/weka/kettle/DataX/Kylin (11)
- hadoop/spark/Hbase/Hive/pig/Zookeeper/HAWQ/cloudera/Impala/Oozie (190)
- ios/swift/android (9)
- 机器学习&算法&大数据 (18)
- Mesos是Apache下的开源分布式资源管理框架 (1)
- echarts/d3/highCharts/tableau (1)
- 行业技能图谱 (1)
- 大数据可视化 (2)
- tornado/ansible/twisted (2)
- Nagios/Cacti/Zabbix (0)
- eclipse/intellijIDEA/webstorm (5)
- cvs/svn/git/sourceTree/gradle/jira/bitbucket (4)
- jsp/jsf/flex/ZKoss (0)
- 测试技术 (2)
- splunk/flunm (2)
- 高并发/大数据量 (1)
- freemarker/vector/thymeleaf (1)
- docker/Kubernetes (2)
- dubbo/ESB/dubboX/wso2 (2)
最新评论
Mybatis的分页功能很弱,它是基于内存的分页(查出所有记录再按偏移量和limit取结果),在大数据量的情况下这样的分页基本上是没有用的。本文基于插件,通过拦截StatementHandler重写sql语句,实现数据库的物理分页。本文适配的mybatis版本是3.2.2。
为什么在StatementHandler拦截
在深入浅出MyBatis-Sqlsession章节介绍了一次sqlsession的完整执行过程,从中可以知道sql的解析是在StatementHandler里完成的,所以为了重写sql需要拦截StatementHandler。
MetaObject简介
在我的实现里大量使用了MetaObject这个对象,因此有必要先介绍下它。MetaObject是Mybatis提供的一个的工具类,通过它包装一个对象后可以获取或设置该对象的原本不可访问的属性(比如那些私有属性)。它有个三个重要方法经常用到:
1) MetaObject forObject(Object object,ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory)
2) Object getValue(String name)
3) void setValue(String name, Object value)
方法1)用于包装对象;方法2)用于获取属性的值(支持OGNL的方法);方法3)用于设置属性的值(支持OGNL的方法);
插件的原理
拦截器签名
[java] view plaincopy
01.@Intercepts({@Signature(type =StatementHandler.class, method = "prepare", args ={Connection.class})})
02.publicclass PageInterceptor implementsInterceptor {
03....
04.}
从签名里可以看出,要拦截的目标类型是StatementHandler(注意:type只能配置成接口类型),拦截的方法是名称为prepare参数为Connection类型的方法。
intercept的实现
[java] view plaincopy
01.public Object intercept(Invocation invocation) throws Throwable {
02. StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
03. MetaObject metaStatementHandler = MetaObject.forObject(statementHandler,
04. DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY);
05. // 分离代理对象链(由于目标类可能被多个拦截器拦截,从而形成多次代理,通过下面的两次循环
06. // 可以分离出最原始的的目标类)
07. while (metaStatementHandler.hasGetter("h")) {
08. Object object = metaStatementHandler.getValue("h");
09. metaStatementHandler = MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY,
10. DEFAULT_OBJECT_WRAPPER_FACTORY);
11. }
12. // 分离最后一个代理对象的目标类
13. while (metaStatementHandler.hasGetter("target")) {
14. Object object = metaStatementHandler.getValue("target");
15. metaStatementHandler = MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY,
16. DEFAULT_OBJECT_WRAPPER_FACTORY);
17. }
18. Configuration configuration = (Configuration) metaStatementHandler.
19. getValue("delegate.configuration");
20. dialect = configuration.getVariables().getProperty("dialect");
21. if (null == dialect || "".equals(dialect)) {
22. logger.warn("Property dialect is not setted,use default 'mysql' ");
23. dialect = defaultDialect;
24. }
25. pageSqlId = configuration.getVariables().getProperty("pageSqlId");
26. if (null == pageSqlId || "".equals(pageSqlId)) {
27. logger.warn("Property pageSqlId is not setted,use default '.*Page$' ");
28. pageSqlId = defaultPageSqlId;
29. }
30. MappedStatement mappedStatement = (MappedStatement)
31. metaStatementHandler.getValue("delegate.mappedStatement");
32. // 只重写需要分页的sql语句。通过MappedStatement的ID匹配,默认重写以Page结尾的
33. // MappedStatement的sql
34. if (mappedStatement.getId().matches(pageSqlId)) {
35. BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
36. Object parameterObject = boundSql.getParameterObject();
37. if (parameterObject == null) {
38. throw new NullPointerException("parameterObject is null!");
39. } else {
40. // 分页参数作为参数对象parameterObject的一个属性
41. PageParameter page = (PageParameter) metaStatementHandler
42. .getValue("delegate.boundSql.parameterObject.page");
43. String sql = boundSql.getSql();
44. // 重写sql
45. String pageSql = buildPageSql(sql, page);
46. metaStatementHandler.setValue("delegate.boundSql.sql", pageSql);
47. // 采用物理分页后,就不需要mybatis的内存分页了,所以重置下面的两个参数
48. metaStatementHandler.setValue("delegate.rowBounds.offset",
49. RowBounds.NO_ROW_OFFSET);
50. metaStatementHandler.setValue("delegate.rowBounds.limit", RowBounds.NO_ROW_LIMIT);
51. Connection connection = (Connection) invocation.getArgs()[0];
52. // 重设分页参数里的总页数等
53. setPageParameter(sql, connection, mappedStatement, boundSql, page);
54. }
55. }
56. // 将执行权交给下一个拦截器
57. return invocation.proceed();
58. }
StatementHandler的默认实现类是RoutingStatementHandler,因此拦截的实际对象是它。RoutingStatementHandler的主要功能是分发,它根据配置Statement类型创建真正执行数据库操作的StatementHandler,并将其保存到delegate属性里。由于delegate是一个私有属性并且没有提供访问它的方法,因此需要借助MetaObject的帮忙。通过MetaObject的封装后我们可以轻易的获得想要的属性。
在上面的方法里有个两个循环,通过他们可以分离出原始的RoutingStatementHandler(而不是代理对象)。
前面提到,签名里配置的要拦截的目标类型是StatementHandler拦截的方法是名称为prepare参数为Connection类型的方法,而这个方法是每次数据库访问都要执行的。因为我是通过重写sql的方式实现分页,为了不影响其他sql(update或不需要分页的query),我采用了通过ID匹配的方式过滤。默认的过滤方式只对id以Page结尾的进行拦截(注意区分大小写),如下:
[html] view plaincopy
01.<select id="queryUserByPage" parameterType="UserDto" resultType="UserDto">
02. <![CDATA[
03. select * from t_user t where t.username = #{username}
04. ]]>
05.</select>
当然,也可以自定义拦截模式,在mybatis的配置文件里加入以下配置项:
[html] view plaincopy
01.<properties>
02. <property name="dialect" value="mysql" />
03. <property name="pageSqlId" value=".*Page$" />
04.</properties>
其中,属性dialect指示数据库类型,目前只支持mysql和oracle两种数据库。其中,属性pageSqlId指示拦截的规则,以正则方式匹配。
sql重写
sql重写其实在原始的sql语句上加入分页的参数,目前支持mysql和oracle两种数据库的分页。
[java] view plaincopy
01.private String buildPageSql(String sql, PageParameter page) {
02. if (page != null) {
03. StringBuilder pageSql = new StringBuilder();
04. if ("mysql".equals(dialect)) {
05. pageSql = buildPageSqlForMysql(sql, page);
06. } else if ("oracle".equals(dialect)) {
07. pageSql = buildPageSqlForOracle(sql, page);
08. } else {
09. return sql;
10. }
11. return pageSql.toString();
12. } else {
13. return sql;
14. }
15.}
mysql的分页实现:
[java] view plaincopy
01.public StringBuilder buildPageSqlForMysql(String sql, PageParameter page) {
02. StringBuilder pageSql = new StringBuilder(100);
03. String beginrow = String.valueOf((page.getCurrentPage() - 1) * page.getPageSize());
04. pageSql.append(sql);
05. pageSql.append(" limit " + beginrow + "," + page.getPageSize());
06. return pageSql;
07.}
oracle的分页实现:
[java] view plaincopy
01.public StringBuilder buildPageSqlForOracle(String sql, PageParameter page) {
02. StringBuilder pageSql = new StringBuilder(100);
03. String beginrow = String.valueOf((page.getCurrentPage() - 1) * page.getPageSize());
04. String endrow = String.valueOf(page.getCurrentPage() * page.getPageSize());
05. pageSql.append("select * from ( select temp.*, rownum row_id from ( ");
06. pageSql.append(sql);
07. pageSql.append(" ) temp where rownum <= ").append(endrow);
08. pageSql.append(") where row_id > ").append(beginrow);
09. return pageSql;
10.}
分页参数重写
有时候会有这种需求,就是不但要查出指定页的结果,还需要知道总的记录数和页数。我通过重写分页参数的方式提供了一种解决方案:
[java] view plaincopy
01./**
02. * 从数据库里查询总的记录数并计算总页数,回写进分页参数<code>PageParameter</code>,这样调用
03. * 者就可用通过 分页参数<code>PageParameter</code>获得相关信息。
04. *
05. * @param sql
06. * @param connection
07. * @param mappedStatement
08. * @param boundSql
09. * @param page
10. */
11.private void setPageParameter(String sql, Connection connection, MappedStatement mappedStatement,
12. BoundSql boundSql, PageParameter page) {
13. // 记录总记录数
14. String countSql = "select count(0) from (" + sql + ") as total";
15. PreparedStatement countStmt = null;
16. ResultSet rs = null;
17. try {
18. countStmt = connection.prepareStatement(countSql);
19. BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql,
20. boundSql.getParameterMappings(), boundSql.getParameterObject());
21. setParameters(countStmt, mappedStatement, countBS, boundSql.getParameterObject());
22. rs = countStmt.executeQuery();
23. int totalCount = 0;
24. if (rs.next()) {
25. totalCount = rs.getInt(1);
26. }
27. page.setTotalCount(totalCount);
28. int totalPage = totalCount / page.getPageSize() + ((totalCount % page.getPageSize() == 0) ? 0 : 1);
29. page.setTotalPage(totalPage);
30. } catch (SQLException e) {
31. logger.error("Ignore this exception", e);
32. } finally {
33. try {
34. rs.close();
35. } catch (SQLException e) {
36. logger.error("Ignore this exception", e);
37. }
38. try {
39. countStmt.close();
40. } catch (SQLException e) {
41. logger.error("Ignore this exception", e);
42. }
43. }
44.}
45.
46./**
47. * 对SQL参数(?)设值
48. *
49. * @param ps
50. * @param mappedStatement
51. * @param boundSql
52. * @param parameterObject
53. * @throws SQLException
54. */
55.private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql,
56. Object parameterObject) throws SQLException {
57. ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
58. parameterHandler.setParameters(ps);
59.}
plugin的实现
[java] view plaincopy
01.public Object plugin(Object target) {
02. // 当目标类是StatementHandler类型时,才包装目标类,否者直接返回目标本身,减少目标被代理的
03. // 次数
04. if (target instanceof StatementHandler) {
05. return Plugin.wrap(target, this);
06. } else {
07. return target;
08. }
09.}
为什么在StatementHandler拦截
在深入浅出MyBatis-Sqlsession章节介绍了一次sqlsession的完整执行过程,从中可以知道sql的解析是在StatementHandler里完成的,所以为了重写sql需要拦截StatementHandler。
MetaObject简介
在我的实现里大量使用了MetaObject这个对象,因此有必要先介绍下它。MetaObject是Mybatis提供的一个的工具类,通过它包装一个对象后可以获取或设置该对象的原本不可访问的属性(比如那些私有属性)。它有个三个重要方法经常用到:
1) MetaObject forObject(Object object,ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory)
2) Object getValue(String name)
3) void setValue(String name, Object value)
方法1)用于包装对象;方法2)用于获取属性的值(支持OGNL的方法);方法3)用于设置属性的值(支持OGNL的方法);
插件的原理
拦截器签名
[java] view plaincopy
01.@Intercepts({@Signature(type =StatementHandler.class, method = "prepare", args ={Connection.class})})
02.publicclass PageInterceptor implementsInterceptor {
03....
04.}
从签名里可以看出,要拦截的目标类型是StatementHandler(注意:type只能配置成接口类型),拦截的方法是名称为prepare参数为Connection类型的方法。
intercept的实现
[java] view plaincopy
01.public Object intercept(Invocation invocation) throws Throwable {
02. StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
03. MetaObject metaStatementHandler = MetaObject.forObject(statementHandler,
04. DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY);
05. // 分离代理对象链(由于目标类可能被多个拦截器拦截,从而形成多次代理,通过下面的两次循环
06. // 可以分离出最原始的的目标类)
07. while (metaStatementHandler.hasGetter("h")) {
08. Object object = metaStatementHandler.getValue("h");
09. metaStatementHandler = MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY,
10. DEFAULT_OBJECT_WRAPPER_FACTORY);
11. }
12. // 分离最后一个代理对象的目标类
13. while (metaStatementHandler.hasGetter("target")) {
14. Object object = metaStatementHandler.getValue("target");
15. metaStatementHandler = MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY,
16. DEFAULT_OBJECT_WRAPPER_FACTORY);
17. }
18. Configuration configuration = (Configuration) metaStatementHandler.
19. getValue("delegate.configuration");
20. dialect = configuration.getVariables().getProperty("dialect");
21. if (null == dialect || "".equals(dialect)) {
22. logger.warn("Property dialect is not setted,use default 'mysql' ");
23. dialect = defaultDialect;
24. }
25. pageSqlId = configuration.getVariables().getProperty("pageSqlId");
26. if (null == pageSqlId || "".equals(pageSqlId)) {
27. logger.warn("Property pageSqlId is not setted,use default '.*Page$' ");
28. pageSqlId = defaultPageSqlId;
29. }
30. MappedStatement mappedStatement = (MappedStatement)
31. metaStatementHandler.getValue("delegate.mappedStatement");
32. // 只重写需要分页的sql语句。通过MappedStatement的ID匹配,默认重写以Page结尾的
33. // MappedStatement的sql
34. if (mappedStatement.getId().matches(pageSqlId)) {
35. BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
36. Object parameterObject = boundSql.getParameterObject();
37. if (parameterObject == null) {
38. throw new NullPointerException("parameterObject is null!");
39. } else {
40. // 分页参数作为参数对象parameterObject的一个属性
41. PageParameter page = (PageParameter) metaStatementHandler
42. .getValue("delegate.boundSql.parameterObject.page");
43. String sql = boundSql.getSql();
44. // 重写sql
45. String pageSql = buildPageSql(sql, page);
46. metaStatementHandler.setValue("delegate.boundSql.sql", pageSql);
47. // 采用物理分页后,就不需要mybatis的内存分页了,所以重置下面的两个参数
48. metaStatementHandler.setValue("delegate.rowBounds.offset",
49. RowBounds.NO_ROW_OFFSET);
50. metaStatementHandler.setValue("delegate.rowBounds.limit", RowBounds.NO_ROW_LIMIT);
51. Connection connection = (Connection) invocation.getArgs()[0];
52. // 重设分页参数里的总页数等
53. setPageParameter(sql, connection, mappedStatement, boundSql, page);
54. }
55. }
56. // 将执行权交给下一个拦截器
57. return invocation.proceed();
58. }
StatementHandler的默认实现类是RoutingStatementHandler,因此拦截的实际对象是它。RoutingStatementHandler的主要功能是分发,它根据配置Statement类型创建真正执行数据库操作的StatementHandler,并将其保存到delegate属性里。由于delegate是一个私有属性并且没有提供访问它的方法,因此需要借助MetaObject的帮忙。通过MetaObject的封装后我们可以轻易的获得想要的属性。
在上面的方法里有个两个循环,通过他们可以分离出原始的RoutingStatementHandler(而不是代理对象)。
前面提到,签名里配置的要拦截的目标类型是StatementHandler拦截的方法是名称为prepare参数为Connection类型的方法,而这个方法是每次数据库访问都要执行的。因为我是通过重写sql的方式实现分页,为了不影响其他sql(update或不需要分页的query),我采用了通过ID匹配的方式过滤。默认的过滤方式只对id以Page结尾的进行拦截(注意区分大小写),如下:
[html] view plaincopy
01.<select id="queryUserByPage" parameterType="UserDto" resultType="UserDto">
02. <![CDATA[
03. select * from t_user t where t.username = #{username}
04. ]]>
05.</select>
当然,也可以自定义拦截模式,在mybatis的配置文件里加入以下配置项:
[html] view plaincopy
01.<properties>
02. <property name="dialect" value="mysql" />
03. <property name="pageSqlId" value=".*Page$" />
04.</properties>
其中,属性dialect指示数据库类型,目前只支持mysql和oracle两种数据库。其中,属性pageSqlId指示拦截的规则,以正则方式匹配。
sql重写
sql重写其实在原始的sql语句上加入分页的参数,目前支持mysql和oracle两种数据库的分页。
[java] view plaincopy
01.private String buildPageSql(String sql, PageParameter page) {
02. if (page != null) {
03. StringBuilder pageSql = new StringBuilder();
04. if ("mysql".equals(dialect)) {
05. pageSql = buildPageSqlForMysql(sql, page);
06. } else if ("oracle".equals(dialect)) {
07. pageSql = buildPageSqlForOracle(sql, page);
08. } else {
09. return sql;
10. }
11. return pageSql.toString();
12. } else {
13. return sql;
14. }
15.}
mysql的分页实现:
[java] view plaincopy
01.public StringBuilder buildPageSqlForMysql(String sql, PageParameter page) {
02. StringBuilder pageSql = new StringBuilder(100);
03. String beginrow = String.valueOf((page.getCurrentPage() - 1) * page.getPageSize());
04. pageSql.append(sql);
05. pageSql.append(" limit " + beginrow + "," + page.getPageSize());
06. return pageSql;
07.}
oracle的分页实现:
[java] view plaincopy
01.public StringBuilder buildPageSqlForOracle(String sql, PageParameter page) {
02. StringBuilder pageSql = new StringBuilder(100);
03. String beginrow = String.valueOf((page.getCurrentPage() - 1) * page.getPageSize());
04. String endrow = String.valueOf(page.getCurrentPage() * page.getPageSize());
05. pageSql.append("select * from ( select temp.*, rownum row_id from ( ");
06. pageSql.append(sql);
07. pageSql.append(" ) temp where rownum <= ").append(endrow);
08. pageSql.append(") where row_id > ").append(beginrow);
09. return pageSql;
10.}
分页参数重写
有时候会有这种需求,就是不但要查出指定页的结果,还需要知道总的记录数和页数。我通过重写分页参数的方式提供了一种解决方案:
[java] view plaincopy
01./**
02. * 从数据库里查询总的记录数并计算总页数,回写进分页参数<code>PageParameter</code>,这样调用
03. * 者就可用通过 分页参数<code>PageParameter</code>获得相关信息。
04. *
05. * @param sql
06. * @param connection
07. * @param mappedStatement
08. * @param boundSql
09. * @param page
10. */
11.private void setPageParameter(String sql, Connection connection, MappedStatement mappedStatement,
12. BoundSql boundSql, PageParameter page) {
13. // 记录总记录数
14. String countSql = "select count(0) from (" + sql + ") as total";
15. PreparedStatement countStmt = null;
16. ResultSet rs = null;
17. try {
18. countStmt = connection.prepareStatement(countSql);
19. BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql,
20. boundSql.getParameterMappings(), boundSql.getParameterObject());
21. setParameters(countStmt, mappedStatement, countBS, boundSql.getParameterObject());
22. rs = countStmt.executeQuery();
23. int totalCount = 0;
24. if (rs.next()) {
25. totalCount = rs.getInt(1);
26. }
27. page.setTotalCount(totalCount);
28. int totalPage = totalCount / page.getPageSize() + ((totalCount % page.getPageSize() == 0) ? 0 : 1);
29. page.setTotalPage(totalPage);
30. } catch (SQLException e) {
31. logger.error("Ignore this exception", e);
32. } finally {
33. try {
34. rs.close();
35. } catch (SQLException e) {
36. logger.error("Ignore this exception", e);
37. }
38. try {
39. countStmt.close();
40. } catch (SQLException e) {
41. logger.error("Ignore this exception", e);
42. }
43. }
44.}
45.
46./**
47. * 对SQL参数(?)设值
48. *
49. * @param ps
50. * @param mappedStatement
51. * @param boundSql
52. * @param parameterObject
53. * @throws SQLException
54. */
55.private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql,
56. Object parameterObject) throws SQLException {
57. ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
58. parameterHandler.setParameters(ps);
59.}
plugin的实现
[java] view plaincopy
01.public Object plugin(Object target) {
02. // 当目标类是StatementHandler类型时,才包装目标类,否者直接返回目标本身,减少目标被代理的
03. // 次数
04. if (target instanceof StatementHandler) {
05. return Plugin.wrap(target, this);
06. } else {
07. return target;
08. }
09.}
发表评论
-
mybatis的学习
2016-01-06 10:18 518首先,POJO /** * @Title: ... -
Hibernate 的Criteria
2017-01-01 23:34 4221、Criteria Hibernate 设计 ... -
hibernate的延时加载的get和load的区别
2017-01-01 23:34 266在hibernate中我们知道如果要从数据库中得到一个对象,通 ... -
hibernate的拦截器(Interceptor)
2017-01-01 23:34 1393拦截器(Interceptor) org.hibe ... -
hibernate原理
2015-12-15 15:06 4081.Hibernate是如何连接数据库 主要是 ... -
mybatis的配置config文件
2015-12-06 22:24 1003MyBatis是一个支持普通SQL查询,存储过程和高级映射的优 ... -
mybatis的配置文件
2015-12-06 21:58 651只为成功找方法,不为失败找借口! MyBatis学习总结(三 ... -
mybais分页
2015-12-06 15:02 568Mybatis 的物理分页是应用中的一个难点,特别是配合检索和 ... -
hibernatet特点
2015-11-23 00:38 285Hibernate优点 (1) 对象/关系数据库映射(ORM) ...
相关推荐
mybatis-plus-sample-pagination: 分页功能示例 mybatis-plus-sample-active-record: ActiveRecord示例 mybatis-plus-sample-sequence: Sequence示例 mybatis-plus-sample-execution-analysis: Sql执行分析示例
特性:无侵入、损耗小、强大的CRUD操作,支持lambda 形势调用、支持多种数据库,支持主键自动生成、支持ActiveRecord模式,支持自定义全局通用操作、支持关键词自动转义,内置代码生成器、内置分页插件、内置性能...
spring + springmvc + mybatis 整合 demo 及 mybatis-pagehelper分页 demo
mybatis-pagination mybatis分页 mybatis拦截器
mybatis-plus分页查询的实现示例
SpringBoot整合mybatis-plus实现多数据源的动态切换且支持分页查询,案例以postgresql和oracle数据库为数据源,分别使用mybatis-plus分页插件和pagehelper分页插件实现分页查询。
支持连表查询的mybatis-plus,mybatis-plus风格的连表操作提供 wrapper.leftJoin() wrapper.rightJoin()等操作
注册中心为nacos SpringBoot整合Mybatis-Plus分页查询+Oracle+Mysql+swagger2
springboot整合mybatis-plus实现多表分页查询,assocication和collection一对一,一对多关联。简单易懂,容易上手!
mybatis-plus分页查询 学习mybatis-plus在项目中的简单使用,代码生成器,分页查询,多数据源等
Spring4.2 + SpringMVC4.2 + Mybatis3.3 + Mybatis-Plus(Mybatis的插件,封装了CRUD和分页查询等功能) + log4j + shrio权限框架,可直接用于后台的开发。
前端html分页逻辑是手写的,用的是Thymeleaf模板引擎,后端用的是mybatis plus的内置分页功能
mybatis-plus实现了简易版的controller,service,mapper自动生成和分页 在com.example.demo.mp.MpGenerator直接执行main方法 备注:生成的mapper需要手动加上注解@Mapper,否则会报错 整个框架属于纯净版,没有...
mybatis-paging-1.0.jar 是mybatis的一个分页插件的jar包,一直没有搜索到,今天好不容易找到了,跟大家分享
MyBatis-Plus 生成增删改查分页等基础代码
NULL 博文链接:https://wang-ping001.iteye.com/blog/1918031
mybatis-generator。 亮点:支持分页;支持MySQL中的注释添加到生成的bean中
mybatis--代码生成器:可视化配置,可以选择生成分页、注解、灵活选择包路径
mybatis-plus分页查询
spring + springmvc + mybatis 整合 demo 及 mybatis-paginator分页 demo