ORM - Entity Mapping

edit

ORM - Entity Mapping

An ORM entity is a CFC that maps to a database table. Each property maps to a column. This page covers entity definition, property attributes, supported data types, primary key strategies, and inheritance patterns.

For relationships between entities, see ORM - Relationships.

Defining an Entity

Mark a CFC as persistent and map it to a table:

component persistent="true" table="products" accessors="true" {

property name="id" fieldtype="id" generator="native" ormtype="integer"; property name="name" ormtype="string" length="200"; property name="price" ormtype="big_decimal";
}

Component Attributes

Attribute Description
persistent true to mark this CFC as an ORM entity
table Database table name. Defaults to the CFC name
schema Database schema. Overrides the global ormSettings.schema
catalog Database catalog. Overrides the global ormSettings.catalog
entityname The name used in HQL and entityLoad(). Defaults to the CFC name. Set this if multiple CFCs share the same name in different directories
accessors true to auto-generate getter/setter methods for all properties
dynamicinsert true to only include non-null columns in INSERT statements. Reduces SQL size for entities with many nullable columns
dynamicupdate true to only include changed columns in UPDATE statements. Must be combined with selectbeforeupdate or optimistic locking to be useful
selectbeforeupdate true to SELECT the entity before UPDATE to detect changes. Avoids unnecessary updates when entities are reattached from detached state
optimisticlock Optimistic locking strategy: "version" (default), "all", "dirty", or "none". See Optimistic Locking
discriminatorColumn Column name for table-per-hierarchy inheritance. See Inheritance
discriminatorValue Value stored in the discriminator column for this entity
joincolumn Column name for table-per-subclass joins. See Inheritance
batchsize Number of instances to load in a single batch when proxied entities are accessed. Helps mitigate N+1 queries
lazy true (default) or false. When false, the entity class itself is never proxied
readonly true to make the entity read-only — Hibernate skips dirty checking and never generates UPDATE statements
where SQL WHERE clause fragment applied to all loads of this entity. Useful for soft-delete patterns: where="deleted = 0"
rowid Maps to the database ROWID (Oracle-specific)
cacheuse L2 cache strategy: "read-only", "nonstrict-read-write", "read-write", or "transactional". See ORM - Caching
cachename Name of the L2 cache region for this entity

Property Attributes

Attribute Description
name Property name (required)
column Database column name. Defaults to the property name
ormtype Hibernate data type. See Supported ormtypes
sqltype SQL column type override (e.g. "varchar", "nvarchar"). More specific than ormtype — controls the exact DDL type
length Column length for string types
precision Total number of digits for numeric types
scale Number of decimal places for numeric types
notnull true to add a NOT NULL constraint
unique true to add a UNIQUE constraint
default Default value applied at the application level when creating new entities
dbdefault Default value applied at the database level (DDL DEFAULT clause). Different from defaultdbdefault affects schema generation, default affects CFC behaviour
insert false to exclude this column from INSERT statements
update false to exclude this column from UPDATE statements
lazy true to lazy-load this individual property (useful for large text/blob columns)
formula SQL expression for a computed property. Not stored in the database
generated "always", "insert", or "never". Tells Hibernate the value is generated by the database (triggers, defaults) and needs re-reading
optimisticlock false to exclude this property from optimistic lock checks
index Creates a database index on this column. Value is the index name
uniquekey Name of a multi-column unique constraint this property belongs to
fieldtype "id" for primary keys, "version" / "timestamp" for optimistic locking, or a relationship type. See Primary Keys and ORM - Relationships

Column Mapping

When the property name doesn't match the column name, use the column attribute:

component persistent="true" table="contacts" accessors="true" {

property name="id" fieldtype="id" ormtype="string"; property name="firstName" ormtype="string" column="first_name"; property name="lastName" ormtype="string" column="last_name"; property name="emailAddr" ormtype="string" column="email_address";
}

Read-Only Columns

Use insert=false and update=false for columns managed by the database:

property name="createdAt" ormtype="timestamp" insert=false update=false;
property name="updatedAt" ormtype="timestamp" insert=false update=false;

Computed Properties

The formula attribute defines a SQL expression that Hibernate evaluates on every load. The property has no corresponding column:

component persistent="true" table="order_items" accessors="true" {

property name="id" fieldtype="id" ormtype="string"; property name="quantity" ormtype="integer"; property name="price" ormtype="big_decimal"; property name="total" formula="quantity * price" type="numeric";
}

Numeric Precision

For financial or scientific data, control precision and scale explicitly:

property name="balance" ormtype="big_decimal" precision="12" scale="4";
property name="rate"    ormtype="double"      precision="8"  scale="6";

Supported ormtypes

ormtype Java Type Typical SQL Type
string java.lang.String VARCHAR
character char CHAR(1)
text java.lang.String TEXT / CLOB
clob java.sql.Clob CLOB
integer / int int INTEGER
long long BIGINT
short short SMALLINT
byte byte TINYINT
float float FLOAT
double double DOUBLE
big_decimal java.math.BigDecimal DECIMAL / NUMERIC
big_integer java.math.BigInteger NUMERIC
boolean boolean BIT / BOOLEAN
yes_no boolean CHAR(1) — stores 'Y' / 'N'
true_false boolean CHAR(1) — stores 'T' / 'F'
date java.sql.Date DATE
time java.sql.Time TIME
timestamp java.sql.Timestamp TIMESTAMP
datetime java.sql.Timestamp TIMESTAMP (alias for timestamp)
timezone java.util.TimeZone VARCHAR
binary byte[] VARBINARY / BLOB
serializable java.io.Serializable VARBINARY

