i Know Kung Foo Consulting

Object Oriented Coldfusion : 7.1 : A Basic Data Access Object (DAO)

As discussed part 6, a bean encapsulates a single record of data and that data doesn't necessarily have to come from a single database table. But in this example, it does.

Table of Contents

  1. Some People We Know
    1. A Contact Database
    2. A Contact Bean
    3. A Contact Bean Test Page
  2. The Good, the Bad and the CRUDly
    1. Constructor
    2. Read()
      1. Dissected
      2. Test Page
    3. Create()
      1. Dissected
      2. Test Page
    4. Update()
      1. Dissected
      2. Test Page
    5. Delete()
      1. Dissected
      2. Test Page
  3. What if I want the Contact's Category Label?
  4. How do I use this In Real Life?

A: Some people we know

A.1: A Contact Database

Let's start off with a couple of tables: Contacts and Categories. Each Contact record can have at most one Category, but each Category record can be mapped to multiple Contacts. For those new to database design, this represents a one-to-many (1:*) relationship.

Hogwarts Database

CATEGORIES
CATEGORY_IDCATEGORY_LABEL
1Student
2Instructor
3Staff

CONTACTS
CONTACT_IDCATEGORY_IDFIRST_NAME LAST_NAME
11HarryPotter
21HermioneGranger
31RonWeasley
42MinervaMcGonagall
53ArgusFilch

From these tables, we can see that

  1. Harry, Ron and Hermione are students
  2. Minerva McGonagall is an Instructor
  3. Argus Filch is a member of the Staff

A.2: A Contact Bean

Now we need a Bean to encapsulate a record of Contact data.

Contact.cfc
<cfcomponent>

<cffunction name="init" access="public"
   output="false" returntype="Contact">


   <cfargument name="CONTACT_ID" required="false"
      type="numeric" default="0" hint="Primary Key" />

   <cfargument name="CATEGORY_ID" required="false"
      type="numeric" default="0" hint="Foreign Key" />

   <cfargument name="FIRST_NAME" required="false"
      type="string" default="" hint="Contact's first name" />

   <cfargument name="LAST_NAME" required="false"
      type="string" default="" hint="Contact's last name" />


   <cfset variables.instance = structNew() />

   <cfset setContactID( arguments.CONTACT_ID ) />
   <cfset setCategoryID( arguments.CATEGORY_ID ) />
   <cfset setFirstName( arguments.FIRST_NAME ) />
   <cfset setLastName( arguments.LAST_NAME ) />

   <cfreturn this />

</cffunction>

<!--- CONTACTS.CONTACT_ID (Primary Key) --->

<cffunction name="setContactID" access="private"
   output="false" returntype="void">

   <cfargument name="CONTACT_ID" required="true"
      type="numeric" hint="Primary Key" />

   <cfset variables.instance.CONTACT_ID = arguments.CONTACT_ID />
</cffunction>

<cffunction name="getContactID" access="public"
   output="false" returntype="numeric">

   <cfreturn variables.instance.CONTACT_ID />
</cffunction>

<!--- CONTACTS.CATEGORY_ID (Foreign Key: CATEGORIES.CATEGORY_ID) --->

<cffunction name="setCategoryID" access="private"
   output="false" returntype="void">

   <cfargument name="CATEGORY_ID" required="true"
      type="numeric" hint="Foreign Key" />

   <cfset variables.instance.CATEGORY_ID = arguments.CATEGORY_ID />
</cffunction>

<cffunction name="getCategoryID" access="public"
   output="false" returntype="numeric">

   <cfreturn variables.instance.CATEGORY_ID />
</cffunction>

<!--- CONTACTS.FIRST_NAME --->

<cffunction name="setFirstName" access="private"
   output="false" returntype="void">

   <cfargument name="FIRST_NAME" required="true"
      type="string" hint="First name" />

   <cfset variables.instance.FIRST_NAME = arguments.FIRST_NAME />
</cffunction>

