您的位置:首页 > 编程语言 > Java开发

家庭财务管理系统实战4- mybatis分页查询功能改进实现

2013-11-22 10:55 666 查看
本篇实现mybatis的分页查询功能,结合easyui的datagrid的pagenation实现列表分页查询功能。本功能实现也是作者通过对网络上各种搜索结果借鉴的结果。

mybatis自带的分页查询功能是对查询结果在内存中进行分页,也就是说,先查询出所有符合条件的结果放到内存,然后再从内存中取出当前页的内容,这对大数据量查询来说存在内存溢出的风险。

本篇实现在数据库进行物理分页,就是只从数据库中查询复合条件的当前页数据。

首先看实体bean:

package system.homebank.entity;

import java.io.Serializable;

public class Payments implements Serializable
{
private static final long serialVersionUID = 1256273897024133599L;

private Integer id;
private String value;
private String name;
private String paymenttype;
private String unit;
private String unitname;
private String descript;
private String day;
private String crttime;
private String type;
private String typename;
public Integer getId()
{
return id;
}
public void setId(Integer id)
{
this.id = id;
}
public String getValue()
{
return value;
}
public void setValue(String value)
{
this.value = value;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getPaymenttype()
{
return paymenttype;
}
public void setPaymenttype(String paymenttype)
{
this.paymenttype = paymenttype;
}
public String getUnit()
{
return unit;
}
public void setUnit(String unit)
{
this.unit = unit;
}
public String getUnitname()
{
return unitname;
}
public void setUnitname(String unitname)
{
this.unitname = unitname;
}
public String getDescript()
{
return descript;
}
public void setDescript(String descript)
{
this.descript = descript;
}
public String getDay()
{
return day;
}
public void setDay(String day)
{
this.day = day;
}
public String getCrttime()
{
return crttime;
}
public void setCrttime(String crttime)
{
this.crttime = crttime;
}
public String getType()
{
return type;
}
public void setType(String type)
{
this.type = type;
}
public String getTypename()
{
return typename;
}
public void setTypename(String typename)
{
this.typename = typename;
}

}


对应的mapper.xml内容:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="system.homebank.mapper.PaymentsMapper" >
<resultMap id="paymentsResultMap" type="Payments">
<id property="id" column="id"/>
<result property="value" column="value"/>
<result property="name" column="name"/>
<result property="paymenttype" column="paymenttype"/>
<result property="unit" column="unit"/>
<result property="descript" column="descript"/>
<result property="day" column="day"/>
<result property="crttime" column="crttime"/>
<result property="type" column="type"/>
<result property="unitname" column="unitname"/>
<result property="typename" column="typename"/>
</resultMap>
<select id="queryPage" parameterType="Map" resultType="list" resultMap="paymentsResultMap">
select * from vpayments
<trim prefix="where" prefixOverrides="and |or">
<if test="paymenttype != null">
paymenttype = #{paymenttype}
</if>
<if test="name != null">
and name like #{name}
</if>
<if test="type != null">
and type = #{type}
</if>
<if test="day != null">
and day = #{day}
</if>
</trim>
order by day desc
</select>
<select id="getTotal" parameterType="Map" resultType="Integer">
select count(*) from vpayments
<trim prefix="where" prefixOverrides="and |or">
<if test="paymenttype != null">
paymenttype = #{paymenttype}
</if>
<if test="name != null">
and name like #{name}
</if>
<if test="type != null">
and type = #{type}
</if>
<if test="day != null">
and day = #{day}
</if>
</trim>
</select>
</mapper>


上面resultMap标签中定义了bean和库表字段的对应关系,下面定义了两个select语句,queryPage语句定义了查询当前页数据的语句,getTotal语句定义了查询总数的语句。

trim标签中的内容应该都很好理解,if用来判断是否有此过滤条件,prefix中定义了where前缀,prefixOverrides中定义了需要覆盖的串:比如,第一个if不符合,第二个if符合,那么这个sql语句就会是 类似 ...where and ...,所以定义了prefixOverrides=“and”,那么这个and就会被去掉。

准备工作做完了,下面实现将mybatis的内存分页转为数据库的物理分页。原理就是对mybatis生成sql的方法进行拦截,生成我们分页需要的sql,拦截器定义如下(网上查找的,直接拿来了,做了部分注释):

package system.homebank.interceptor;

import java.sql.Connection;
import java.util.Properties;

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.Configuration;
import org.apache.ibatis.session.RowBounds;

//拦截StatementHandler接口中的参数为Connection的prepare方法
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class})})
public class PageInterceptor implements Interceptor
{
private String databaseType;//数据库类型
private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
private static String DEFAULT_PAGE_SQL_ID = ".*Page$"; // 需要拦截的ID正则匹配

@Override
public Object intercept(Invocation invocation) throws Throwable
{
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
MetaObject metaStatementHandler = MetaObject.forObject(statementHandler, DEFAULT_OBJECT_FACTORY,
DEFAULT_OBJECT_WRAPPER_FACTORY);
RowBounds rowBounds = (RowBounds) metaStatementHandler.getValue("delegate.rowBounds");
// 分离代理对象链(由于目标类可能被多个拦截器拦截,从而形成多次代理,通过下面的两次循环可以分离出最原始的的目标类)
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);
}

