MyBatis的分页插件

发表时间:2018-03-05 11:36:23 浏览量( 15 ) 留言数( 0 )

学习目标:

1、了解MyBatis的

2、了解MyBatis的分页插件的使用


学习过程:

   分页查询经常使用,如果每一次都是自己写分页的语句比较麻烦,需要先统计再查询列表,而且不同语言的分页语法也不一样,所以最好能够把分页的算法封装,方便以后的使用,MyBatis并没有提供分页的算法,需要自己写插件扩展。

一、分页对象

public class Page extends org.apache.ibatis.session.RowBounds implements Serializable, Cloneable {

	private static final long serialVersionUID = 1L;
	private static int PAGE_SIZE_DEFAULT = 10; // 显示数目
	private int pageSize = PAGE_SIZE_DEFAULT; // 当页显示数目
	private int totalCount; // 总记录数
	private int currentPage; // 当前页
	private int offset; // 记录偏移量
	
	private boolean isHasNext;//是否有下一页
	private boolean isHasPre;//是否有上一页

二、分页插件

   这个是核心算法,前面的注解和接口类是MyBatis的标准写法,然后匹配需要拦截的SQL,如果方法名称带有Page,或者参数有Page等都会做分页的拦截算法,在后面使用的时候需要注意这一点。

   后面的算法其实也没有什么特别的,就是拦截后组装select count进行统计,然后根据方言在组装分页的算法。把结果放入到Page对象中。

@SuppressWarnings("unchecked")
@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class,Integer.class }) })
public class PagePlugin implements Interceptor {

	public final static Logger logger = LoggerFactory.getLogger(PagePlugin.class);

	private static Dialect dialectObject = null; // 数据库方言
	private static String pageSqlId = ""; // mapper.xml中需要拦截的ID(正则匹配)

	public Object intercept(Invocation ivk) throws Throwable {

		if (ivk.getTarget() instanceof RoutingStatementHandler) {
			RoutingStatementHandler statementHandler = (RoutingStatementHandler) ivk.getTarget();
			BaseStatementHandler delegate = (BaseStatementHandler) ReflectHelper.getValueByFieldName(statementHandler,
					"delegate");
			MappedStatement mappedStatement = (MappedStatement) ReflectHelper.getValueByFieldName(delegate,
					"mappedStatement");

			if (mappedStatement.getId().matches(pageSqlId)) { // 拦截需要分页的SQL
				BoundSql boundSql = delegate.getBoundSql();
				// 分页SQL<select>中parameterType属性对应的实体参数,即Mapper接口中执行分页方法的参数,该参数不得为空
				Object parameterObject = boundSql.getParameterObject();//
				if (parameterObject == null) {
					throw new NullPointerException("boundSql.getParameterObject() is null!");
				} else {

					// Page 可以直接作为参数,或者放在Map中,或者实体对象的属性都可以
					Page page = null;
					if (parameterObject instanceof Page) { // 参数就是Pages实体
						page = (Page) parameterObject;
					} else if (parameterObject instanceof Map) {
						for (Entry entry : (Set<Entry>) ((Map) parameterObject).entrySet()) {
							if (entry.getValue() instanceof Page) {
								page = (Page) entry.getValue();
								break;
							}
						}
					} else { // 参数为某个实体,该实体拥有Pages属性
						page = ReflectHelper.getValueByFieldType(parameterObject, Page.class);
						if (page == null) {
							return ivk.proceed();
						}
					}
					
					String sql = boundSql.getSql();
					logger.debug("原始的mybatis的sql===" + sql);
					PreparedStatement countStmt = null;
					ResultSet rs = null;
					try {
						Connection connection = (Connection) ivk.getArgs()[0];
						String countSql = "select count(1) from (" + sql + ") tmp_count"; // 记录统计
						countStmt = connection.prepareStatement(countSql);
						ReflectHelper.setValueByFieldName(boundSql, "sql", countSql);
						DefaultParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement,
								parameterObject, boundSql);
						parameterHandler.setParameters(countStmt);
						rs = countStmt.executeQuery();
						int count = 0;
						if (rs.next()) {
							count = ((Number) rs.getObject(1)).intValue();
						}
						// 把统计出来的总数放入 page对象中
						page.setTotalCount(count);
					} finally {
						try {
							rs.close();
						} catch (Exception e) {
						}
						try {
							countStmt.close();
						} catch (Exception e) {
						}
					}
					logger.debug("分页对象==="+page.toString());
					String pageSql = generatePagesSql(sql, page);
					logger.debug("分页后的的sql语句=======" + pageSql);
					ReflectHelper.setValueByFieldName(boundSql, "sql", pageSql); // 将分页sql语句反射回BoundSql.
				}
			}
		}
		return ivk.proceed();
	}

	/**
	 * 根据数据库方言,生成特定的分页sql
	 * 
	 * @param sql
	 * @param page
	 * @return
	 */
	private String generatePagesSql(String sql, Page page) {
		if (page != null && dialectObject != null) {
			return dialectObject.getLimitString(sql, page.getOffset(),
					page.getPageSize());
		}
		return sql;
	}

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

	public void setProperties(Properties p) {
		String dialect = ""; // 数据库方言
		dialect = p.getProperty("dialect");
		if (StringUtils.isEmpty(dialect)) {

			logger.error("PagePlugin=====没有这个方言定义,方法异常");

			// try {
			// throw new PropertyException("dialect property is not found!");
			// } catch (PropertyException e) {
			// Logger.error("PagePlugin=====setProperties方法异常", e);
			// }
		} else {
			try {
				dialectObject = (Dialect) Class.forName(dialect).getDeclaredConstructor().newInstance();
			} catch (Exception e) {
				throw new RuntimeException(dialect + ", init fail!\n" + e);
			}
		}
		pageSqlId = p.getProperty("pageSqlId");
		if (StringUtils.isEmpty(pageSqlId)) {
			/*
			 * try { throw new PropertyException(
			 * "pageSqlId property is not found!"); } catch (PropertyException
			 * e) { Logger.error("PagePlugin=====setProperties方法异常", e); }
			 */
		}
	}
}

