ORM - Getting Started

edit

ORM - Getting Started

Lucee's ORM (Object-Relational Mapping) lets you work with database records as CFC objects instead of writing SQL. Define a persistent component, map its properties to columns, and use built-in functions like EntitySave() and EntityLoad() to read and write data.

Under the hood, Lucee uses Hibernate 5.6, one of the most widely used Java ORM frameworks.

Enable ORM

Add ORM settings to your Application.cfc:

component {

this.name = "my-orm-app";
this.datasource = "myDatasource";
this.ormEnabled = true; this.ormSettings = { dbcreate: "dropcreate", cfclocation: [ getDirectoryFromPath( getCurrentTemplatePath() ) ] }; }

That's three essential settings:

  • ormEnabled — turns ORM on
  • dbcreate — tells Hibernate how to manage your database schema. "dropcreate" drops and recreates tables on every application start — perfect for development, never use in production
  • cfclocation — where to scan for persistent CFCs. Defaults to your application root if omitted

Your datasource can be any supported database — H2, MySQL, PostgreSQL, MSSQL, Oracle, etc. ORM uses whichever datasource is set as this.datasource.

For the full list of settings, see ORM - Configuration.

Define an Entity

An entity is a CFC with persistent="true". Each property maps to a database column:

// User.cfc
component persistent="true" table="users" accessors="true" {

property name="id" fieldtype="id" generator="native" ormtype="integer"; property name="name" ormtype="string" length="150"; property name="email" ormtype="string" length="255";
}

  • persistent="true" — marks this CFC as an ORM entity
  • table — the database table name (defaults to the CFC name if omitted)
  • accessors="true" — generates getter/setter methods for each property
  • fieldtype="id" — marks the primary key
  • generator="native" — lets the database generate IDs (auto-increment on MySQL, sequence on PostgreSQL)
  • ormtype — the Hibernate data type for the column

For more on entity mapping, primary keys, and inheritance, see ORM - Entity Mapping.

Create

Use EntityNew() to create a new entity instance, then EntitySave() to persist it:

user = entityNew( "User" );
user.setName( "Susi Sorglos" );
user.setEmail( "susi@example.com" );
entitySave( user );

You can also pass a struct of property values:

user = entityNew( "User", { name: "Susi Sorglos", email: "susi@example.com" } );
entitySave( user );

Note: EntitySave() doesn't immediately write to the database — it marks the entity for persistence. The actual SQL runs when the session is flushed: either at the end of the request (if flushAtRequestEnd=true, the default), inside a cftransaction commit, or when you call ORMFlush() explicitly. See ORM - Sessions and Transactions for details.

Read

Load entities by primary key, by criteria, or by example:

// By primary key
user = entityLoadByPK( "User", 1 );

// By criteria struct — returns an array results = entityLoad( "User", { name: "Susi Sorglos" } );
// All entities of a type allUsers = entityLoad( "User" );

EntityLoadByPK() returns a single entity or null if not found. EntityLoad() with a filter struct always returns an array.

For HQL queries, pagination, and more advanced loading, see ORM - Querying.

Update

Just modify the entity's properties — Hibernate tracks changes automatically:

user = entityLoadByPK( "User", 1 );
user.setEmail( "new-email@example.com" );
// No need to call entitySave() — Hibernate detects the change and flushes it

This is called dirty checking. Any loaded entity that's been modified will be persisted when the session flushes. This is powerful but can surprise you — if you modify an entity for display purposes without intending to save, the change still persists. See ORM - Troubleshooting for how to avoid this.

Delete

user = entityLoadByPK( "User", 1 );
if ( !isNull( user ) )
	entityDelete( user );

Like entitySave(), the actual DELETE runs on flush.

Complete Example

Here's a full working example you can drop into a directory and run:

Application.cfc:

component {

this.name = "orm-quickstart-#hash( getCurrentTemplatePath() )#";
this.datasource = { class: "org.h2.Driver", connectionString: "jdbc:h2:mem:quickstart;DB_CLOSE_DELAY=-1" };
this.ormEnabled = true; this.ormSettings = { dbcreate: "dropcreate", cfclocation: [ getDirectoryFromPath( getCurrentTemplatePath() ) ] }; }

Product.cfc:

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";
}

index.cfm:

<cfscript>
	// Create
	product = entityNew( "Product", { name: "Widget", price: 9.99 } );
	entitySave( product );
	ormFlush();

// Read loaded = entityLoadByPK( "Product", product.getId() ); writeOutput( "Created: #loaded.getName()# — $#loaded.getPrice()#<br>" );
// Update loaded.setPrice( 12.99 ); ormFlush(); writeOutput( "Updated price: $#loaded.getPrice()#<br>" );
// Delete entityDelete( loaded ); ormFlush();
remaining = entityLoad( "Product" ); writeOutput( "Products after delete: #arrayLen( remaining )#<br>" ); </cfscript>

Using Transactions

For anything beyond toy examples, wrap your ORM operations in a transaction:

transaction {
	product = entityNew( "Product", { name: "Gadget", price: 19.99 } );
	entitySave( product );
	// transaction commit flushes the session automatically
}

If something goes wrong inside the transaction block, everything rolls back. Without a transaction, a flush failure leaves your data in a partial state with no way to recover. See ORM - Sessions and Transactions for the full story.

What's Next?

ColdBox users: If you're using the ColdBox framework, check out cborm which provides service layers, Active Record patterns, and criteria builders on top of native ORM.

See also