<cffunction name="getFirstName" access="public"
   output="false" returntype="string">

   <cfreturn variables.instance.FIRST_NAME />
</cffunction>

<!--- CONTACTS.LAST_NAME --->

<cffunction name="setLastName" access="private"
   output="false" returntype="void">

   <cfargument name="LAST_NAME" required="true"
      type="string" hint="Last Name" />

   <cfset variables.instance.LAST_NAME = arguments.LAST_NAME />
</cffunction>

<cffunction name="getLastName" access="public"
   output="false" returntype="string">

   <cfreturn variables.instance.LAST_NAME />
</cffunction>

</cfcomponent>

A.3: A Contact Bean Test Page

Finally, let's make a test page and create an empty instance of the bean.

contact_bean.cfm
<cfset contact = createObject("component", "Contact").init() />

<cfoutput>
ContactID: #contact.getContactID()#<br />
CategoryID: #contact.getCategoryID()#<br />
First Name: #contact.getFirstName()#<br />
Last Name: #contact.getLastName()#
</cfoutput>

output ContactID: 0
CategoryID: 0
First Name:
Last Name:

B: The Good, the Bad and the CRUDly

Now we're going to create a basic Data Access Object that will abstract database interactions related to Contact data. We'll start with four simple database functions:

  1. Create
  2. Read
  3. Update
  4. Delete

B.1: Constructor

Before we get to the database functions, we need to create a constructor for the object. Remember that the constructor's returntype should be the same as the name of the object: ContactDAO.

And just as with the Gateway Object,

  1. we're going to inject the value of the data source into the DAO
  2. we're going to (most often) create only one instance of the DAO in the application scope so it can be referenced from memory by any process

ContactDAO.cfc - init()
<cffunction name="init" access="public" output="false"
   returntype="ContactDAO">

   <cfargument name="DSN" required="true" type="string"
      hint="datasource" />

   <cfset variables.DSN = arguments.DSN />
   <cfreturn this />
</cffunction>
Creating a instance:

<cfset contactDAO = createObject("component", "ContactDAO").init( DSN = variables.DSN ) />

Application.cfc : onApplicationStart()
<cfset application.DSN = "myData" />

<cfset application.contactGateway = createObject("component", "cfc.mySite.contacts.ContactGateway").init( DSN = application.DSN ) />
<cfset application.contactDAO = createObject("component", "cfc.mySite.contacts.ContactDAO").init( DSN = application.DSN ) />

B.2: Read()

Now we need to read a specific record and populate the bean.

ContactDAO.cfc - read()
<cffunction name="read" access="public" output="false"
   returntype="boolean">
<!--- [1] --->

   <!--- [2] --->
   <cfargument name="contact" required="true"
      type="Contact" hint="Contact bean" />

   
   <!--- [3] --->
   <cfset var qReadOne = "" />
   
   <!--- [4] --->
   <cfquery name="qReadOne"
      datasource="#variables.DSN#">
<!--- [5] --->
      SELECT
         CONTACT_ID,
         CATEGORY_ID,
         FIRST_NAME,
         LAST_NAME
      FROM
         CONTACTS
      WHERE <!--- [6] --->
         CONTACT_ID = <cfqueryparam
               value="#arguments.contact.getContactID()#"
               cfsqltype="cf_sql_integer" />

   </cfquery>
   
   <!--- [7] --->
   <cfif qReadOne.recordcount eq 1>
   
      <cfset arguments.contact.init(
            CONTACT_ID = qReadOne.CONTACT_ID,
            CATEGORY_ID = qReadOne.CATEGORY_ID,
            FIRST_NAME = qReadOne.FIRST_NAME,
            LAST_NAME = qReadOne.LAST_NAME
            ) />


      <cfreturn true />

   </cfif>
   
   <!--- [8] --->
   <cfreturn false />
   
</cffunction>

B.2.a: Read() - Dissected