三、MySql方言的分页算法

public class MySQLDialect extends Dialect{

	public boolean supportsLimitOffset(){
		return true;
	}
	
    public boolean supportsLimit() {   
        return true;   
    }  
    
	public String getLimitString(String sql, int offset,String offsetPlaceholder, int limit, String limitPlaceholder) {
		if (offset > 0) {   
        	return sql + " limit "+offsetPlaceholder+","+limitPlaceholder; 
        } else {   
            return sql + " limit "+limitPlaceholder;
        }  
	}   
  
}

四、应用

1、修改mybatis-config.xml配置,拦截Page命名的方法

	<plugins>
		<plugin interceptor="com.liubao.util.mybatis.plugin.PagePlugin">
			<property name="dialect" value="com.liubao.util.mybatis.dialet.MySQLDialect" />
			<property name="pageSqlId" value=".*Page.*" />
		</plugin>
	</plugins>

2、这里和上一节课不一样,直接使用接口的方法名称和映射文件的id对应即可,dao层不需要实现了。

	<!-- 只扫描有这个注解的Map接口 -->
	<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
		<property name="basePackage" value="com.liubao.dao" />
		<property name="annotationClass" value="org.springframework.stereotype.Repository"></property>
	</bean>

3、分页的查询和普通的查询一样就行了,只是名称要注意一下

	<select id="selectConditonForPage" parameterType="com.liubao.pojo.GoodsCondition" resultMap="goodsMap">
		select * from goods where 1=1
		<if test="goodsName != null">
			and goods_name like #{goodsName}
		</if>
		<if test="goodsDesc != null">
			and goods_desc like #{goodsDesc}
		</if>

	</select>

4、测试类

	@Test
	public void testListGoods() {
		
		GoodsCondition condition=new GoodsCondition();
		Page page=new Page();
		page.setCurrentPage(1);
		page.setPageSize(10);
		
		condition.setGoodsName("%测试%");
		
		condition.setPage(page);
		
		List<Goods>  goodses=goodsDao.selectConditonForPage(condition);
		
		for(Goods good:goodses) {
			System.out.println(good.getGoodsId()+":"+good.getGoodsName());
		}
		
		System.out.println(condition.getPage().getTotalPage());
		
	}

5、输出的结果

11:52:14.765 [main] DEBUG c.l.util.mybatis.plugin.PagePlugin - 原始的mybatis的sql===select * from goods where 1=1

 

and goods_name like ?

==>  Preparing: select count(1) from (select * from goods where 1=1 and goods_name like ?) tmp_count 

==> Parameters: %测试%(String)

<==    Columns: count(1)

<==        Row: 2

11:52:14.793 [main] DEBUG c.l.util.mybatis.plugin.PagePlugin - 分页对象===Pages [currentPage=1, pageSize=10,  totalCount=2]

11:52:14.794 [main] DEBUG c.l.util.mybatis.plugin.PagePlugin - 分页后的的sql语句=======select * from goods where 1=1

 

and goods_name like ? limit 10

==>  Preparing: select * from goods where 1=1 and goods_name like ? limit 10 

==> Parameters: %测试%(String)

<==    Columns: goods_id, goods_name, goods_cash, goods_desc

<==        Row: 10, 测试的, 12.20, 描述

<==        Row: 11, 测试的, 12.20, 描述

<==      Total: 2

Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@61c4eee0]

10:测试的

11:测试的

1