Coldfusion ORM Table Per Class Hierarchy, Tips & Tricks By: Greg Moser

Over the course of the past 6 months I have gotten a full start to finish crash course on Adobe Coldusion & Railo's implementation of Hibernate more commonly know as Coldfusion ORM.  During this time I have run into "oh no's", "gotcha's", and "what the hell whas that?".  While all of these things took countless hours of racking my brain... at then end of the day I have not only grown to love Hibernate, but also learned a few tips that I'm very excited to share.

One the features that really opened my eyes was the advanced mapping technique called Table Per Class Hierarchy.  If this is the first you are hearing about it, you will want to check out the explination on Adobe's Advanced Mapping Techniques Documentation.  In short it allows you to map persistent objects that extend a base object and store all of the data in a single database table.  The example that Adobe uses, and so will I, is that of order payments.

Consider you have orders, and those orders can either be paid for by Credit Card or Check.  If a customer pays with a Credit Card you want to store the card number and experation date.  If they pay with a check you will want to store the account and routing numbers.  Either way, you will always want to store the amount of the payment.  With that in mind, here is what our components look like:

Payment.cfc
component persistent="true" table="Payment" discriminatorColumn="paymentType" {
    property name="amount";
}

PaymentCreditCard.cfc
component persistent="true" table="Payment" extends="Payment" discriminatorValue="creditCard" {
    property name="creditCardNumber";
    property name="experationMonth";
    property name="expirationYear";
}

PaymentCheck.cfc
component persistent="true" table="Payment" extends="Payment" discriminatorValue="check" {
    property name="accountNumber";
    property name="routingNumber";
}


So that's great and it is basically exactly what Adobe explains in their document.  However when we actually go to apply this to the rest of our DB model we need to relate this to our orders.  For example let's say you want to get the payments that are related to a give order, all you need to do is setup a mapping like this in your order entity.

Order.cfc
component persistent="true" table="Order" {
    property name="payments" fieldtype="one-to-many" fkcolumn="orderID" cfc="Payment";
}

With a setup like this, you can take your order entity and do getPayments() and it will return an array of mixed objects.  If the order has a credit card payment applied and a check payment applied to it this method will return an array where the first entity is a PaymentCreditCard.cfc and the second is a PaymentCheck.cfc.  That is great, but if you are looping over the array and doing some logic it would be nice to know what type of payment each index of the array is.

The other option is to create a relationship for each payment time in the order.cfc.  That would look something like this:

Order.cfc
component persistent="true" table="Order" {
    property name="paymentCreditCards" fieldtype="one-to-many" fkcolumn="orderID" cfc="PaymentCreditCard";
    property name="paymentChecks" fieldtype="one-to-many" fkcolumn="orderID" cfc="PaymentCheck";
}

You could imagine this approach would get very hard to manage if you had a bunch of different payment types.  You would have to loop over each array independently and check each one for info.  For me, that is just too hard to manage, so lets go back to the first option:

Order.cfc
component persistent="true" table="Order" {
    property name="payments" fieldtype="one-to-many" fkcolumn="orderID" cfc="Payment";
}

Now the trick here is that we want to be able to get this array of payments and determine programatically what type each payment is.  We can do that by adding a special property to our Payment.cfc

Payment.cfc
component persistent="true" table="Payment" discriminatorColumn="paymentType" {
    property name="amount";
    property name="paymentType" insert="false" update="false";
}

What this new second property allows us to do is read the value in the discriminator column so that we can determine the value, and also the payment type.  Notice that insert and update are both set to false, this is important because the actual discriminator value is set by the type of component you are persisting when you initially save.

So this is great, now we can getPayments() on the order and then do getPaymentType() on each of those payments to determine what type they are.  Now, let's take it one step further.  Let's say that payment types aren't just a dummy value like "creditCard" & "check" but instead related another entity called PaymentType.cfc.  This extra relationship is important because PaymentType.cfc might be where you save the some payment gateway information or which payment types are available for specific order types.

Well that special property paymentType can not only be a simple value, but actually a relationship property.  It would look something like this:

Payment.cfc
component persistent="true" table="Payment" discriminatorColumn="paymentType" {
    property name="amount";
    property name="paymentType" fieldtype="many-to-one" cfc="PaymentType" fkcolumn="paymentType" insert="false" update="false";
}

PaymentType.cfc
component persistent="true" table="PaymentType" {
    property name="paymentType" filedtype="id";
    property name="active";
    property name="paymentGateway";
}

Notice that I still leave insert & update to false so that the value can be managed by the type of payment component you are using.  To explain further, now when you do entityNew("paymentCreditCard") it will automatically have the relationship set to the entity in the PaymentType table with the id of "creditCard".  Also when you are looping over all of the payments for a specific order you can do something like this:

order = entityLoad("Order", 1);
for( var i=1; i <= arrayLen( order.getPayments() ); i++) {
    gatwayToProcessWith = order.getPayments()[i].getPaymentType().getPaymentGateway();
}

While this specific example will probably not fit your requirements exactly, I hope that it opens you up to some new ideas.  If you would like to check out some actual code that uses this methodology, you can have a look at this folder that has the Order Payment components of the open source eCommerce project Slatwall on github.

Comments

Aaron Greenlee Posted: June 2, 2011, 1:08 AM

Very cool! Thanks for sharing.

Greg Moser Posted: June 2, 2011, 1:32 AM

Thanks Aaron!

Tony Garcia Posted: June 2, 2011, 11:32 PM

Good stuff Greg! One comment though -- in your example code for PaymentCheck.cfc above, I think you meant it to have properties of accountNumber and routingNumber and not amount, since that has already been defined in its parent class (Payment.cfc). Just didn't want readers to be confused.

Greg Moser Posted: June 2, 2011, 11:36 PM

Ya, you are 100% right Tony. Thanks for catching that typo, all fixed now.

Peter Posted: June 5, 2011, 4:18 AM

Thanks for your post on Cf ORM.

I'm intresteded to hear how the community hands scheme / data migrations on both development / production databases. Specificy, when changing presisted cfc's may expect a specific data structure to be in place for historical data and / or destory data?

Greg Moser Posted: June 6, 2011, 5:24 PM

@Peter

That is an interesting point regarding the management of your database Scheme, specifically in relationship to develop vs production, and quite honestly it seems like a question that fall under use-case and opinion.

The underlying question is fundamentally, "Can I count on Hibernate to manage and update my DB Scheme?"

Sounds like a good topic to explore further.

Post a Comment
  1. Leave this field empty

Required Field