Let's dissect the read method:

  1. The returntype is "boolean". This makes it easy to know if you've populated the bean with a record from the database. Alternately, you could make the returntype a struct, so you could return multiple values like a boolean and a string. This would let you set and return a message when the read fails.
     
  2. The function requires a single argument: a Contact bean.
     
  3. We created a function local variable (var scope) named qReadOne.
     
  4. A SELECT query (named qReadOne) to read a specific record from the CONTACTS table.
     
  5. The datasource for the query is being read from the variables scope of the component. This value was set by the init method when the object was created.
     
  6. The WHERE clause of the query needs a single value, taken directly from the Contact bean itself. This means that the value of the bean's CONTACT_ID should have been set before it was passed to this method.
     
  7. Once we've found a single record matching the CONTACT_ID we requested, we can then call the init method of the Contact bean, passing in values from the query.

    Now that we have populated the Contact bean, we can return true from the function and return to the calling process.
     
  8. If we haven't found a matching record, then we return false from the function and return to the calling process.

B.2.b: Read() - Test page

Now we'll update the test page to populate the bean via the DAO.

contact_bean_read.cfm
<!--- Create an Contact bean, populating the CONTACT_ID --->
<cfset contact = createObject("component", "Contact").init( CONTACT_ID = 4 ) />

<cfoutput>
ContactID: #contact.getContactID()#<br />
CategoryID: #contact.getCategoryID()#<br />
First Name: #contact.getFirstName()#<br />
Last Name: #contact.getLastName()#
</cfoutput>

<hr />

<!--- Create an instace of the Contact Data Access Object --->
<cfset contactDAO = createObject("component", "ContactDAO").init( DSN = "myDSN" ) />

<!--- Pass the existing instance of the Contact bean to the read method --->
<cfset contactDAO.read( contact ) />

<!--- Output the data from the COTNACTS table where CONTACT_ID = 4 --->
<cfoutput>
ContactID: #contact.getContactID()#<br />
CategoryID: #contact.getCategoryID()#<br />
First Name: #contact.getFirstName()#<br />
Last Name: #contact.getLastName()#
</cfoutput>

output ContactID: 4
CategoryID: 0
First Name:
Last Name:

ContactID: 4
CategoryID: 2
First Name: Minerva
Last Name: McGonagall

So what just happened?

First, we instantiated a Contact bean, passing in a value for the CONTACT_ID at the same time. That value could come from, among other sources, a URL or FORM scoped variable.

Next, we created an instance of ContactDAO. Then we passed a reference to the Contact bean into the read method of the DAO. We did NOT pass in the actual instance of the Contact bean, we only passed a reference to it.

With Coldfusion, Objects are passed by reference.

(Read those last few lines over and over until they are burned into your memory.)

Inside the DAO's read method, the database was read, the Contact bean's init() method was called and data from the SELECT query was passed into it. When this happens, we're populating the actual instance of the Contact bean at the level of the calling process or page.

This is why we're returning a boolean value and not returning a Contact bean. The Contact bean was passed by reference, so there is no actual bean to return.

B.3: Create()

ContactDAO.cfc - create()
<cffunction name="create" access="public" output="false"
   returntype="boolean">
<!--- [1] --->

   <!--- [2] --->
   <cfargument name="contact" required="true"
      type="Contact" hint="Contact bean" />

   <!--- [3] --->
   <cfset var qCreateContact = "" />
   <!--- [4] --->
   <cftransaction action="begin">

      <!--- [5] --->
      <cftry>

         <!--- [6] --->
         <cfquery name="qCreateContact"
            datasource="#variables.DSN#">

            INSERT INTO
               Contacts
               (
                  CATEGORY_ID,
                  FIRST_NAME,
                  LAST_NAME
               )
            VALUES
               (
                  <cfqueryparam
                     value="#arguments.contact.getCategoryID()#"
                     cfsqltype="cf_sql_integer" />
,
                  <cfqueryparam
                     value="#arguments.contact.getFirstName()#"
                     cfsqltype="cf_sql_varchar" />
,
                  <cfqueryparam
                     value="#arguments.contact.getLastName()#"
                     cfsqltype="cf_sql_varchar" />

               )
         </cfquery>

         <!--- [7] --->
         <cfcatch type="database">
            <!--- [7.1] --->
            <cftransaction action="rollback" />
            <!--- [7.2] --->
            <cfreturn false />
         </cfcatch>

      </cftry>

   <!--- [8] --->
   </cftransaction>

   <!--- [9] --->
   <cfreturn true />

