Into The Box Notes: Slaying the ORM Dragons with CBORM, Luis Majano

April 29, 2017

#1 - modeling is key
ORM relationship modeling is key
UML will be your best friend
stop thinking about the DATA
start thinking about object orientation: data AND behavior
you’re not modeling a database here
might be cases where you’re doing strange things (nulls, or holes or whatever)
what you’re concerned about is your Rich Object Model

start with UML diagram

the object is data + behavior

#2 - Bad Engine Defaults
defaults in ACF and Lucee are not the best
just using the vanilla defaults, you’ll probably fail
so don’t use them
Control Data Flushing + how transactions are applied in your app (Transaction Borders)
also important for non-ORM stuff
need to control when/what goes to the database instead of just having every query be sent immediately
don’t ant things flushed at the end of request
don’t want ORM to manage sessions
ormsettings.flushAtRequested = false
ormsettings.autoMangaeSession = false

this.ormsettings.cfcloation = could be an array of locations
map it to a specific folder
if not, ACF will traverse your entire folder structure to find these. very slow.
use a specified directory, else pay the price of “discovery”.

this.ormsettings.logSQL = true in dev
= false in Production
slow in Prod under load. turn it off.

when you have issues (and you will)
save the Hibernate MXML mappings
this.ormsettings.saveMapping = true;

DataBoss - Ortus project
every time you work with your entities, will inspect, analyze and scaffold them.

#3 - Understand the Hibernate Session

if you don’t understand the Hibernate session, you’ll fail
not the same as the CF session.

entityLoad()
puts records in Hibernate session

entityNew()
does NOT put them into Hibernation Session
same as CreateObject() or new()
no ORM listeners in here.

How to control when to send to the database
use Transactions (also good for non-ORM stuff)

The Hibernate session in detail:
created by Hibernate session Factory
it’s a singleton
lives in application scope of your app
in charge of creating / managing Hibernate Sessions
every time you do transaction blocks new Hibernate sessions will be created/closed for you
Hibernate Session should “feel like a convo b/t you and the database”
a lot will go in here

entityNew() is in it’s own space, NOT in Hibernate Session
things are taken from DB and placed into this space.

Batches the sql for you, tries to be smart

Persistence
entitySave() doesn’t really go to the DB
just tells Hibernate that entity is MARKED for saving
“batched sql approach”

ways to clear the Hibernate session
ORMClearSession()
if there is stale data in the Hibernate Session or things you don’t want in the db, clear the session.

can attach an external cache (ehCache, CouchBase, traditional hashmaps, a few others)
the DATA is what gets cached, not the CFComponents

How Hibernate flushes into the DB is important
will often get cryptic messages about what’s going on
insertions are done i the order you call EntitySave()
then updates happen
then deletes
then collection deletion,updates, inserts, collection insertions
deletions in order

important to understand the order, especially when dealing with bi-directional relationships

#4 Transaction Demarcation
important for any app, not just ORM
“whatever I have in my transaction has to compete successfully in order to batch send to the db. if something goes wrong inside, it should NOT all be sent to the db”

no commit to db should occur without a transaction

must be reactive
“reactive programming”: expect the worst

use CFTransactions (easiest) or Hibernate Transactions if you’re more of a java guy

Transaction theory - what happens inside of a transaction
any existing ORM session will be flushed (sent to he db)
can be reused (won’t be destroyed)
data can be committed or rolled back
can have nested and named transactions
ORMflush is ignored in the transactions because you’ve said “i will use the tranaction”
if you commit, goes to db
if you roll back, session is cleared but not destroyed

#5 Laziness

when you do relationships (1 to many, many to 1, etc)
default is called “immediate fetching”
means: does SELECT * with LEFT OUTER by default
VERY poor performance
relationships w/ many records, this will put entire db in memory if you’re not careful
use lazy for ALL relationships no matter way

3 types of lazy
lazy = true (easiest to use)
that thing wont be loaded until you call its getter() method
better than left outer but still has issues.
what if i have 50,000 comments?

lazy = extra
applies to 1 to many or many to many relationships
builds proxy objects w/ Primary Keys ONLY
so you can say ArrayLen() on them, verify IDs, etc
but wont load data

lazy = proxy
same as lazy = extra
but only applies to 1 to 1 or many to one
does same thing as lazy = extra