You can also use the sqltype attribute for finer control over the DDL column type:

property name="varcharCol"  sqltype="varchar"  length="50";
property name="nvarcharCol" sqltype="nvarchar" length="100";

Primary Keys

Every entity needs at least one property with fieldtype="id".

Generator Strategies

The generator attribute controls how primary key values are assigned:

Generator Description Typical ormtype
native Database picks the strategy (IDENTITY on MySQL/MSSQL, SEQUENCE on PostgreSQL/Oracle) integer / long
identity Uses database IDENTITY / AUTO_INCREMENT columns integer / long
increment Hibernate-managed counter (reads max ID + 1). Not safe for concurrent inserts from multiple app servers integer / long
uuid Hibernate generates a 32-character hex UUID string (length="32")
assigned Application manages the ID. You must set it before entitySave() any
sequence Uses a database sequence (PostgreSQL, Oracle, DB2) integer / long
select Reads the ID back from the database after insert using a trigger-assigned value string

native is the safest default — it delegates to the database:

property name="id" fieldtype="id" ormtype="long" generator="native";

assigned when you manage IDs yourself (e.g. UUIDs):

property name="id" fieldtype="id" ormtype="string" generator="assigned";

uuid for Hibernate-generated 32-char hex strings:

property name="id" fieldtype="id" ormtype="string" length="32" generator="uuid";

Note: With generator="native" or generator="identity", the INSERT executes immediately on entitySave() (not waiting for flush) because Hibernate needs the database-generated ID.

Composite Keys

Use multiple fieldtype="id" properties for a composite primary key:

component persistent="true" table="enrolments" accessors="true" {

property name="studentId" fieldtype="id" ormtype="string"; property name="courseId" fieldtype="id" ormtype="integer"; property name="grade" ormtype="string";
}

Load by passing a struct:

enrolment = entityLoad( "Enrolment", { studentId: "abc", courseId: 101 } );

Optimistic Locking

Optimistic locking prevents lost updates when multiple requests modify the same entity. Hibernate checks that the row hasn't changed since it was loaded before writing an update.

Version Column

The most common approach — add a fieldtype="version" property:

component persistent="true" table="documents" accessors="true" {

property name="id" fieldtype="id" ormtype="string"; property name="title" ormtype="string"; property name="version" fieldtype="version" ormtype="integer";
}

Hibernate increments the version on every update. If the version in the database doesn't match what was loaded, the update fails with a StaleObjectStateException.

Timestamp Column

Alternative to version — uses a timestamp:

property name="lastModified" fieldtype="timestamp";

All-Column Locking

Compares all column values instead of using a version column:

component persistent="true" table="records" accessors="true"
	optimisticlock="all" dynamicupdate="true" {

property name="id" fieldtype="id" ormtype="string"; property name="name" ormtype="string"; property name="notes" ormtype="string" optimisticlock="false";
}

Set optimisticlock="false" on individual properties to exclude them from the check.

Inheritance

Hibernate supports three inheritance mapping strategies. In all cases, subclass CFCs use extends to inherit from the parent entity.

Table-Per-Hierarchy (Single Table)

All classes in the hierarchy share one table. A discriminator column identifies the type:

// Vehicle.cfc — base entity
component persistent="true" table="vehicles" accessors="true"
	discriminatorColumn="vehicle_type" discriminatorValue="vehicle" {

property name="id" fieldtype="id" ormtype="string"; property name="make" ormtype="string"; property name="model" ormtype="string";
}

// Car.cfc — subclass
component persistent="true" extends="Vehicle" accessors="true"
	discriminatorValue="car" {

property name="doors" ormtype="integer";
}

// Truck.cfc — subclass
component persistent="true" extends="Vehicle" accessors="true"
	discriminatorValue="truck" {

property name="towCapacity" ormtype="integer";
}

Querying for Vehicle returns all types. Querying for Car returns only cars. This is the most performant strategy — no joins needed — but every subclass column must be nullable.

Table-Per-Subclass (Joined)

Each class gets its own table. Subclass tables join to the parent via a foreign key:

// Person.cfc — base entity
component persistent="true" table="people" accessors="true" {

property name="id" fieldtype="id" ormtype="string"; property name="name" ormtype="string";
}

// Employee.cfc — subclass with its own table
component persistent="true" extends="Person" accessors="true"
	joincolumn="person_id" table="employees" {

property name="salary" ormtype="double";
}

Cleaner schema — no nullable columns — but queries require joins.

Table-Per-Concrete-Class (Union)

Each concrete class gets a complete table with all inherited columns:

// Shape.cfc — base entity
component persistent="true" table="shapes" accessors="true" {

property name="id" fieldtype="id" ormtype="string"; property name="color" ormtype="string";
}

// Circle.cfc — concrete subclass with its own complete table
component persistent="true" extends="Shape" accessors="true"
	table="circles" {

property name="radius" ormtype="double";
}

The circles table contains id, color, and radius. Querying for Shape uses a UNION across all concrete tables — can be slow with many subclasses.

Mapped Superclass

If the base class shouldn't be an entity itself (no table, can't be queried), set persistent="false":

// BaseEntity.cfc — not a table, just shared properties
component persistent="false" accessors="true" {

property name="createdAt" ormtype="timestamp" insert=false update=false; property name="updatedAt" ormtype="timestamp" insert=false update=false;
}

// User.cfc — concrete entity that inherits shared properties
component persistent="true" table="users" extends="BaseEntity" accessors="true" {

property name="id" fieldtype="id" ormtype="string"; property name="name" ormtype="string";
}

What's Next?

See also