</cffunction>

B.3.a: Create() - Dissected

Let's dissect the create method:

  1. The returntype is "boolean".
     
  2. The function requires a single argument: a Contact bean.
     
  3. We created a function local variable (var scope) named qCreateContact.
     
  4. If your database can handle transactions, use cftransaction to begin one.
     
  5. Use cftry (with cfcatch) to handle any errors we may encounter with the query.
     
  6. An INSERT query (named qCreateContact) to add a record to the Contacts table.
     
  7. Should an error occur during the INSERT, the cfcatch will be triggered, allowing us to
     
    1. Rollback the transaction: This means that all database processes within the cftransaction tags will be pulled back, as if they all had not run at all.
       
    2. Since the INSERT failed, we can return false from the function and return to the calling process.
       
  8. If the INSERT completed correctly, end (commit) the transaction to the database. If your database does not automatically commit transactions, then you'll have to add
    <cftransaction action="commit" />
    before closing it.
     
  9. Since the INSERT completed correctly, we can return true from the function and return to the calling process.
     

B.3.b: Create() - Test Page

contact_bean_create.cfm
<cfset contact = createObject("component", "Contact").init(
      CATEGORY_ID = 2,
      FIRST_NAME = "Quirinus",
      LAST_NAME = "Quirrell"
      ) />

      
<cfset contactDAO = createObject("component", "ContactDAO").init( DSN = "myDSN" ) />

<cfset contactDAO.create( contact ) />

Notice that we did not pass in a value for the CONTACT_ID. More often than not, the Primary Key of a database table like our Contacts table uses an auto-incrementing numeric value of some sort. This mean that the value of CONTACT_ID for a new record will be generated by the database, so there's no need to pass one to the bean when creating a new record.

If the create method completed correctly, then the Contacts table now has a new entry.

Hogwarts Database
CONTACTS
CONTACT_IDCATEGORY_IDFIRST_NAME LAST_NAME
11HarryPotter
21HermioneGranger
31RonWeasley
42MinervaMcGonagall
53ArgusFilch
62QuirinusQuirrell

B.4: Update()

ContactDAO.cfc - update()
<cffunction name="update" access="public" output="false"
   returntype="boolean">


   <cfargument name="contact" required="true"
      type="Contact" hint="Contact bean" />


   <cfset var qUpdateContact = "" />

   <cftransaction action="begin">

      <cftry>

         <cfquery name="qUpdateContact"
            datasource="#variables.DSN#">

            UPDATE
               Contacts
            SET
            (
               CATEGORY_ID = <cfqueryparam
                     value="#arguments.contact.getCategoryID()#"
                     cfsqltype="cf_sql_integer" />
,
               FIRST_NAME = <cfqueryparam
                     value="#arguments.contact.getFirstName()#"
                     cfsqltype="cf_sql_varchar" />
,
               LAST_NAME = <cfqueryparam
                     value="#arguments.contact.getLastName()#"
                     cfsqltype="cf_sql_varchar" />

            )
            WHERE
               CONTACT_ID = <cfqueryparam
                     value="#arguments.contact.getContactID()#"
                     cfsqltype="cf_sql_integer" />

         </cfquery>
         
         <cfcatch type="database">
            <cftransaction action="rollback" />
            <cfreturn false />
         </cfcatch>

      </cftry>

   </cftransaction>

   <cfreturn true />

</cffunction>

B.4.a: Update() - Dissected

There's no need to dissect this method since the only difference between create() and update() is the actual SQL involved.

Now, it's commonly known that Hogwarts has a problem keeping teachers of a certain subject around. So let's update the database to show the replacement for Professor Quirrell.

