AEM Query Builder — Từ Lý Thuyết Đến Thực Hành (On-Premise)
Phạm vi: AEM 6.5 on-premise. Tập trung Query Builder, bỏ qua JCR-SQL2.
1. Nền Tảng: JCR và Cách Query Hoạt Động
JCR (Java Content Repository) là kho lưu trữ dạng cây phân cấp — mỗi node có primaryType, properties và child nodes. Mọi thứ trong AEM đều là node:
/content/mysite/en/about
├── jcr:primaryType = "cq:Page"
└── jcr:content
├── jcr:title = "About Us"
└── sling:resourceType = "mysite/components/page"Query Builder là lớp API nằm trên đỉnh của Oak query engine. Nó nhận một map key-value (predicates), dịch sang XPath, rồi Oak thực thi. Mọi query cuối cùng đều chạy qua Oak index.
QueryBuilder predicates → XPath → Oak engine → Oak Index (hoặc traversal)2. Hai Cách Sử Dụng Query Builder
REST API
GET http://localhost:4502/bin/querybuilder.json?
type=cq:Page
&path=/content/mysite
&property=jcr:content/jcr:title
&property.value=About
&p.limit=10Java API (Sling Model)
@OSGiService
private QueryBuilder queryBuilder;
public List<Hit> search(ResourceResolver resolver) {
Map<String, String> params = new HashMap<>();
params.put("type", "cq:Page");
params.put("path", "/content/mysite");
params.put("property", "jcr:content/jcr:title");
params.put("property.value", "About");
params.put("p.limit", "10");
params.put("p.guessTotal", "true");
Session session = resolver.adaptTo(Session.class);
Query query = queryBuilder.createQuery(PredicateGroup.create(params), session);
return query.getResult().getHits();
}Dùng QueryBuilder (thay vì
findResources) khi cần sanitize input từ user, vì nó xử lý escaping an toàn hơn.
3. Predicates Quan Trọng
path — Giới hạn phạm vi tìm kiếm
path=/content/mysite/en- Mặc định tìm toàn bộ subtree (tất cả con cháu)
path.exact=true→ chỉ match đúng path đópath.flat=true→ chỉ tìm direct children
Quan trọng: Luôn đặt
pathcàng hẹp càng tốt. Không cópath= quét toàn bộ/content.
type — Lọc theo node type
type=cq:Page
# hoặc
type=dam:Asset
type=cq:PageContentLưu ý thực tế:
typebao gồm cả subtypes.type=nt:unstructuredsẽ trả về rất nhiều kết quả không mong muốn. Ưu tiên dùngcq:Page,dam:Assetthay vì các type chung.
property — Lọc theo property
# Exact match
property=jcr:content/jcr:title
property.value=About Us
# Chứa chuỗi (LIKE)
property=jcr:content/jcr:title
property.operation=like
property.value=%About%
# Kiểm tra tồn tại
property=jcr:content/myFlag
property.operation=exists
property.value=true
# Multi-value: OR
property=jcr:content/status
property.1_value=published
property.2_value=approved
# Multi-value: AND
property=jcr:content/tags
property.and=true
property.1_value=tag1
property.2_value=tag2Các operation hỗ trợ: equals (default), unequals, like, not, exists.
daterange — Lọc theo khoảng thời gian
# Tìm content tạo trong tháng 1/2025
daterange.property=jcr:content/jcr:created
daterange.lowerBound=2025-01-01
daterange.lowerOperation=>=
daterange.upperBound=2025-01-31T23:59:59
daterange.upperOperation=<=Format: ISO8601 (YYYY-MM-DDTHH:mm:ss) hoặc POSIX timestamp.
relativedaterange — Khoảng thời gian tương đối
# Content được sửa trong 7 ngày qua
relativedaterange.property=jcr:content/cq:lastModified
relativedaterange.lowerBound=-7d
# upperBound mặc định = nowSyntax: 1s 2m 3h 4d 5w 6M 7y. Prefix - = trong quá khứ.
nodename — Lọc theo tên node
# Tìm tất cả file .jar
type=nt:file
nodename=*.jarHỗ trợ wildcard: * (0 hoặc nhiều ký tự), ? (1 ký tự), [abc] (ký tự trong tập hợp).
fulltext — Tìm kiếm toàn văn
fulltext=Adobe Experience Manager
# Chỉ tìm trong jcr:content
fulltext.relPath=jcr:contentDùng Lucene index, không hỗ trợ filtering. Cần fulltext index được cấu hình.
boolproperty — Property boolean
boolproperty=jcr:content/hideInNav
boolproperty.value=truefalse match cả node không có property đó.
tag và tagid — Tìm theo tag
# Theo tag ID
tagid=properties:orientation/landscape
tagid.property=jcr:content/cq:tags
# Theo tag title path
tag=properties:orientation/landscape4. Nhóm Logic: p.or và p.and
Mặc định tất cả predicates được AND với nhau. Dùng group để tạo điều kiện phức tạp:
OR trong một nhóm
# (title = "About") OR (navTitle = "About")
group.p.or=true
group.1_property=jcr:content/jcr:title
group.1_property.value=About
group.2_property=jcr:content/navTitle
group.2_property.value=AboutNested groups
# fulltext="AEM" AND (path1 OR path2)
fulltext=AEM
group.p.or=true
group.1_path=/content/mysite/en
group.2_path=/content/mysite/deConceptually: fulltext AND (path1 OR path2)
Phủ định nhóm
group.p.not=true
group.property=jcr:content/hideInNav
group.property.value=trueLưu ý: Không được dùng cùng numeric prefix trong một query, kể cả cho các predicate khác nhau.
5. Phân Trang và Kiểm Soát Kết Quả
Các tham số quan trọng
p.offset=0 # bắt đầu từ kết quả thứ N
p.limit=20 # số kết quả mỗi trang
p.guessTotal=true # ước tính tổng, tránh đếm chính xácPattern phân trang chuẩn
params.put("p.offset", "0");
params.put("p.limit", "20");
params.put("p.guessTotal", "true");
SearchResult result = query.getResult();
// result.getHits() → danh sách kết quả (tối đa 20)
// result.hasMore() → còn kết quả tiếp theo không
// result.getTotalMatches() → ước tính tổng (với guessTotal)Kiểm soát output
p.hits=full # tất cả properties
p.hits=selective # chỉ properties chỉ định
p.properties=jcr:title sling:resourceType # dùng với selective
p.nodedepth=2 # include child nodes đến depth N6. Sorting
orderby=@jcr:content/cq:lastModified
orderby.sort=desc # hoặc asc (default)
orderby.case=ignore # case-insensitive
# Sort theo nhiều field
1_orderby=@jcr:content/jcr:title
2_orderby=@jcr:content/cq:lastModified7. Query Builder Debugger
Công cụ thiết yếu khi phát triển:
http://localhost:4502/libs/cq/search/content/querydebug.htmlDebugger cho phép:
- Nhập predicates dạng text, chạy query
- Xem kết quả JSON
- Lấy XPath tương đương → paste vào Explain Query
Workflow debug chuẩn
- Viết query trong Debugger
- Copy XPath được sinh ra
- Paste vào Explain Query (
Tools → Operations → Diagnosis → Explain Query) - Xem query plan → index nào được dùng
- Nếu thấy "no index found" → cần tạo Oak index
Lấy XPath qua logging
Felix Console → Sling Log Support
Logger: com.day.cq.search.impl.builder.QueryImpl
Level: DEBUGOutput sẽ có dạng:
XPath query: /jcr:root/content//element(*, cq:Page)[jcr:contains(jcr:content, "AEM")]8. Oak Indexing — Điều Không Thể Bỏ Qua (On-Premise)
Oak không index mặc định
Không như Jackrabbit 2, Oak không tự index properties. Nếu query không hit index, Oak sẽ traverse toàn bộ repository — cực kỳ chậm và nguy hiểm.
Dấu hiệu query traversal trong error.log:
*WARN* Traversed 1000 nodes with filter Filter(query=select ...)
consider creating an index or changing the queryCác loại index trong AEM 6.5
| Loại | Khi nào dùng |
|---|---|
| Lucene (async) | Property search, fulltext — phổ biến nhất |
| Property (sync) | Unique constraint, tìm theo 1 property đơn giản |
| Traversal | Fallback khi không có index nào — tránh dùng production |
Tạo Lucene Property Index
/oak:index/myCustomIndex
├── jcr:primaryType = oak:QueryIndexDefinition
├── type = lucene
├── async = async
├── fulltextEnabled = false
└── indexRules
└── cq:PageContent
└── properties
└── myProperty
├── name = myProperty
└── propertyIndex = trueSau khi tạo, set reindex=true trên node index để trigger re-indexing.
Lưu ý: Lucene index chạy async — kết quả có thể chưa reflect thay đổi vừa mới write. Không dùng cho các use case cần strong consistency.
Công cụ quản lý index
| Công cụ | URL |
|---|---|
| Index Manager | Tools → Operations → Diagnosis → Index Manager |
| Query Performance | Tools → Operations → Diagnosis → Query Performance |
| Explain Query | Tools → Operations → Diagnosis → Explain Query |
| Oak Index JSON | http://localhost:4502/oak:index.tidy.-1.json |
9. Best Practices & Lỗi Thường Gặp
Tuyệt đối không làm
# NGUY HIỂM: Fetch toàn bộ kết quả, có thể crash instance
p.limit=-1# NGUY HIỂM: Không có path, quét toàn /content
type=cq:Page
property=jcr:content/myProp
property.value=something
# Thiếu: path=/content/mysite// NGUY HIỂM: getTotalMatches() không có guessTotal
// Buộc Oak đếm chính xác tất cả kết quả → chậm với dataset lớn
long total = result.getTotalMatches();Nên làm
path=/content/mysite/en # path càng hẹp càng tốt
type=cq:Page # type cụ thể
p.limit=20 # luôn có limit
p.guessTotal=true # tránh đếm chính xácLỗi thường gặp
1. Query trả về jcr:content thay vì cq:Page
# Sai: trả về jcr:content nodes
type=cq:PageContent
property=cq:template
property.value=/conf/mysite/settings/wcm/templates/mytemplate
# Đúng: trả về cq:Page nodes
type=cq:Page
property=jcr:content/cq:template
property.value=/conf/mysite/settings/wcm/templates/mytemplate2. Numeric prefix trùng nhau
# SAI: cùng prefix "1_" cho hai predicate khác nhau
1_property=jcr:title
1_property.value=Test
1_fulltext=Test # conflict!
# ĐÚNG
1_property=jcr:title
1_property.value=Test
2_fulltext=Test3. type=nt:unstructured cho query lớn
Trả về hầu như mọi node trong AEM, gây traversal. Dùng type cụ thể hơn.
4. OR query thiếu index
OR joins (p.or=true) yêu cầu index cho từng nhánh. Nếu không, hiệu năng rất kém.
10. Ví Dụ Thực Tế
Tìm trang dùng template cụ thể
type=cq:Page
path=/content/mysite
property=jcr:content/cq:template
property.value=/conf/mysite/settings/wcm/templates/article
orderby=@jcr:content/cq:lastModified
orderby.sort=desc
p.limit=20
p.guessTotal=trueTìm asset upload trong 30 ngày qua
type=dam:Asset
path=/content/dam/mysite
relativedaterange.property=jcr:content/metadata/jcr:lastModified
relativedaterange.lowerBound=-30d
p.limit=50
p.guessTotal=trueTìm trang trong nhiều path (OR)
type=cq:Page
fulltext=campaign
group.p.or=true
group.1_path=/content/mysite/en
group.2_path=/content/mysite/de
p.limit=20Tìm trang bị ẩn trong navigation
type=cq:Page
path=/content/mysite
boolproperty=jcr:content/hideInNav
boolproperty.value=true
p.limit=100
p.guessTotal=trueFull-text search trên page content và tags
type=cq:Page
path=/content/mysite
group.p.or=true
group.1_fulltext=keyword
group.1_fulltext.relPath=jcr:content
group.2_fulltext=keyword
group.2_fulltext.relPath=jcr:content/@cq:tags
p.limit=20
p.guessTotal=true11. Checklist Trước Khi Deploy Query Lên Production
- [ ]
pathđặt càng hẹp càng tốt - [ ]
typechỉ định node type cụ thể - [ ]
p.limitđặt giá trị hợp lý (10–100) - [ ]
p.guessTotal=truethay vì để AEM đếm chính xác - [ ] Dùng Explain Query → xác nhận query hit index
- [ ] Không còn WARN traversal trong
error.log - [ ] Nếu dùng property custom → đã có Oak index cho property đó
- [ ] Batch operations dùng pagination, không dùng
p.limit=-1
Tham Khảo
- AEM 6.5 Query Builder API — Adobe Experience League
- Query Builder Predicate Reference — Adobe Experience League
- Oak Queries and Indexing (AEM 6.5) — Adobe Experience League
- JCR Query Cheatsheet (PDF) — Adobe