实现查询与分页

本文是《轻量级 Java Web 框架架构设计》的系列博文。

之前做过一个 CRUD 示例(点击这里打开),但没有提供查询与分页功能,有些网友也提到了这个问题。今天我要做的就是,让这个示例更加全面,让它支持查询与分页。

思路反过来,我们先看页面:

这里提供了一个 Search 框,还有一个翻页控件。

要实现的目标是:使用 Ajax 效果实现查询与分页。

先看 Action 吧:

@Bean
public class ProductAction extends BaseAction {

    ...

    @Request("post:/products")
    public Result searchProducts(Map<String, String> formFieldMap) {
        int pageNumber = CastUtil.castInt(formFieldMap.get("pageNumber"));
        int pageSize = CastUtil.castInt(formFieldMap.get("pageSize"));
        String queryString = formFieldMap.get("queryString");

        Map<String, String> queryMap = WebUtil.createQueryMap(queryString);

        Pager<Product> productPager = productService.searchProductPager(pageNumber, pageSize, queryMap);
        return new Result(true).data(productPager);
    }
}

为了实现查询,需要在 ProductAction 中增加一个用于查询的 searchProducts() 方法。

首先,该方法接收 POST 类型的 /products 请求,通过 Map<String, String> formFieldMap 参数,从前端 form 中获取相应的数据。包括:

  • pageNumber:页面编号
  • pageSize:每页条数
  • queryString:查询字符串(格式:name1:value1;name2:value2;...)

然后,调用 WebUtil.createQueryMap() 方法,从 queryString 中创建 queryMap,也就是将 queryString 中的分号和冒号分隔成 Map 的形式并返回。

最后,调用 productService 的 searchProductPager() 方法,将相关参数传入其中,返回一个 Pager<Product> 对象,这个也就是封装的分页对象了。

Pager 类代码如下:

public class Pager<T> extends BaseBean {

    private int pageNumber;     // 页面编号
    private int pageSize;       // 每页条数
    private int totalRecord;    // 总记录数
    private List<T> recordList; // 数据列表

    public Pager(int pageNumber, int pageSize, int totalRecord, List<T> recordList) {
        this.pageNumber = pageNumber;
        this.pageSize = pageSize;
        this.totalRecord = totalRecord;
        this.recordList = recordList;
    }

    public int getTotalPage() {
        return totalRecord % pageSize == 0 ? totalRecord / pageSize : totalRecord / pageSize + 1;
    }

    public int getPageNumber() {
        return pageNumber;
    }

    public void setPageNumber(int pageNumber) {
        this.pageNumber = pageNumber;
    }

    ...
}

Pager 类封装了所有与分页相关的数据,并提供 getTotalPage() 方法,方便获取总页面数,以供前端显示。

返回的 Result 对象将被序列化为 JSON 对象,这是由 Smart Framework 提供的。返回的 JSON 数据大致是这样的:

{
    success: true,
    error: 0,
    data: {
        pageNumber: 1,
        pageSize: 10,
        totalRecord: 13,
        recordList: [
            {
                id: 1,
                productTypeId: 1,
                productName: "iPhone 3gs",
                productCode: "MP001",
                price: 3500,
                description: "iPhone 3gs 移动电话"
            },
            ...
        ],
        totalPage: 2
    }
}

当 JS 通过 AJAX 拿到以上数据后,剩下的事情就是将这些数据进行解析并渲染到界面中。实现方式比较多,这里就不做深入了。总之,数据拿到了界面展现就容易了。

可能有人会问:翻页是需要做 SQL 查询的,这些代码写在哪里呢?

关于分页的 SQL 语句,是平台给封装好了,目前实现了 MySQL、Oracle、SQL Server 三种,对于其他数据库也非常容易扩展。当然,这些都由架构师或高级程序员负责扩展的,普通程序员只需要知道怎么用就行了。下面就是具体的用法。在 ProductServcie 接口中,添加了一个 searchProductPager() 方法,并完成实现类:

@Bean
public class ProductServiceImpl extends BaseService implements ProductService {

    ...

    @Override
    public Pager<Product> searchProductPager(int pageNumber, int pageSize, Map<String, String> queryMap) {
        String where = "";
        if (MapUtil.isNotEmpty(queryMap)) {
            String productName = queryMap.get("productName");
            if (StringUtil.isNotEmpty(productName)) {
                where += "product_name like '%" + productName + "%'";
            }
        }

        String sort = "id asc";

        int totalRecord = DataSet.selectCount(Product.class, where);
        List<Product> productList = DataSet.selectListForPager(pageNumber, pageSize, Product.class, where, sort);

        return new Pager<Product>(pageNumber, pageSize, totalRecord, productList);
    }
}

首先,从 queryMap 中获取相关的参数,这里只有一个 productName 参数,然后拼成 SQL 查询条件。

然后,定义排序方式。注意:在某些情况下,需要指定排序字段,尤其对于 Oracle 数据库而言。

随后,调用 DataSet.selectCount() 方法,获取 totalRecord(总记录数);调用 DataSet.selectListForPager() 方法获取 productList(分页列表)。

最后,将相关数据封装为 Pager<Product> 对象并返回。

以上是实现查询与分页要做的事情,是不是很简单,很 Smart 呢?欢迎您的评论!


补充(2013-09-19)