B.4.b: Update() - Test Page

contact_bean_update.cfm
<cfset contact = createObject("component", "Contact").init(
      CONTACT_ID = 6,
      CATEGORY_ID = 2,
      FIRST_NAME = "Gilderoy",
      LAST_NAME = "Lockhart"
      ) />


<cfset contactDAO = createObject("component", "ContactDAO").init( DSN = "myDSN" ) />

<cfset contactDAO.update( contact ) />

Since we're updating an existing record, we have to make sure to pass in the CONTACT_ID this time. If the update method completed correctly, then Professor Quirrell has been replaced.

Hogwarts Database
CONTACTS
CONTACT_IDCATEGORY_IDFIRST_NAME LAST_NAME
11HarryPotter
21HermioneGranger
31RonWeasley
42MinervaMcGonagall
53ArgusFilch
62GilderoyLockhart

B.5: Delete()

ContactDAO.cfc - delete()
<cffunction name="delete" access="public" output="false"
   returntype="boolean">


   <cfargument name="contact" required="true"
      type="Contact" hint="Contact bean" />


   <cfset var qDeleteContact = "" />

   <cftransaction action="begin">

      <cftry>

         <cfquery name="qDeleteContact"
            datasource="#variables.DSN#">

            DELETE
            FROM
               Contacts
            WHERE
               CONTACT_ID = <cfqueryparam
                     value="#arguments.contact.getContactID()#"
                     cfsqltype="cf_sql_integer" />

         </cfquery>
         
         <cfcatch type="database">
            <cftransaction action="rollback" />
            <cfreturn false />
         </cfcatch>

      </cftry>

   </cftransaction>

   <cfreturn true />

</cffunction>

B.5.a: Delete() - Dissected

Again there should be no need to dissect this method as the SQL is all that different between create(), update() and delete().

Rather than try and keep up with all the changes or Instructors at Hogwarts, let's just get rid of Professor Lockhart instead of updating his position.

B.5.b: Delete() - Test Page

contact_bean_delete.cfm
<cfset contact = createObject("component", "Contact").init(
      CONTACT_ID = 6
      ) />


<cfset contactDAO = createObject("component", "ContactDAO").init( DSN = "myDSN" ) />

<cfset contactDAO.delete( contact ) />

We're deleting a specific record, so all we really need in the Contact bean is the CONTACT_ID. However, you can populate it completely if you like. If the delete method completed correctly, then the record we created earlier will have been removed from the Contacts table.

Hogwarts Database
CONTACTS
CONTACT_IDCATEGORY_IDFIRST_NAME LAST_NAME
11HarryPotter
21HermioneGranger
31RonWeasley
42MinervaMcGonagall
53ArgusFilch

C: What if I want the Contact's Category Label?

This question (essentially) was asked by "Shane":

"Ok, so what about lookup tables?

In procedural style, you could just perform the lookup joins in the query on the page. But with OO, your bean would only contain the lookup ID, and not the lookup value.

So what's the best practice? Create a lookup object and feed your bean's lookup IDs to it whenever needed? Modify your read method in the bean to include lookup values? Create separate lookup methods in the bean or gateway?"

The answer is simple:

Add CATEGORY_LABEL as a Propery of the Bean.

C.1: First you need to add the correct getter and setter

Contact.cfc - new methods
<cffunction name="setCategoryLabel" access="private" output="false" returntype="void">
   <cfargument name="CATEGORY_LABEL" required="true" type="string" hint="Category Label" />
   <cfset variables.instance.CATEGORY_LABEL = arguments.CATEGORY_LABEL />
</cffunction>
<cffunction name="getCategoryLabel" access="public" output="false" returntype="string">
   <cfreturn variables.instance.CATEGORY_LABEL />
</cffunction>

C.2: Then update the Constructor to accept the new data

