Skip to content

Entity Definition ​

Basic @QfEntity Declaration ​

Entities are declared using the @QfEntity annotation on a class.

java
@QfEntity(
    appKey = "app",   // client app key (optional; defaults to the single declared @QfClientApp)
    name = @QfI18n(
        defaultMessage = "Product",
        texts = { @QfI18nText(locale = "ko", message = "상품") }
    )
)
public class ProductEntity {
    // field declarations...
}

@QfEntity Key Attributes ​

AttributeTypeRequiredDescriptionDefault
appKeyStringOwning client app key"" (uses the single declared app)
name@QfI18nLocalized display name@QfI18n(texts = {})
autoHistoryEnabledbooleanEnable automatic change historyfalse
treePolicy@QfTreePolicyEnable and configure tree structure@QfTreePolicy (disabled)
ownerPolicy@QfOwnerPolicyOwnership and visibility policy (org or user)@QfOwnerPolicy(enabled = false)
deletePolicy@QfCrudPolicyDelete operation policy@QfCrudPolicy(enabled = false)
capabilityKeyStringCapability key for access control"" (derived from class name)
excelDownloadablebooleanEnable Excel exporttrue
excelUploadablebooleanEnable Excel importtrue

Attribute Declaration ​

Fields are managed by placing display and behavior annotations directly on them. There is no @QfField marker annotation — each annotation controls a specific concern.

java
@QfEntity(
    name = @QfI18n(defaultMessage = "Product", texts = {})
)
public class ProductEntity {

    // List + detail + create + update
    @QfI18n(defaultMessage = "Product Name")
    @QfListAttribute(sortable = true)
    @QfDetailAttribute
    @QfCreateAttribute(requiredOn = @QfRequiredOn(always = true))
    @QfUpdateAttribute
    private String name;

    // List + detail only (read-only price)
    @QfI18n(defaultMessage = "Price")
    @QfListAttribute
    @QfDetailAttribute
    private Integer price;

    // Detail + create + update with textarea control
    @QfI18n(defaultMessage = "Description")
    @QfDetailAttribute
    @QfCreateAttribute
    @QfUpdateAttribute
    @QfControlType(QfControlType.Type.textarea)
    private String description;
}

Display Annotations ​

AnnotationDescription
@QfI18nDeclare localized field label (placed directly on the field)
@QfListAttributeDisplay as a column in the list view
@QfDetailAttributeDisplay in the detail view
@QfCreateAttributeDisplay as an input field in the create form
@QfUpdateAttributeDisplay as an input field in the update form
@QfSearchEnable as a search filter in the list view

Required Fields ​

Required rules are declared inside @QfCreateAttribute or @QfUpdateAttribute via requiredOn:

java
// Always required
@QfI18n(defaultMessage = "Name")
@QfCreateAttribute(requiredOn = @QfRequiredOn(always = true))
@QfUpdateAttribute(requiredOn = @QfRequiredOn(always = true))
private String name;

// Required only when type == 'BUSINESS'
@QfI18n(defaultMessage = "Business Registration Number")
@QfCreateAttribute(
    requiredOn = @QfRequiredOn(
        conditions = @QfConditionExpr(expr = "type == 'BUSINESS'")
    )
)
private String businessNumber;

@QfCrypto Encryption ​

Declaring @QfCrypto on a sensitive field enables automatic encryption on save and decryption on read.

java
@QfI18n(defaultMessage = "Email")
@QfDetailAttribute
@QfCreateAttribute
@QfCrypto                    // automatic encryption
@QfControlType(QfControlType.Type.email)
private String email;

@QfI18n(defaultMessage = "Phone")
@QfDetailAttribute
@QfCreateAttribute
@QfUpdateAttribute
@QfCrypto
private String phone;

Encrypted Field Restriction

A field with @QfCrypto cannot be combined with @QfSearch. Encrypted data does not support server-side LIKE search. This is enforced as a compile error.


Owner Policy ​

Ownership-based data filtering is configured on the entity via ownerPolicy. Two ownership models are supported: ORGANIZATION (org hierarchy filtering) and USER (caller identity filtering).

java
// Organization ownership — filter by org scope
@QfEntity(
    name = @QfI18n(defaultMessage = "Order", texts = {}),
    ownerPolicy = @QfOwnerPolicy(
        enabled = true,
        attribute = "orgId"   // field name that holds the organization ID
    )
)
public class OrderEntity {

    private String orgId;    // org filter target (declared in ownerPolicy.attribute)

    @QfI18n(defaultMessage = "Order Number")
    @QfListAttribute
    private String orderNumber;

    @QfI18n(defaultMessage = "Order Amount")
    @QfListAttribute
    private Long amount;
}

// User ownership — filter by creator
@QfEntity(
    name = @QfI18n(defaultMessage = "Memo", texts = {}),
    ownerPolicy = @QfOwnerPolicy(
        enabled = true,
        ownerType = QfOwnerType.USER,
        attribute = "createdBy"
    )
)
public class MemoEntity { }