以上示例中只是对 product 表进行查询(单表查询),注意在返回的结果集中有一个 product_type_id 字段,直接将该字段的值显示在界面上,恐怕不太友好,有必要将其转换为 product_type 表的 product_type_name 字段并显示在界面上。这样必然会出现 product 表连接 product_type 表的一个连接查询或子查询,如果将 SQL 语句写在程序中或配置文件中当然是一种解决方案,但我并没有这样去做。

以下我的解决方案:

首先,需要对 ProductService 接口的以下方法进行了重构:

Pager<Product> searchProductPager(int pageNumber, int pageSize, Map<String, String> queryMap)

重构后的代码如下:

Pager<ProductBean> searchProductPager(int pageNumber, int pageSize, Map<String, String> formFieldMap);

这里将 Product 重构为 ProductBean,它是一个典型的 JavaBean。代码如下:

public class ProductBean extends BaseBean {

    private Product product;
    private ProductType productType;

    public ProductBean(Product product, ProductType productType) {
        this.product = product;
        this.productType = productType;
    }

    ...
}

可见,ProductBean 封装了 Product 与 ProductType 这两个实体。

然后,要做的就是如何初始化这个 ProductBean,并将其作为 List 放入 Pager 对象中,从而实现分页的效果。

需要对 searchProductPager() 方法的实现代码进行如下改动:

...
    @Override
    public Pager<ProductBean> searchProductPager(int pageNumber, int pageSize, Map<String, String> queryMap) {
        String where = "";
        if (MapUtil.isNotEmpty(queryMap)) {
            String productName = queryMap.get("productName");
            if (StringUtil.isNotEmpty(productName)) {
                where += "product_name like '%" + productName + "%'";
            }
        }

        String sort = "id asc";

        int totalRecord = DataSet.selectCount(Product.class, where);
        List<Product> productList = DataSet.selectListForPager(pageNumber, pageSize, Product.class, where, sort);

        List<ProductBean> productBeanList = new ArrayList<ProductBean>();
        Map<Long, ProductType> productTypeMap = DataSet.selectMap(ProductType.class, "");
        for (Product product : productList) {
            ProductType productType = productTypeMap.get(product.getProductTypeId());
            if (productType != null) {
                productBeanList.add(new ProductBean(product, productType));
            }
        }

        return new Pager<ProductBean>(pageNumber, pageSize, totalRecord, productBeanList);
    }
...

从 17 行代码开始是我做的修改。首先定义了一个 productBeanList,空的,下面的代码就是为它初始化。然后调用 DataSet.selectMap() 方法获取一个 productTypeMap,它是 ProductType 实体的主键 ID 与 ProductType 对象的之间建立的映射关系。随后遍历 productList,并初始化 productBeanList,在遍历的过程中,需要借助 productTypeMap,通过 ID 来获取 ProductType 对象。最后将初始化后的 productBeanList 放入 Pager 中。

在 DataSet 类中增加了 selectMap() 方法:

...
    public static <T> Map<Long, T> selectMap(Class<T> cls, String condition, Object... params) {
        Map<Long, T> map = new HashMap<Long, T>();
        List<T> list = selectList(cls, condition, "", params);
        for (T obj : list) {
            Long id = CastUtil.castLong(ObjectUtil.getFieldValue(obj, "id"));
            map.put(id, obj);
        }
        return map;
    }
...

实际上还是调用 selectList() 方法,拿到一个 list,再遍历这个 list,去初始化 map,最后将 map 返回。需要注意的是,这里通过反射获取了实体的 id 字段。这种做法有个问题,实体必须有一个 long 型的 id 主键字段,如果它的名称或类型改了,运行时这行代码肯定会报错。需要在修改实体主键的时候,也顺便修改一下这一行代码。目前找不到更好的解决方案了。

现在返回的 JSON 是这样的:

{
    success: true,
    error: 0,
    data: {
        pageNumber: 1,
        pageSize: 10,
        totalRecord: 13,
        recordList: [
            {
                "product": {
                    "id": 1,
                    "productTypeId": 1,
                    "productName": "iPhone 3gs",
                    "productCode": "MP001",
                    "price": 3500,
                    "description": "iPhone 3gs 移动电话"
                },
                "productType": {
                    "id": 1,
                    "productTypeName": "Mobile Phone",
                    "productTypeCode": "MP"
                }
            },
            ...
        ],
        totalPage: 2
    }
}

最后看看 HTML 因如何改动:

以前:

...
    <tr data-id="{id}" data-name="${productName}">
        <td>{productTypeId}</td>
        <td>{productName}</td>
        <td>{productCode}</td>
        <td>{price}</td>
        <td>{description}</td>
        <td>
            <a href="#" class="ext-product-edit">Edit</a>
            <a href="#" class="ext-product-delete">Delete</a>
        </td>
    </tr>
...

现在:

...
    <tr data-id="{product.id}" data-name="${product.productName}">
        <td>{productType.productTypeName}</td>
        <td>{product.productName}</td>
        <td>{product.productCode}</td>
        <td>{product.price}</td>
        <td>{product.description}</td>
        <td>
            <a href="#" class="ext-product-edit">Edit</a>
            <a href="#" class="ext-product-delete">Delete</a>
        </td>
    </tr>
...

这里使用了 HTML 模板技术来生成相关 HTML 代码。

转载于:https://my.oschina.net/huangyong/blog/162027

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值