Contact.cfc - update to init()
<cffunction name="init" access="public" output="false" returntype="Contact">

   <cfargument name="CONTACT_ID" required="false" type="numeric" default="0" hint="Primary Key" />
   <cfargument name="CATEGORY_ID" required="false" type="numeric" default="0" hint="Foreign Key" />
   <cfargument name="FIRST_NAME" required="false" type="string" default="" hint="Contact's first name" />
   <cfargument name="LAST_NAME" required="false" type="string" default="" hint="Contact's last name" />
   <cfargument name="CATEGORY_LABEL" required="false" type="string" default="" hint="Contact's Category Label" />

   <cfset variables.instance = structNew() />

   <cfset setContactID( arguments.CONTACT_ID ) />
   <cfset setCategoryID( arguments.CATEGORY_ID ) />
   <cfset setFirstName( arguments.FIRST_NAME ) />
   <cfset setLastName( arguments.LAST_NAME ) />
   <cfset setCategoryLabel( arguments.CATEGORY_LABEL ) />

   <cfreturn this />

</cffunction>

C.3: Finally, you'll need to update the query in the read() method

Read() should now also retrieve the Category Label for the selected Contact and pass it into the Contact bean with the rest of the data.

ContactDAO.cfc - update to read()
<cffunction name="read" access="public" output="false" returntype="boolean">

   <cfargument name="contact" required="true" type="Contact" hint="Contact bean" />

   <cfset var qReadOne = "" />

   <cfquery name="qReadOne" datasource="#variables.DSN#">
      SELECT
         a.CONTACT_ID,
         a.CATEGORY_ID,
         a.FIRST_NAME,
         a.LAST_NAME,
         b.CATEGORY_LABEL
      FROM
         CONTACTS a
      LEFT JOIN
         CATEGORIES b
            ON b.CATEGORY_ID = a.CATEGORY_ID
      WHERE
         CONTACT_ID = <cfqueryparam
                     value="#arguments.contact.getContactID()#"
                     cfsqltype="cf_sql_integer" />

   </cfquery>

   <cfif qReadOne.recordcount eq 0>

      <cfreturn false />

   <cfelse>

      <cfset arguments.contact.init(
            CONTACT_ID = qReadOne.CONTACT_ID,
            CATEGORY_ID = qReadOne.CATEGORY_ID,
            FIRST_NAME = qReadOne.FIRST_NAME,
            LAST_NAME = qReadOne.LAST_NAME,
            CATEGORY_LABEL = qReadOne.CATEGORY_LABEL
            ) />


   </cfif>

   <cfreturn true />

</cffunction>

Simple, huh?

D: Ok, I get it now (I think), but how do I use it In Real Life?

Most of the examples shown are using hard-coded values, but in a real-world application they'll often be read from FORM or URL scoped variables. At times, they'll come from SESSION or APPLICATION scoped variables. If you're using a framework like Mach-II, Fusebox or Model-Glue, those values will be passed in from some process in the framework.

Rather than extend this post past the code for a Basic Data Access Object, I'll move some examples to the next post.


Comments (Comment Moderation is enabled. Your comment will not appear until approved.)
duncan's Gravatar "Each Contact can have at most one Category. For those new to database design, this represents a one-to-one (1:1) relationship." Surely you mean one-to-many (1:M); one Category has many Contacts.
# Posted By duncan | 10/23/07 5:01 AM
Michael White's Gravatar pretty soon you'll have enough for a full course!
# Posted By Michael White | 10/23/07 8:53 AM
Adrian J. Moreno's Gravatar @duncan: Surely, I did. Thanks.
# Posted By Adrian J. Moreno | 10/23/07 1:10 PM
Shane's Gravatar Excellent. Adding lookup values as bean fields makes the most sense intuitively, but none of the other tutorials I'd seen had dealt with them. Thanks!
# Posted By Shane | 10/25/07 12:41 PM
Bruce's Gravatar Very good tutorial and excellent notes. Your explanations are easy to follow.

One point I am curious about since how you do this differs from the methodology I use.

