Skip to content

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=10

Java API (Sling Model)

java
@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

properties
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 path càng hẹp càng tốt. Không có path = quét toàn bộ /content.


type — Lọc theo node type

properties
type=cq:Page
# hoặc
type=dam:Asset
type=cq:PageContent

Lưu ý thực tế: type bao gồm cả subtypes. type=nt:unstructured sẽ trả về rất nhiều kết quả không mong muốn. Ưu tiên dùng cq:Page, dam:Asset thay vì các type chung.


property — Lọc theo property

properties
# 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=tag2

Các operation hỗ trợ: equals (default), unequals, like, not, exists.


daterange — Lọc theo khoảng thời gian

properties
# 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

properties
# Content được sửa trong 7 ngày qua
relativedaterange.property=jcr:content/cq:lastModified
relativedaterange.lowerBound=-7d
# upperBound mặc định = now

Syntax: 1s 2m 3h 4d 5w 6M 7y. Prefix - = trong quá khứ.


nodename — Lọc theo tên node

properties
# Tìm tất cả file .jar
type=nt:file
nodename=*.jar

Hỗ 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

properties
fulltext=Adobe Experience Manager
# Chỉ tìm trong jcr:content
fulltext.relPath=jcr:content

Dùng Lucene index, không hỗ trợ filtering. Cần fulltext index được cấu hình.


boolproperty — Property boolean

properties
boolproperty=jcr:content/hideInNav
boolproperty.value=true

false match cả node không có property đó.


tagtagid — Tìm theo tag

properties
# Theo tag ID
tagid=properties:orientation/landscape
tagid.property=jcr:content/cq:tags

# Theo tag title path
tag=properties:orientation/landscape

4. Nhóm Logic: p.orp.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

properties
# (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=About

Nested groups

properties
# fulltext="AEM" AND (path1 OR path2)
fulltext=AEM
group.p.or=true
group.1_path=/content/mysite/en
group.2_path=/content/mysite/de

Conceptually: fulltext AND (path1 OR path2)

Phủ định nhóm

properties
group.p.not=true
group.property=jcr:content/hideInNav
group.property.value=true

Lư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

properties
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ác

Pattern phân trang chuẩn

java
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

properties
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 N

6. Sorting

properties
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:lastModified

7. Query Builder Debugger

Công cụ thiết yếu khi phát triển:

http://localhost:4502/libs/cq/search/content/querydebug.html

Debugger 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

  1. Viết query trong Debugger
  2. Copy XPath được sinh ra
  3. Paste vào Explain Query (Tools → Operations → Diagnosis → Explain Query)
  4. Xem query plan → index nào được dùng
  5. 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:  DEBUG

Output 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 query

Các loại index trong AEM 6.5

LoạiKhi 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
TraversalFallback 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 = true

Sau 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 ManagerTools → Operations → Diagnosis → Index Manager
Query PerformanceTools → Operations → Diagnosis → Query Performance
Explain QueryTools → Operations → Diagnosis → Explain Query
Oak Index JSONhttp://localhost:4502/oak:index.tidy.-1.json

9. Best Practices & Lỗi Thường Gặp

Tuyệt đối không làm

properties
# NGUY HIỂM: Fetch toàn bộ kết quả, có thể crash instance
p.limit=-1
properties
# 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
java
// 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

properties
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ác

Lỗi thường gặp

1. Query trả về jcr:content thay vì cq:Page

properties
# 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/mytemplate

2. Numeric prefix trùng nhau

properties
# 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=Test

3. 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ể

properties
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=true

Tìm asset upload trong 30 ngày qua

properties
type=dam:Asset
path=/content/dam/mysite
relativedaterange.property=jcr:content/metadata/jcr:lastModified
relativedaterange.lowerBound=-30d
p.limit=50
p.guessTotal=true

Tìm trang trong nhiều path (OR)

properties
type=cq:Page
fulltext=campaign
group.p.or=true
group.1_path=/content/mysite/en
group.2_path=/content/mysite/de
p.limit=20

Tìm trang bị ẩn trong navigation

properties
type=cq:Page
path=/content/mysite
boolproperty=jcr:content/hideInNav
boolproperty.value=true
p.limit=100
p.guessTotal=true

Full-text search trên page content và tags

properties
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=true

11. Checklist Trước Khi Deploy Query Lên Production

  • [ ] path đặt càng hẹp càng tốt
  • [ ] type chỉ định node type cụ thể
  • [ ] p.limit đặt giá trị hợp lý (10–100)
  • [ ] p.guessTotal=true thay 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 On-Premise Developer Notes