// property在mybatis settings文件内配置
Configuration configuration = (Configuration) metaStatementHandler.getValue("delegate.configuration");

// 设置pageSqlId
String pageSqlId = "";
Properties p = configuration.getVariables();
if (p != null)
pageSqlId = p.getProperty("pageSqlId");
if (null == pageSqlId || "".equals(pageSqlId)) {
pageSqlId = DEFAULT_PAGE_SQL_ID;
}

MappedStatement mappedStatement = (MappedStatement)
metaStatementHandler.getValue("delegate.mappedStatement");
// 只重写需要分页的sql语句。通过MappedStatement的ID匹配,默认重写以Page结尾的MappedStatement的sql
if (mappedStatement.getId().matches(pageSqlId)) {
BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
Object parameterObject = boundSql.getParameterObject();
if (parameterObject == null) {
throw new NullPointerException("parameterObject is null!");
} else {
String sql = boundSql.getSql();
// 重写sql,变成类似select * from table where ... limit 0,20
if (this.databaseType.equals("mysql"))
sql = sql + " LIMIT " + rowBounds.getOffset() + "," + rowBounds.getLimit();
metaStatementHandler.setValue("delegate.boundSql.sql", sql);
// 采用物理分页后,就不需要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 target)
{
//目标类是StatementHandler的时候才拦截
if (target instanceof StatementHandler)
{
return Plugin.wrap(target, this);
}
else
{
return target;
}
}

@Override
public void setProperties(Properties properties)
{
this.databaseType = properties.getProperty("databaseType");
}

}


拦截器定义好了,在mybatis配置文件中加入如下内容:

<plugins>
<plugin interceptor="system.homebank.interceptor.PageInterceptor">
<property name="databaseType" value="mysql"/>
</plugin>
</plugins>


到此,准备工作真的完成了,下面看代码中如何调用。

首先看cotroller的代码:

package system.homebank.controller;

import java.util.HashMap;
import java.util.Map;

import javax.annotation.Resource;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import system.homebank.service.PaymentsService;

@Controller
@RequestMapping("/paymentsController")
public class PaymentsController
{
@Resource
private PaymentsService service;

@RequestMapping("/query.do")
@ResponseBody
public Object query(@RequestParam Map<String,Object> filter)
{
return this.service.query(filter);
}
}


service的代码:

package system.homebank.service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;

import org.springframework.stereotype.Service;

import system.homebank.dao.PaymentsDao;
import system.homebank.entity.Payments;
import system.homebank.model.Page;

@Service
public class PaymentsServiceImpl implements PaymentsService
{
@Resource
private PaymentsDao dao;

@Override
public Page query(Map<String, Object> filter)
{
int pageno = Integer.parseInt(filter.get("page").toString());
int rows = Integer.parseInt(filter.get("rows").toString());
int start = (pageno-1)*rows;

filter.remove("page");
filter.remove("rows");

Map<String, Object> map = new HashMap<String, Object>();
for (String o : filter.keySet())
{
if (filter.get(o) == null || filter.get(o).equals(""))
continue;
map.put(o, filter.get(o));
}
List<Payments> list = this.dao.query(map,start,rows);
int total = this.dao.getTotal(map);
Page page = new Page();
page.setRows(list);
page.setTotal(total);
return page;
}

}


dao代码:

package system.homebank.dao;

import java.util.List;
import java.util.Map;

import javax.annotation.Resource;

import org.apache.ibatis.session.RowBounds;
import org.springframework.stereotype.Repository;

import system.homebank.entity.Payments;
import system.homebank.mapper.PaymentsMapper;
import system.homebank.model.Page;

@Repository
public class PaymentsDaoImpl implements PaymentsDao
{
@Resource
private PaymentsMapper maper;

@Override
public List<Payments> query(Map<String, Object> filter, int start, int rows)
{
RowBounds rb = new RowBounds(start,rows);
return this.maper.queryPage(filter,rb);
}

@Override
public int getTotal(Map<String, Object> map)
{
return this.maper.getTotal(map);
}
}


mapper接口的定义:

package system.homebank.mapper;

import java.util.List;
import java.util.Map;

import org.apache.ibatis.session.RowBounds;

import system.homebank.entity.Payments;
import system.homebank.model.Page;

public interface PaymentsMapper  extends BaseMapper
{
public List<Payments> queryPage(Map<String, Object> filter, RowBounds rb);

public int getTotal(Map<String, Object> map);
}


下面是jsp文件的内容:

<%@ page language="java" contentType="text/html; charset=utf-8"
pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme() + "://"
+ request.getServerName() + ":" + request.getServerPort()
+ path ;
%>
<script>
$(function(){
$('#datagrid_payout').datagrid({
url:'<%=basePath%>/paymentsController/query.do?paymenttype=2',//收入支出数据是一个表,这个参数代表支出
singleSelect:true,
height:$('#tabs').height() - 50 - $('#btn').height(),//这个高度根据页面自动计算,好像又点问题,先不管了
pagination:true,
pageSize:20,
pageList:[10,20,30,50],
columns:[[
{field:'id',title:'id',hidden:true},
{field:'name',title:'支出名称',width:250},
{field:'value',title:'支出金额',width:100},
{field:'unitname',title:'单位',width:90},
{field:'typename',title:'类别',width:100},
{field:'day',title:'支出日期',width:80},
{field:'crttime',title:'记录时间',width:130},
{field:'descript',title:'描述',width:250}
]],
toolbar: [],
onLoadSuccess:function(data){
}
});
});
function onSubmit()
{
$('#datagrid_payout').datagrid('load',
{name:'%'+$('#name').val()+'%',
type:$('#type').combobox('getValue'),
day:$('#day').datebox('getValue').replace(/\-/g, '')}
);//点击搜索按钮的时候重新加载datagrid数据,并向后台发送检索参数
}

</script>
<form id="searchform" method="post">
<span>支出名称:</span><input id="name" name="name" />
<span>支出类型:</span><input id="type" name="type" class="easyui-combobox"
data-options="
valueField:'code',
textField:'codename',
url:'<%=basePath%>/commonController/listDatadictCata.do?catalog=payout'"/>
<span>支出日期:</span><input id="day" type="text" class="easyui-datebox" />
<a id="btn" href="#" class="easyui-linkbutton" data-options="iconCls:'icon-search'"
onclick="javascript:onSubmit()">查询</a>
</form>
<div>
<table id="datagrid_payout"></table>
</div>


下面看几张效果图:



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