In the ContactDAO.cfc read function, why do you do use the init function of the Contact.cfc to set the values of the Contact's instance variables (variables.instance.CONTACT_ID, variables.instance.CATEGORY_ID) after doing the database query instead of just calling the set functions directly on the contact argument passed to the read function (arguments.contact.setContactID(qReadOne.CONTACT_ID) )?

Calling the set functions will set the values of the instance variables for the contact bean passed to the read function and seems a bit cleaner and easier to understand. I think it would also be less overhead then calling the Contact.cfc init function (which just calls the set functions anyway).
# Posted By Bruce | 10/26/07 11:28 AM
Adrian J. Moreno's Gravatar @Bruce: Glad you like the series.

The reason I use the init() method is because my Contact bean has private setters. If the setters were public, then you can call the individual setProperty() methods instead of only init(). I covered this at the end of part 6, but I may need to go back and reference that section in the examples.

There's no real overhead using init() to pass values to the setters. I prefer to use private setters to ensure that no outside process can change the value of a bean without my knowing about it. For example, it's easier to search for the string

Bean").init(

than to search for any number of public setter calls.
# Posted By Adrian J. Moreno | 10/26/07 1:32 PM
Bruce's Gravatar Adrian - thanks for the explanation. I read back over your part 6 tutorial.

I do respectfully disagree with your use of private setter functions for your bean for the following reasons:

1. Requiring the user of the Bean to call init() any time he/she wants to update one or more of the Bean's properties seems like overkill and is not intuitive.

2. I think there is overhead with calling the init() function since you recreate the variables.instance structure and return a Bean object that is not consumed by anyone.

3. Your use of init() seems somewhat counter to the best practice described by several other developers; that is, init() is a way of specifying a constructor for a CFC and is used to create the CFC object and provide initial values for the properties. A constructor once called should not be called again (and in most OOP languages cannot be called again).

However, please do not think my minor disagreement with your use of the private set property functions lessens my appreciation and admiration for your work on these tutorials. Your series on Object Oriented ColdFusion is excellent and should be required reading for all ColdFusion developers.
# Posted By Bruce | 10/26/07 9:09 PM
Adrian J.Moreno's Gravatar @Bruce, thanks for the input. These are all valid concerns, but rather than go into detail in the comments section, I'm going to work out another post for this topic. I think this is worth discussing in more detail.
# Posted By Adrian J.Moreno | 10/28/07 2:15 PM
Larkin Cunningham's Gravatar Great set of tutorials. Some other options for DAOs that I like...

1) Retrieve the last generated id in create() and populate the candidate id using a public setter. You might want to perform subsequent actions related to the inserted row / bean and knowing the id will be useful - maybe even just to display the new id on a web page. Most databases provide a way to do this, e.g. @@IDENTITY in SQL Server, LAST_INSERTED_ID() in MySQL, SequenceName.CURRVAL in Oracle.

2) use a wrapper function called persist() where you check if the id is 0, and if so call create(), otherwise call update(), e.g.

<cffunction name="persist" displayname="Persist" hint="Persist the Contact" access="public" output="false" returntype="boolean">
   <cfargument name="contact" type="Contact" required="true" hint="Contact Bean">
   
   <cfif contact.contactID is 0) <!--- i.e. this is a new add --->
      <cfreturn create(candidate)>
   <cfelse>
      <cfreturn update(candidate)>
   </cfscript>
   
</cffunction>

In this case you can optionally make both create and update private functions.
# Posted By Larkin Cunningham | 2/28/08 11:37 AM
David Walton's Gravatar I am working at a site where they use application.cfm; so is there an alternative to using onApplicationStart()?
As I said before, this is a GREAT set of tutorials.
# Posted By David Walton | 9/19/08 5:56 PM
Adrian J. Moreno's Gravatar @David: Thanks. With Application.cfm, you have to resort to the old standby:

<cfif not structKeyExists( application, "contactDAO" )>
<cfset . . . />
</cfif>

This ensures that the application variable is set only once per application start.
# Posted By Adrian J. Moreno | 9/19/08 6:19 PM