Eager Fetching
there will be times when you have a 1 to 1 or many to 1 and every time you call for that relationship, it will be loaded in 2 sql calls.
property name=“role” fieldtype=“many-to-one” fetch=join
does the load in ONE sql call instead of 2

Batch Fetching -
will limit the way the relationships are loaded
if not, Hibernate will try to load ALL the records
you want some sort of pseudo-pagination probably instead
property name=“comments” fieldtype=“one-to-many” batchsize=“10” lazy=“extra”;
only loads comments 10 at a time

can also use at the Entity level and put defaults there
component name=“comment batchsize=“10” { … }

Oracle Tip -
has JDBCFetchSize
part of the JDBC driver
default is to retrieve only 10 records at a time
really bad
mySQL is dynamic tries to be smart for you (so doe Postgres)
can do as a custom setting OR in the JDBC data source:
hibernate.jdbc.fetch_size = 100

#6 Avoid bi-directional relationships
can be a huge headache
especially w /cascading deletes
if you DO use it:
chose the master relationship: how am i going to use these entities?  Am I getting a blog and then getting the comments to that blog? Or am I getting a comment then attaching it to the blog entry later?
if you don’t set this, hibernate doesn’t know which is mater, so it does the sql TWICE
tell it which side is the master
put this on the master component: “inverse=true”

might need to write supporting methods for the bi-directional relationships
newComment() method
that method needs to know how to link to the blog

ditto for cascading deletes. have to delete the children first, then parents

in Hibernate 5 there is an AOP setting that can take care of a lot of this for you
tickets have been created to ACF and Lucee to hopefully fix this in a new version.

#7 D not store entities in the CF scopes
seriously, don’t do it!
no linkage to Hibernate session
relationships will fail they’re if not lazy
entityMerge() - at beginning of request, can grab that and send it back into the Hibernate session
not very nice tho, has hassles
Store the ID’s instead.
and whenever HIbernate requests that ID from a session, it will just rebuild the object for you. can take advantage of secondary caches to speed that up if need be.

#8 - Use DB indexes
goes for everybody, not just ORM
majority of perf issues is because there are no indexes
when data gets large enough
#1 performance problem: dev’s forget about indexes

DB “explain” plan will tell you which indexes to create
property name=“isAcrive’ index=“idxActive”
- index can be a list.
- Hibernate will even CREATE the index for you

#9 have a good cache strategy
don’t go crazy
develop a a strategy
extremely easy to put things IN a cache
getting them out / removing them is harder
don’t store CFCs, store individual property values
use distributed caches: EHcache, CouchBase
you can cache:
entity property data: only caches property data values
entity association data: only caches primary keys
querydata:     hql, ORMExecuteQuery() - any time you use those (or cfquery type=‘hql’) it can do caching
need to evict
ORMEvictEntity()
ORMEvictCollection()

#10 HQL Maps
most of the time we don’t WANT an array of objects, especially if we’re just marshaling them to JSON
stop converting all your queries to arrays of objects if you don’t need to
native way in HQL is
select new map( “things i want in my map” ) from Cat cat
returns native data structures - extremely fast, no conversion to/from queries/objects

ColdBox ORM Module: CBORM
box install cborm
inspired by Gorm - Ruby on Rails Hibernate thing
gives you base services
creates virtual ORM services
binds a service layer to specific entity
can have Active Entity (aka Active Record pattern)
- an entity can save/delete/query itself

Entity Populators
-populate entities form xml, json, queries, structures

can apply DI/AOP to your entities

Base ORM Service
OO style Querying, caching, transactions
Dynamic finders, getters, counters
object meta data & session manage
exposes more features in Hibernate for you
90% foundation

demo app
box install cartracker-demo
shows all functionality on CBORM
has an embedded H2 database, so you can just “download and go”, no installing a DB required

count()
countWhere( some where clause )
delete()
lots more

Criteria Builder
entityLoad has issues
programmatic DSL builder
can do counters - max, min, etc.
has concept of “result transformers”
can turn data from queries into objects
or you can build your own transformers
like a closure, it will use your transformer on every record

can do getSQL and do “pretty print” the SQL

arrays of structs are TWICE  as fast as queries

slug:reminderType
colon is like the “as” operator
“slug as reminderType” in SQL

can get back READ-ONLY objects that cannot be persisted back to the database

Lots more stuff too.