What are some of the things you've observed in ColdFusion 9 with CF-ORM (Hibernate) that one should watch out for?
Things to watch out for in ColdFusion 9 with CF-ORM
coldfusioncoldfusion-9orm
Related Solutions
Let's say you have a collection of Car
objects (database rows), and each Car
has a collection of Wheel
objects (also rows). In other words, Car
→ Wheel
is a 1-to-many relationship.
Now, let's say you need to iterate through all the cars, and for each one, print out a list of the wheels. The naive O/R implementation would do the following:
SELECT * FROM Cars;
And then for each Car
:
SELECT * FROM Wheel WHERE CarId = ?
In other words, you have one select for the Cars, and then N additional selects, where N is the total number of cars.
Alternatively, one could get all wheels and perform the lookups in memory:
SELECT * FROM Wheel
This reduces the number of round-trips to the database from N+1 to 2. Most ORM tools give you several ways to prevent N+1 selects.
Reference: Java Persistence with Hibernate, chapter 13.
I've actually used your first approach with quite some success, but in a slightly different ways that I think would solve some of your problems:
Keep the entire schema and scripts for creating it in source control so that anyone can create the current database schema after a check out. In addition, keep sample data in data files that get loaded by part of the build process. As you discover data that causes errors, add it to your sample data to check that errors don't re-emerge.
Use a continuous integration server to build the database schema, load the sample data, and run tests. This is how we keep our test database in sync (rebuilding it at every test run). Though this requires that the CI server have access and ownership of its own dedicated database instance, I say that having our db schema built 3 times a day has dramatically helped find errors that probably would not have been found till just before delivery (if not later). I can't say that I rebuild the schema before every commit. Does anybody? With this approach you won't have to (well maybe we should, but its not a big deal if someone forgets).
For my group, user input is done at the application level (not db) so this is tested via standard unit tests.
Loading Production Database Copy:
This was the approach that was used at my last job. It was a huge pain cause of a couple of issues:
- The copy would get out of date from the production version
- Changes would be made to the copy's schema and wouldn't get propagated to the production systems. At this point we'd have diverging schemas. Not fun.
Mocking Database Server:
We also do this at my current job. After every commit we execute unit tests against the application code that have mock db accessors injected. Then three times a day we execute the full db build described above. I definitely recommend both approaches.
Best Answer
entity
init()
method must not have required argument(s), otherwiseEntityNew()
and other CF-ORM actions will break. You may want to use a Factory to create the entity, and enforce the required arguments there.A bug regarding this limitation has been filed in the Adobe Bugbase.
ORMReload()
withormsettings.dbcreate = "drop create"
might not drop all tables for you. CF9 Cumulative Hot Fix 1 improves this a little bit, but you might want to drop the tables in DB yourself.type="date"
(default to useormtype="date"
), will only store date but not time. If you want to persisted time as well, useormtype="timestamp"
type="string"
will default tovarchar(255)
type="numeric"
will default tofloat
, notint
. Use ormtype="int" if needed.if
fieldtype="id"
and generator is set to some generator, ormtype will default toint
.type="string" length="10"
will usevarchar(10)
, notchar(10)
ormtype="char" length="10"
will usechar(1)
still. Usesqltype="char(10)"
if you really need to.type="boolean"
usetinyint
by default, usesqltype="bit"
if you need to.should use
inverse=true
in a bi-directional relationship, usually in "one-to-many" side.do NOT use
inverse="true"
in uni-directional relationship! The relationship might not be persisted at all!If you use MS-SQL, you cannot have more than 1 entity with one-to-one property set to Null, because Null is considered as an unique value in an index. Good idea to make column not null. (or use linktable)
EntityLoad("entity", 1, true)
works, butEntityLoadByPK("entity", 1)
is cleaner!EntityLoad()
,EntityLoadByPK()
, andORMExecuteQuery
withunique=true
, will returnnull
if entity is not found. UseisNull()
to check before you use the returned value.ORMExecuteQuery
will return empty array if no entity is found by default.don't forget to use
singularname
property in "one-to-many" / "many-to-many" for nicer looking generated functions (e.g.addDog(Dog dog)
vsaddDogs(Dog dogs)
.)<cfdump>
will load all the lazy-load properties. Alternatively you may try<cfdump var="#entityToQuery([entity])#">
or set top=1 to dump efficiently.entity stored in Session scope will be disconnected with its Hibernate session scope, and lazy load property will not be loaded. To restore the hibernate session scope, use
entityLoadByExample()
orentitySave(entity)
.cascade="all-delete-orphan"
usually make more sense for "one-to-many" or "many-to-many" relationship. Hibernate sets null then delete, so make sure the column is nullable. Test and see if that's your desire behaviour.set
required="true"
whenevernotnull="true"
, more readable for others browsing the CFC with CFCExplorerEntityNew('Y')
is slightly more efficient thannew com.X.Y
if the entity is to be persisted later according to some Adobe engineer.relationship with an inherited entity may break sometimes due to an unfixed Hibernate bug, use
linktable
as a workaround.structKeyColumn
cannot be the PK of the target entity.bi-directional many-to-many cannot use struct
When adding new entity to struct,
structKeyColumn
is ignored when CF persists the parent entity.If you access the one-to-many / many-to-many array or struct directly, make sure the corresponding array/struct exists before use. Generated addX()/hasX()/removeX() are safe to use anytime.
at
postInsert()
, the entity hibernate session is no longer available, so setting property at postInsert() will be silently ignore, or Session is Closed exception will be thrown.after entity is loaded by
entityLoad()
or HQL from DB, the changes will be automatically persisted even ifEntitySave()
is not called.transaction with CF-ORM is implemented in a way that it starts a new session and close when it's done.
inside the event (i.e. preLoad() / postInsert()), assigning to variables might throw Java exception about types. Use JavaCast() to work around the bug.
UPDATE
<cfquery dbtype="hql">
, easier to docfqueryparam
, and debug output actually shows you the binded values.