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

本篇实现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'" 
                  οnclick="javascript:onSubmit()">查询</a>
</form>
<div>
    <table id="datagrid_payout"></table>
</div>

下面看几张效果图:



猜你喜欢

转载自blog.csdn.net/u012071890/article/details/16879061