ORM - Entity Mapping
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 default — dbdefault 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"orgenerator="identity", the INSERT executes immediately onentitySave()(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?
- ORM - Relationships — many-to-one, one-to-many, many-to-many, cascade, fetching strategies
- ORM - Querying — HQL queries and entity loading
- ORM - Troubleshooting — common mapping mistakes and error messages