上篇实现了 EasyUI DataGrid 静态 JSON 展示,本篇打通「前端 DataGrid ↔ Struts2 Action ↔ Service ↔ DB」完整数据链路,实现分页、条件查询、关联对象序列化控制。
EasyUI DataGrid (url: 'category_queryJoinAccount.action', page, rows)
↓ HTTP GET/POST
Struts2 Dispatcher → CategoryAction.queryJoinAccount()
↓ 注入参数 (model.type, page, rows)
CategoryService.queryJoinAccount(type, page, rows) → Hibernate HQL 分页查询
↓ 返回 List (含关联 Account)
Action 封装 Map pageMap { total, rows }
↓ Struts2 JSON Result (root=pageMap, excludeProperties)
JSON 响应 → DataGrid 渲染
@Controller("baseAction")
@Scope("prototype")
public class BaseAction<T> extends ActionSupport
implements ModelDriven<T>, RequestAware, SessionAware, ApplicationAware {
// EasyUI 分页参数自动注入
protected Integer page; // 当前页
protected Integer rows; // 每页行数
// JSON 结果数据容器
protected Map<String, Object> pageMap = new HashMap<>();
// ModelDriven 自动实例化模型对象
@Override public T getModel() { ... }
// 域对象注入
@Override public void setRequest(Map<String, Object> request) { this.request = request; }
// ... session, application 同理
}
@Controller("categoryAction")
@Scope("prototype")
public class CategoryAction extends BaseAction<Category> {
@Resource CategoryService categoryService;
public String queryJoinAccount() {
pageMap = new HashMap<>();
// 1. 条件分页查询数据行
List<Category> rows = categoryService.queryJoinAccount(
model.getType(), page, rows);
pageMap.put("rows", rows);
// 2. 查询总记录数(用于分页控件)
Long total = categoryService.getCount(model.getType());
pageMap.put("total", total);
return "jsonMap"; // 对应 struts.xml result name
}
}
@Service("categoryService")
public class CategoryServiceImpl extends BaseServiceImpl<Category>
implements CategoryService {
@Override
public List<Category> queryJoinAccount(String type, int page, int size) {
String hql = "FROM Category c LEFT JOIN FETCH c.account WHERE c.type LIKE :type";
return getSession().createQuery(hql)
.setString("type", "%" + type + "%")
.setFirstResult((page - 1) * size)
.setMaxResults(size)
.list();
}
@Override
public Long getCount(String type) {
String hql = "SELECT COUNT(c) FROM Category c WHERE c.type LIKE :type";
return (Long) getSession().createQuery(hql)
.setString("type", "%" + type + "%")
.uniqueResult();
}
}
<package name="shop" extends="json-default"> <!-- 继承 json-default 包含 struts2-json-plugin -->
<action name="category_*" class="categoryAction" method="{1}">
<result name="jsonMap" type="json">
<!-- 指定序列化根对象 -->
<param name="root">pageMap</param>
<!-- 排除敏感/循环引用字段:Account.password -->
<param name="excludeProperties">
rows\[\d+\]\.account\.password
</param>
</result>
</action>
</package>
关键点:
extends="json-default":必须继承,提供 type="json" Result 类型root=pageMap:只序列化 Map,避免序列化整个 Action 对象excludeProperties:正则排除字段,rows\[\d+\]\.account\.password 匹配列表中每个元素的关联对象密码字段,防止敏感信息泄露$('#dg').datagrid({
url: 'category_queryJoinAccount.action',
loadMsg: 'Loading......',
queryParams: { type: '' }, // 传递查询条件
fitColumns: true,
striped: true,
nowrap: true,
singleSelect: true,
pagination: true, // 启用分页工具栏
// 行样式交替
rowStyler: function(index, row) {
return index % 2 == 0 ? 'background:#fff' : 'background:#ffffe0';
},
// 冻结列(复选框+ID)
frozenColumns: [[
{ field: 'ck', checkbox: true },
{ field: 'id', title: '编号', width: 80 }
]],
// 普通列
columns: [[
{
field: 'type', title: '分类名称', width: 120,
formatter: (v, r, i) => '<span title="' + v + '">' + v + '</span>'
},
{
field: 'hot', title: '热销', width: 80,
formatter: (v, r, i) => v
? '<input type="checkbox" checked disabled>'
: '<input type="checkbox" disabled>'
},
{
field: 'account.login', title: '所属管理员', width: 150,
formatter: (v, r, i) => r.account?.login ?? '无管理员'
}
]]
});
{
"total": 25,
"rows": [
{
"id": 1,
"type": "手机数码",
"hot": true,
"account": { "id": 2, "login": "admin", "name": "管理员A" } // password 已排除
},
...
]
}
| 现象 | 原因 | 解决 |
|---|---|---|
| DataGrid 显示 "total:0, rows:[]" | root 对象不包含 total/rows 字段 | 检查 Action 封装 Map 键名、struts.xml root 配置 |
| 分页工具栏不显示/页码错乱 | total 值类型不对或未返回 | 确保 Service 返回 Long,Action 放入 Map |
| 关联对象序列化报错/死循环 | 双向关联循环引用 | excludeProperties 排除反向引用,或实体加 @JsonIgnore |
| 中文乱码 | 响应编码未设置 | struts.xml <constant name="struts.i18n.encoding" value="UTF-8"/> |
Struts2 整合 JSON 核心在于:Action 封装标准 Map 结构 → json-default 包提供序列化 → excludeProperties 精准控制输出。配合 EasyUI DataGrid 标准协议(total+rows),实现零胶水代码的前后端数据交互。下一步可扩展:编辑器联动、导出 Excel、批量操作等企业级功能。