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)
organizationPolicy@QfOrganizationPolicyOrganization-based data filtering@QfOrganizationPolicy(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
    @QfListAttribute(sortable = true)
    @QfDetailAttribute
    @QfCreateAttribute(requiredOn = @QfRequiredOn(always = true))
    @QfUpdateAttribute
    @QfDisplayLabel(text = "Product Name")
    private String name;

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

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

Display Annotations

AnnotationDescription
@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
@QfDisplayLabelSet the display label (key for i18n lookup, or literal text)

Required Fields

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

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

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

@QfCrypto Encryption

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

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

@QfDetailAttribute
@QfCreateAttribute
@QfUpdateAttribute
@QfCrypto
@QfDisplayLabel(text = "Phone")
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.


Organization Policy

Organization-based data filtering is configured on the entity via organizationPolicy, specifying which attribute holds the organization identifier.

java
@QfEntity(
    name = @QfI18n(defaultMessage = "Order", texts = {}),
    organizationPolicy = @QfOrganizationPolicy(
        enabled = true,
        attribute = "orgId"   // field name that holds the organization ID
    )
)
public class OrderEntity {

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

    @QfListAttribute
    @QfDisplayLabel(text = "Order Number")
    private String orderNumber;

    @QfListAttribute
    @QfDisplayLabel(text = "Order Amount")
    private Long amount;
}

Entities with organization policy enabled:

  • Automatically filter to the current user's organizational scope on queries
  • Automatically populate the organization 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

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

    @QfListAttribute
    @QfCreateAttribute(requiredOn = @QfRequiredOn(always = true))
    @QfDisplayLabel(text = "Quantity")
    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

    @QfListAttribute(isTreeNodeTitle = true)
    @QfDisplayLabel(text = "Category Name")
    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 = "직원") }
    ),
    organizationPolicy = @QfOrganizationPolicy(
        enabled = true,
        attribute = "orgId"
    ),
    autoHistoryEnabled = true,
    deletePolicy = @QfCrudPolicy(enabled = true),
    capabilityKey = "hr-management"
)
public class EmployeeEntity {

    // organization scope — filtered automatically
    private String orgId;

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

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

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

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

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

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

Next Steps

Released under the Apache 2.0 License.