Entities with owner policy enabled:

  • Automatically filter to the current user's ownership scope on queries
  • Automatically populate the owner ID on creation
  • Require no manual WHERE clause

Capability Key ​

Link an entity to a Capability by setting capabilityKey:

java
@QfEntity(
    name = @QfI18n(defaultMessage = "Product", texts = {}),
    capabilityKey = "product-management"   // must match a @QfCapability key
)
public class ProductEntity {
    // ...
}

Master-Detail Relationship ​

Declare that an entity is a detail of a master entity via masterRelation:

java
// Order item (Detail entity)
@QfEntity(
    name = @QfI18n(defaultMessage = "Order Item", texts = {}),
    masterRelation = @QfMasterRelation(
        enabled = true,
        masterEntityFqcn = "com.example.OrderEntity",  // fully-qualified master class name
        masterKeyAttribute = "orderId",                 // attribute on THIS entity holding master's ID
        onMasterDelete = QfMasterRelation.OnMasterDelete.CASCADE_DELETE
    )
)
public class OrderItemEntity {

    private String orderId;   // master's ID

    @QfI18n(defaultMessage = "Product Name")
    @QfListAttribute
    @QfCreateAttribute(requiredOn = @QfRequiredOn(always = true))
    private String productName;

    @QfI18n(defaultMessage = "Quantity")
    @QfListAttribute
    @QfCreateAttribute(requiredOn = @QfRequiredOn(always = true))
    private Integer quantity;
}

onMasterDelete options:

  • CASCADE_DELETE — delete detail records when master is deleted
  • RESTRICT — reject master deletion if details exist
  • IGNORE — leave detail records orphaned

Enabling Change History ​

java
@QfEntity(
    name = @QfI18n(defaultMessage = "Product", texts = {}),
    autoHistoryEnabled = true    // enable automatic change history
)
public class ProductEntity {
    // ...
}

When autoHistoryEnabled = true:

  • All update and delete operations are automatically recorded
  • A history query endpoint is automatically generated

Tree Structure Entities ​

Configure tree behavior via treePolicy:

java
@QfEntity(
    name = @QfI18n(defaultMessage = "Category", texts = {}),
    treePolicy = @QfTreePolicy
)
public class CategoryEntity {

    @QfParent
    private String parentId;   // parent node ID

    @QfTreeDepth
    private Integer depth;     // depth level

    @QfI18n(defaultMessage = "Category Name")
    @QfListAttribute
    private String name;
}

With tree policy configured, a tree query endpoint is automatically generated.


Full Example: Composite Entity ​

java
@QfEntity(
    name = @QfI18n(
        defaultMessage = "Employee",
        texts = { @QfI18nText(locale = "ko", message = "직원") }
    ),
    ownerPolicy = @QfOwnerPolicy(
        enabled = true,
        attribute = "orgId"
    ),
    autoHistoryEnabled = true,
    deletePolicy = @QfCrudPolicy(enabled = true),
    capabilityKey = "hr-management"
)
public class EmployeeEntity {

    // org scope — filtered automatically
    private String orgId;

    // employee number
    @QfI18n(defaultMessage = "Employee Number")
    @QfListAttribute(sortable = true)
    @QfDetailAttribute
    @QfCreateAttribute(requiredOn = @QfRequiredOn(always = true))
    @QfSearch(type = QfSearch.Type.text)
    private String employeeNumber;

    // full name
    @QfI18n(defaultMessage = "Full Name")
    @QfListAttribute(sortable = true)
    @QfDetailAttribute
    @QfCreateAttribute(requiredOn = @QfRequiredOn(always = true))
    @QfUpdateAttribute(requiredOn = @QfRequiredOn(always = true))
    @QfSearch(type = QfSearch.Type.text)
    private String fullName;

    // SSN — encrypted, no search
    @QfI18n(defaultMessage = "SSN")
    @QfDetailAttribute
    @QfCreateAttribute
    @QfCrypto
    private String ssn;

    // phone — encrypted with regex validation
    @QfI18n(defaultMessage = "Phone")
    @QfDetailAttribute
    @QfCreateAttribute
    @QfUpdateAttribute
    @QfCrypto
    @QfValidationRule(
        rule = QfValidationRule.Rule.regex,
        params = "^(?:\\+82[-\\s]?)?(?:0?1[0-9])[-\\s]?\\d{3,4}[-\\s]?\\d{4}$"
    )
    private String phone;

    // rank — from code group
    @QfI18n(defaultMessage = "Rank")
    @QfListAttribute
    @QfDetailAttribute
    @QfCreateAttribute
    @QfUpdateAttribute
    @QfCodeGroup(code = "EMPLOYEE_RANK")
    private String rank;

    // join date
    @QfI18n(defaultMessage = "Join Date")
    @QfListAttribute
    @QfDetailAttribute
    @QfCreateAttribute
    private LocalDate joinDate;
}

Next Steps ​

Released under the Apache 2.0 License.