# 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](orm-relationships.md).

## Defining an Entity

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

```cfml
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](#optimistic-locking) |
| `discriminatorColumn` | Column name for table-per-hierarchy inheritance. See [Inheritance](#inheritance) |
| `discriminatorValue` | Value stored in the discriminator column for this entity |
| `joincolumn` | Column name for table-per-subclass joins. See [Inheritance](#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](orm-caching.md) |
| `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](#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](#primary-keys) and [ORM - Relationships](orm-relationships.md) |

### Column Mapping

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

```cfml
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:

```cfml
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:

```cfml
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:

```cfml
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:

```cfml
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:

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

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

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

**uuid** for Hibernate-generated 32-char hex strings:

```cfml
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:

```cfml
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:

```cfml
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:

```cfml
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:

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

### All-Column Locking

Compares all column values instead of using a version column:

```cfml
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:

```cfml
// 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";

}
```

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

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

}
```

```cfml
// 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:

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

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

}
```

```cfml
// 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:

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

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

}
```

```cfml
// 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"`:

```cfml
// 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;

}
```

```cfml
// 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](orm-relationships.md) — many-to-one, one-to-many, many-to-many, cascade, fetching strategies
- [ORM - Querying](orm-querying.md) — HQL queries and entity loading
- [ORM - Troubleshooting](orm-troubleshooting.md) — common mapping mistakes and error messages

# Categories

[ORM](../categories/orm.md)

# See Also

[EntityNew()](../reference/functions/entitynew.md), [EntitySave()](../reference/functions/entitysave.md), [ORM - Configuration](orm-configuration.md), [ORM - Events](orm-events.md), [ORM - Getting Started](orm-getting-started.md), [ORM - Querying](orm-querying.md), [ORM - Relationships](orm-relationships.md), [ORM - Troubleshooting](orm-troubleshooting.md)