CFTRANSACTION and exception handling
The default behavior of the CFTRANSACTION tag is such that if you do not explicitly command
it to commit or rollback, it does so implicitly for you. This is the technique shown in Listings
10-1 and 10-3. Many ColdFusion developers are used to coding that way, but if you rely on the
implicit behavior that CFTRANSACTION automatically commits and rolls back for you, stop
doing so right now. In our own tests, ColdFusion MX slows to a crawl if CFTRANSACTION tags
are not explicitly coded with BEGIN, COMMIT, and ROLLBACK commands.
To make sure that you are committing only if everything works correctly and rolling back
only if it doesn’t, you should be very aware of CFTRANSACTION’s behavior with respect to
exception handling and also how to correctly nest CFTRANSACTION and CFTRY tags.
The best practice for coding CFTRANSACTION is as follows:
1. CFSET a flag variable TRUE.
2. Begin a CFTRANSACTION.
3. Open a CFTRY block.
4. Code any database queries you need.
5. Test for exceptions with CFCATCH blocks as necessary.
6. Within any and all CFCATCH blocks that would indicate a failure of any part of the transaction,
CFSET the flag FALSE.
7. Close the CFTRY block.
8. Test the flag: Commit the transaction if TRUE and roll it back if FALSE.
9. Close the CFTRANSACTION.
Listing 10-4 rewrites Listing 10-1 to incorporate CFTRANSACTION best practices
124 trang |
Chia sẻ: tlsuongmuoi | Lượt xem: 2253 | Lượt tải: 0
Bạn đang xem trước 20 trang tài liệu Using ColdFusion MX with Databases, để xem tài liệu hoàn chỉnh bạn click vào nút DOWNLOAD ở trên
208 Part II ✦ Using ColdFusion MX with Databases
Listing 10-2 (continued)
RAISERROR 50001 ‘The OrderItem could not be inserted.’
ROLLBACK TRANSACTION
RETURN
END
UPDATE
InventoryItem
SET
AvailableToSell = AvailableToSell - 10
WHERE
ItemNumber = ‘CAS30-BLK’
IF @@ERROR != 0
BEGIN
RAISERROR 50002 ‘The InventoryItem could not be updated.’
ROLLBACK TRANSACTION
RETURN
END
COMMIT TRANSACTION
The syntax is a little different, but the principles are very similar, aren’t they? It’s really just a
matter of learning both methods for implementing transactions and then controlling them as
close to the database server as your application enables you to do so.
A good example of when you need to control a transaction within ColdFusion is whenever you
are passing multiple rows of data from ColdFusion to the database server, and you want to
encapsulate all those queries into a single transaction. Listing 10-3 illustrates this example.
Listing 10-3: Inserting multiple rows within a single transaction
<cfquery name=”InsertOrderItems”
datasource=”CFMXBible”>
INSERT INTO OrderItem (
SalesOrderID,
ItemNumber,
Description,
UnitPrice,
Quantity
)
VALUES (
#Val(arSalesOrderID[i])#,
‘#Trim(arItemNumber[i])#’,
‘#Trim(arDescription[i])#’,
#Val(arUnitPrice[i])#,
#Val(arQuantity[i])#
13546228 ch10.F 1/30/03 10:50 AM Page 208
209Chapter 10 ✦ Using Advanced Database Techniques
)
You currently have no easy and effective way to directly send multidimensional data from
ColdFusion to a database server in a single statement. Until such a method exists, you must
loop over ColdFusion arrays or structures and call CFQUERY once for each unit of data to be
stored in the database.
CFTRANSACTION and exception handling
The default behavior of the CFTRANSACTION tag is such that if you do not explicitly command
it to commit or rollback, it does so implicitly for you. This is the technique shown in Listings
10-1 and 10-3. Many ColdFusion developers are used to coding that way, but if you rely on the
implicit behavior that CFTRANSACTION automatically commits and rolls back for you, stop
doing so right now. In our own tests, ColdFusion MX slows to a crawl if CFTRANSACTION tags
are not explicitly coded with BEGIN, COMMIT, and ROLLBACK commands.
To make sure that you are committing only if everything works correctly and rolling back
only if it doesn’t, you should be very aware of CFTRANSACTION’s behavior with respect to
exception handling and also how to correctly nest CFTRANSACTION and CFTRY tags.
The best practice for coding CFTRANSACTION is as follows:
1. CFSET a flag variable TRUE.
2. Begin a CFTRANSACTION.
3. Open a CFTRY block.
4. Code any database queries you need.
5. Test for exceptions with CFCATCH blocks as necessary.
6. Within any and all CFCATCH blocks that would indicate a failure of any part of the trans-
action, CFSET the flag FALSE.
7. Close the CFTRY block.
8. Test the flag: Commit the transaction if TRUE and roll it back if FALSE.
9. Close the CFTRANSACTION.
Listing 10-4 rewrites Listing 10-1 to incorporate CFTRANSACTION best practices.
Listing 10-4: Combining CFTRANSACTION with CFTRY and CFCATCH
<cfquery name=”InsertOrderItem”
datasource=”CFMXBible”>
INSERT INTO OrderItem (
Continued
13546228 ch10.F 1/30/03 10:50 AM Page 209
210 Part II ✦ Using ColdFusion MX with Databases
Listing 10-4 (continued)
SalesOrderID,
ItemNumber,
Description,
UnitPrice,
Quantity
)
VALUES (
1,
‘CAS30-BLK’,
‘30-Minute Cassette, Black Case’,
1.05,
10
)
<!--- If an error occurs after the first query,
control immediately falls to CFCATCH --->
<cfquery name=”UpdateInventory”
datasource=”CFMXBible”>
UPDATE
InventoryItem
SET
AvailableToSell = AvailableToSell - 10
WHERE
ItemNumber = ‘CAS30-BLK’
As soon as any one of the queries throws an exception, program flow falls immediately to the
applicable CFCATCH block, which then takes control and sets the OKtoCommit flag to FALSE.
This circumvents any attempts to execute any other queries in the transaction. After the
CFTRY block, the flag is tested and the entire transaction is either committed to disk or rolled
back as if nothing ever happened.
If you’re upgrading from an earlier version of ColdFusion Server, go right now and run the
Find command on all your code for CFTRANSACTION, inspect your code, and determine
whether you need to shore up your CFTRANSACTION tags.
13546228 ch10.F 1/30/03 10:50 AM Page 210
211Chapter 10 ✦ Using Advanced Database Techniques
Transaction isolation
Transaction isolation is one of the most misunderstood concepts of transactions, possibly
because it forces you to think in terms of multiple users executing various multitable transac-
tions over time, and that isn’t easy. If you don’t quite get it at first, that’s okay — don’t be so
hard on yourself.
Transaction isolation is the degree to which the effects of one transaction are isolated from
those of other transactions that are also trying to execute at the same time. Isolating the
effects of one transaction from those of another is controlled through the database server’s
data-locking mechanisms, and these are, in turn, controlled through the SET TRANSACTION
ISOLATION LEVEL command.
Transaction isolation is a balancing act between concurrency and consistency. In other words,
“how many people can bang on it at once” versus “how accurate is the data at any given
point in time.” At one extreme, you can have everyone accessing data simultaneously (high
concurrency) but at the cost of them working with data that may still be undergoing change
as they access it (low consistency). At the other extreme, you can have each transaction
absolutely separated from every other transaction such that everything appears to have
been performed on a single-user machine (high consistency) but at the cost of considerably
slowing down multi-user access (low concurrency).
The key to a high performance database is executing each transaction with the minimum
amount of locking possible to enable sufficiently consistent data. Doing so enables your
database to operate with the highest possibly concurrency while affording it the data
consistency that it requires.
All in all, you have the following four levels of transaction isolation:
✦ Read Uncommitted
✦ Read Committed
✦ Repeatable Read
✦ Serializable
Various database servers have various degrees of support for transaction isolation: Some
support all four levels, some support none, and most serious database products support only
two or three, so carefully read the documentation for both your database product and its
driver before you attempt to set a transaction’s isolation level.
The following sections describe what each of these levels mean.
Read Uncommitted
Read Uncommitted is the least-used level of isolation, because it is the least safe of all.
Remember that all data operations (selects, inserts, updates, deletes) take place in the
database server’s RAM and not on its disk. After data is requested, the database engine first
looks for it in RAM — the data is possibly already there from another user’s request — and if
the data isn’t there, it retrieves the necessary data from disk and loads it into RAM for use.
If the data is being modified, after the database server sees that its operations in RAM are
successful, it becomes “committed” to writing such modifications to disk, where they become
permanent changes.
Between the time that data is being modified in RAM and the time that the database server is
committed to writing such modifications to disk, the data is said to be in an uncommitted
state. If you set the isolation level to Read Uncommitted and then perform a query of data
13546228 ch10.F 1/30/03 10:50 AM Page 211
212 Part II ✦ Using ColdFusion MX with Databases
that is in the process of being updated by another user, the result set contains data exactly as
it appears with the other user’s modifications at that moment — even if those modifications
have not yet been written to disk. If the original transaction rolls back, its modifications never
really existed, and therefore the second transaction’s read of its uncommitted data was
“dirty.” This is what the term dirty read means.
Read Uncommitted can perform dirty reads because it has nothing at all to do with locks of
any kind. At this level of isolation, locks are neither set by a transaction nor are they honored
if set by another transaction. If you query the database to give you a set of data, it returns to
you whatever values are in that data set at the very moment that it is requested, regardless of
anything else that’s going on at the time.
Read Uncommitted has the highest concurrency and the lowest consistency. It is set as
follows:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
go
BEGIN TRANSACTION
. . .
Oracle doesn’t support the Read Uncommitted isolation level.
Read Committed
If you take one step up in the consistency department, you take one corresponding step
down in the concurrency department, and you end up at the Read Committed isolation level.
The difference between Read Uncommitted and Read Committed is that Read Committed
sees only data that has been committed to being written out to disk — in other words, locks
placed on the data modified by the first user’s transaction are honored by the second user’s
transaction, so data read by the second transaction is “clean” rather than “dirty.”
Although Read Committed doesn’t permit dirty reads, it does permit what are called
nonrepeatable reads, which can also become a problem. If you have a transaction that reads
a set of data, performs some operation, and then reads the same set of data, for example,
another user’s transaction could have modified your set of data in between the two times
that it was read, thereby causing the second read to appear different from the first. In other
words, the read was nonrepeatable.
Read Committed is the default level of isolation for most database products and offers a good
combination of concurrency and consistency for most applications. It is set as follows:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
go
BEGIN TRANSACTION
. . .
Repeatable Read
If your isolation level is set to Repeatable Read, no one else’s transactions can affect the
consistency between one read of a data set at the beginning of the transaction and another
read toward its end. By data set, we are talking about exactly the same collection of rows —
not the results of a repeat of the SELECT statement that produced those rows.
You see, a repeat of the SELECT statement may return rows that satisfy the WHERE clause that
were inserted mid-transaction by another user. Such newly inserted rows are called phantom
rows and are permitted under the Repeatable Read isolation level, because Repeatable Read
preserves only the exact collection of rows originally read at the beginning of the transaction.
Note
13546228 ch10.F 1/30/03 10:50 AM Page 212
213Chapter 10 ✦ Using Advanced Database Techniques
Repeatable Read involves more locks on data across transactions, so although it increases
data consistency, concurrency takes a performance hit. It is set as follows:
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
go
BEGIN TRANSACTION
. . .
Oracle doesn’t support the Repeatable Read isolation level.
Serializable
Now you come to the most isolated level of all. Serializable means that all transactions
operate as if they were executed in series, or one after the other. In other words, the
Serializable isolation level reduces your big, expensive, multi-user database server to a
single-user machine so that the effects of one user’s operations are totally unaffected by
those of others. That’s because each user, in effect, hogs the entire database while he is
using it and doesn’t give it up until he finishes.
The Serializable isolation level is the only one that prevents phantom rows, but you should
ask yourself whether your transactions really care about this. As you can imagine, the
Serializable level inflicts a serious and sometimes deadly hit to the multi-user performance
of your system and, as such, should be used only if you have an absolutely compelling
reason for it.
The Serializable isolation level is set as follows:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
go
BEGIN TRANSACTION
. . .
The big picture of transaction isolation
Having a chart that plots isolation level against behavior may be useful, so check out the
following table.
Transaction isolation levels versus behaviors
Isolation Level Dirty Read Nonrepeatable Read Phantom
Read Uncommitted X X X
Read Committed X X
Repeatable Read X
Serializable
With all this about transaction isolation level said, how do you choose the appropriate isolation
level for your transactions? By carefully studying the code in your transactions and considering
their minimum consistency needs with respect to every other possible transaction that could
be occurring simultaneously in the system. It’s not a simple task, but the following list gives you
the basics:
Note
13546228 ch10.F 1/30/03 10:50 AM Page 213
214 Part II ✦ Using ColdFusion MX with Databases
1. Start with your default transaction isolation level set to Read Committed.
2. Examine your first transaction’s code for the tables that it reads from and writes to.
3. If you don’t care whether the data being read is dirty, if the transaction’s modifications
could take place anyway despite other concurrently running transactions, and if no
further tests require a higher level of isolation, you can reduce the isolation level to
Read Uncommitted for that transaction.
4. If you read from the same table twice during the same transaction and whether the first
and second reads are different makes a difference to you, increase your isolation level
to Repeatable Read on that transaction.
5. If you have any transactions that must not see others rows that are inserted while your
transaction is executing, increase the isolation level of that transaction to Serializable.
6. Examine the rest of the transactions in your system by using this technique.
Remember that you must individually consider each of your transactions in parallel with all
other database operations that could possibly occur at the same time with multiple users —
doing so is what makes fine-tuning transaction isolation such a difficult task.
By the way, many developers just go as far as Step 1 and leave it at that. But if you’re going to
use your database in a high-transaction throughput system, it serves you well to take the
hours necessary to carefully analyze your transactions for their optimum isolation level and
dial them in accordingly.
Views
As we state in Chapter 9, no matter how many tables a relational join traverses, regardless of
whether it contains a GROUP BY clause (or anything else for that matter), all query result sets
manifest themselves as one or more rows that contain an identical collection of one or more
columns. So, in a way, query results are virtual tables based on underlying physical tables
of data.
Now imagine if you could take a query statement and define it as a formal database object
that could be accessed just as a table can. Well, you can — that is what is called a view.
Listing 10-5 defines a database view.
Listing 10-5: Defining a database view
CREATE VIEW vwEmployee
AS
SELECT
SSN,
CompanyID,
Firstname,
Lastname,
DateOfBirth
FROM
Employee
13546228 ch10.F 1/30/03 10:50 AM Page 214
215Chapter 10 ✦ Using Advanced Database Techniques
The boldfaced code in Listing 10-5 is just a standard SELECT statement; the rest of the listing
simply encapsulates that SELECT statement as a formal, reusable database object — a view —
with a formal name. Now, whenever you want to see all employees without their salaries, you
can simply perform the following:
SELECT * FROM vwEmployee
Similarly, you can perform more elaborate queries on a view, as if it was a table, as follows:
SELECT
SSN,
Firstname + ‘ ‘ + Lastname AS Fullname,
DateOfBirth
FROM
vwEmployee
WHERE
DateOfBirth > ‘01/01/1960’
ORDER BY
DateOfBirth DESC
In fact, views can be filtered, sorted, and joined just as any physical table can.
Horizontal and vertical masking
Views typically hide elements of data so that what is returned exposes only the data that is
needed. The elements being hid can be specific columns of a table, rows in that table that do
not satisfy a WHERE clause, or a combination of both. Basically, a view masks unneeded data
from your application.
These masks can be either vertical or horizontal. A vertical mask shields specific columns of
a table from returning and is simply the defined collection of underlying table columns that
your view contains. A horizontal mask shields specific rows of a table from returning and is
simply the WHERE clause that supplies the filtering criteria for the view.
You can combine both vertical and horizontal masking in the same view. Listing 10-6, for
example, returns only the SalesOrderID, SaleDate, and OrderTotal columns of only those
sales orders with a Status of 20.
Listing 10-6: A view that combines both vertical and horizontal
masking
CREATE VIEW vwFinishedSalesOrder
AS
SELECT
SalesOrderID,
SaleDate,
Total
FROM
SalesOrder
WHERE
Status = 20
13546228 ch10.F 1/30/03 10:50 AM Page 215
216 Part II ✦ Using ColdFusion MX with Databases
Relational views
Views don’t stop at a single table. In fact, views are often used to simplify complicated
relational joins across multiple tables so that the application developer doesn’t need to
concern himself with such complexities and can instead concentrate on simply displaying
relevant data. Listing 10-7 defines such a relational view.
Listing 10-7: Defining a view across multiple joined tables
CREATE VIEW vwEmployeeCompany
AS
SELECT
e.SSN,
e.Firstname + ‘ ‘ + e.Lastname AS Fullname,
e.DateOfBirth,
c.CompanyName
FROM
Employee e INNER JOIN Company c
ON e.CompanyID = c.CompanyID
The ColdFusion developer now can select all employees born earlier than 1960, hide the
salary column, and display the name of the company for which each works, all by simply
doing the following:
SELECT * FROM vwEmployeeCompany
Similarly, suppose that you tried to perform a SELECT against this view that included the
Salary column, as follows:
SELECT
SSN,
Fullname,
DateOfBirth,
CompanyName
FROM
VwEmployeeCompany
WHERE
Salary > 100000
It would fail, because although the Salary column is a part of the underlying Employee table,
it is not a part of the vwEmployeeCompany view being queried.
Precompiling queries
So why define views at all? Why not just CFINCLUDE a common CFQUERY call wherever that
query is needed? Isn’t that just as good?
No, it isn’t. Not by a long shot. You see, if you send a query to your database server, the
server goes through the complexities and processing expense of parsing the syntax with
which the query is defined, optimizing the query for best performance, and compiling an
execution plan. This very expensive process is what happens almost every time that you
send a plain query to your database — just so that it can run the query!
13546228 ch10.F 1/30/03 10:50 AM Page 216
217Chapter 10 ✦ Using Advanced Database Techniques
If you define a view, your database performs the same parse/optimize/compile process on the
query for which the view is defined. If you call the view, the database server knows that an
appropriate execution plan already exists and executes it directly, thereby eliminating virtually
all the overhead necessary to execute the query and, in the bargain, increasing performance.
You realize the biggest increases in performance by defining views on very complex relational
queries, because these queries are the most difficult and time-consuming to parse and optimize.
Enhancing security
Another benefit that views afford you is the capability to lock down security on your
database. Because a view is a formal database object, you can grant user and group privileges
to it just as you would a table or a stored procedure. If fact, to denying direct access to
physical tables and granting them only on views and stored procedures is a good practice
overall — that way, you are absolutely certain that ColdFusion developers touch only the
data that you want them to access.
Caveats
Views are great for easily selecting only that data that you want to access, but they can also
be used to insert and update data. This can be both a help and a hindrance, depending on
the view.
Suppose that your view is a simple, single-table view containing all the NOT NULL columns of
a table, as shown in Figure 10-1.
Figure 10-1: A simple view containing all critical columns of a single table.
In such a case, you can easily insert and update data through this view, as shown in Listing 10-8.
Listing 10-8: Examples of inserting and updating through a simple view
INSERT INTO vwInventoryItem (
ItemNumber,
Description,
UnitPrice,
AvailableToSell
)
VALUES (
‘CAS30-BLK’,
Continued
InventoryItem.ItemNumber (vPK)
InventoryItem.Description
InventoryItem.UnitPrice
InventoryItem.AvailableToSell
InventoryItem.ReorderLevel
InventoryItem.ReorderQuantity
ItemNumber VARCHAR(15) NOT NULL
Description VARCHAR(40) NOT NULL
UnitPrice NUMERIC(12,2) NOT NULL
AvailableToSell INTEGER NOT NULL
ReorderLevel INTEGER NOT NULL
ReorderQuantity INTEGER NOT NULL
Comments VARCHAR(200) NULL
InventoryItem
vwInventoryItem
13546228 ch10.F 1/30/03 10:50 AM Page 217
218 Part II ✦ Using ColdFusion MX with Databases
Listing 10-8 (continued)
‘30-Minute Cassette, Black Case’,
1.05,
100
)
UPDATE
vwInventoryItem
SET
Description = ‘30-Minute Cassette, Black Case’,
UnitPrice = 1.25,
AvailableToSell = 90
WHERE
ItemNumber = ‘CAS30-BLK’
But if the view does not contain all the critical (that is, NOT NULL) columns from its underlying
table, you cannot insert new rows into the underlying table through that view, because you
cannot supply all the data necessary to create a valid row in that table. The exception to
this rule are NOT NULL columns for which default values are defined; these columns are
automatically assigned their default values on insert if they’re not part of the view. You can
update existing rows through such a view as long as you are setting the values only of those
columns defined in the view — but that’s it.
Problems appear if you attempt to insert, update, or delete by using relational views, because
the action that you think you are performing isn’t always what’s going to happen — and the
results can destroy your production data. In some cases, you throw an error because the
database can’t figure out what you’re trying to accomplish.
Our suggestion is that you never attempt to modify data by using relational views and instead
define specific views and/or stored procedures for data modification or have application
developers insert, update, and delete from the physical tables themselves.
If you are interested in performing inserts, updates, and deletes from relational or complicated
views, read your database product’s documentation regarding “Instead-Of Triggers,” which
replace the code in the triggering statement with code of your own. Although we do touch on
Instead-Of Triggers in Chapter 11, a discussion of using them to resolve relational view data
modification problems is beyond the scope of this book.
Stored Procedures
After you first learned ColdFusion, you performed every call to your database by using a
CFQUERY call. CFQUERY is simple and straightforward, so it is popular with developers
who just want to get the job done, but another method is more scalable and flexible for
manipulating your data: stored procedures.
What is a stored procedure?
At its most basic level, a stored procedure takes the SQL logic that you would normally
write into a CFQUERY call and stores it directly in the database server. This may not sound like
a big deal, and it may not even seem that useful at first, but the added efficiency of stored
procedures can spell a huge performance gain for your ColdFusion applications.
13546228 ch10.F 1/30/03 10:50 AM Page 218
219Chapter 10 ✦ Using Advanced Database Techniques
Whenever ColdFusion Server sends a CFQUERY call to your database server, the SQL logic in
that CFQUERY call is pulled apart, or parsed, and translated into internal machine language
instructions that your database server can directly understand; then these instructions are
put through a process that determines the most efficient methods for executing them against
the database. After the most efficient methods are decided on, they are assembled into an
execution plan that is compiled and executed against the database. With some exceptions,
this process happens every time that you send a CFQUERY call to your database server. That’s
a lot of work to do just to get ready to execute your SQL logic.
A stored procedure eliminates the need to repeat such drudgery every time that it is
executed because it stores the compiled execution plan on the database server, where it
can be called directly by an outside program — such as your ColdFusion application.
And after this procedure is placed into the server’s memory, it remains there until it is
pushed out by some other process that needs to run. So if you have sufficient memory, your
stored procedure most likely stays cached in memory; this means that your database server
doesn’t even need to retrieve the stored procedure object from disk to execute it, resulting in
the fastest possible execution of SQL logic.
Nice theory, but you need to take a look at the nuts and bolts of how it’s done. Listing 10-9
shows a typical CFQUERY call.
Listing 10-9: A typical CFQUERY call
<cfquery name=”GetCompanies”
datasource=”CFMXBible”>
SELECT
CompanyName,
ZipCode
FROM
Company
WHERE
State = ‘#Trim(FORM.State)#’
ORDER BY
ZipCode ASC
To create a stored procedure in Microsoft SQL Server from the SQL logic inside this CFQUERY
call, you add the following code (Listing 10-10) and execute it against the database server (by
using Query Analyzer or some other batch processing utility).
Listing 10-10: Creating a stored procedure from the SQL inside
Listing 10-9
CREATE PROCEDURE sp_GetCompanies (
@State CHAR(2)
)
AS
SELECT
Continued
13546228 ch10.F 1/30/03 10:50 AM Page 219
220 Part II ✦ Using ColdFusion MX with Databases
Listing 10-10 (continued)
CompanyName,
ZipCode
FROM
Company
WHERE
State = @State
ORDER BY
ZipCode ASC
RETURN
After you execute Listing 10-10 against the database, you have a precompiled stored procedure
object, sp_GetCompanies, that you can call from your ColdFusion application.
To call this stored procedure from ColdFusion, you use a CFSTOREDPROC tag in place of
the CFQUERY tag, and you supply the parameter used by the WHERE clause through the
CFPROCPARAM tag. This process is shown in Listing 10-11.
Listing 10-11: Calling the stored procedure created in Listing 10-10
<cfstoredproc procedure=”sp_GetCompanies”
datasource=”CFMXBible”>
<cfprocparam type=”In”
cfsqltype=”CF_SQL_CHAR”
dbvarname=”@State”
value=”#Trim(FORM.State)#”
maxlength=”2”
null=”No”>
The database server receives this parameter from the CFPROCPARAM tags and supplies it to
the stored procedure.
You may also notice that, in Listing 10-12, you have a third tag involved named CFPROCRESULT.
CFPROCRESULT binds the result set returned from the stored procedure to a named ColdFusion
query object that can be CFOUTPUT just as the result of running your original CFQUERY call can
in Listing 10-9.
13546228 ch10.F 1/30/03 10:50 AM Page 220
221Chapter 10 ✦ Using Advanced Database Techniques
Listing 10-12: Stored procedure result sets are identical to CFQUERY
result sets
Company Name
Zip Code
#CompanyName#
#ZipCode#
Listing 10-12 is a very simple stored procedure that takes a single input parameter and returns
a single result set. Listing 10-13 is a stored procedure that takes two input parameters and
returns two result sets. See the section “Input parameters” for more details.
Listing 10-13: A stored procedure that returns two result sets
CREATE PROCEDURE sp_GetCompaniesEmployees (
@State Char(2),
@Name Char(1)
)
AS
BEGIN
SELECT
CompanyName,
ZipCode
FROM
Company
WHERE
State = @State
ORDER BY
ZipCode ASC
SELECT
Firstname,
Lastname
FROM
Employee
WHERE
Lastname LIKE @Name + ‘%’
ORDER BY
Lastname ASC,
Firstname ASC
END
13546228 ch10.F 1/30/03 10:50 AM Page 221
222 Part II ✦ Using ColdFusion MX with Databases
Listing 10-13 is an easy extension of Listing 10-10. You just add another input parameter and
another result set. Whenever multiple parameters are sent to a stored procedure, they are
sent in the order that they appear within the CFSTOREDPROC tag. Listing 10-14 shows how you
call Listing 10-13 from ColdFusion.
Listing 10-14: Calling the stored procedure from Listing 10-13
<cfstoredproc procedure=”sp_GetCompaniesEmployees”
datasource=”CFMXBible”>
<cfprocparam type=”In”
cfsqltype=”CF_SQL_CHAR”
dbvarname=”@State”
value=”#Trim(FORM.State)#”
maxlength=”2”
null=”No”>
<cfprocparam type=”In”
cfsqltype=”CF_SQL_CHAR”
dbvarname=”@Name”
value=”#Trim(FORM.Name)#”
maxlength=”1”
null=”No”>
Again, Listing 10-14 is an easy extension of what you’ve already done in Listing 10-11: You just
supply another input parameter and return a second result set that you can use as you can
any other ColdFusion query object, as shown in Listing 10-15.
Listing 10-15: Using multiple result sets from a stored procedure.
Companies
Company Name
Zip Code
#CompanyName#
#ZipCode#
13546228 ch10.F 1/30/03 10:50 AM Page 222
223Chapter 10 ✦ Using Advanced Database Techniques
Employees
Firstname
Lastname
#Firstname#
#Lastname#
That’s the big picture of stored procedures. The following sections delve into the details.
The three components of calling a stored procedure
As you saw earlier in the section “What is a stored procedure?” the following three tags are
associated with calling a stored procedure:
1. CFSTOREDPROC, which specifies the stored procedure being called.
2. CFPROCPARAM, which enables the flow of parameters between ColdFusion and the
stored procedure.
3. CFPROCRESULT, which enables ColdFusion to use result sets returned from the stored
procedure.
We cover each of these in detail by using stored procedures written for SQL Server 2000,
because it is a good platform for learning stored procedures. After you’re familiar with how
stored procedures are implemented in SQL Server 2000, we follow up by showing how
sp_GetCompaniesEmployees is adapted to run on the Oracle 9i platform so that you can
see the critical differences between them — and you see plenty.
CFSTOREDPROC
To make ColdFusion call a stored procedure and use any result sets it returns, you must tell
the CFSTOREDPROC tag the following four things:
✦ What database contains the stored procedure.
✦ The name of the stored procedure to be executed.
✦ How big the database server should make the blocks of data that it returns to
ColdFusion Server.
✦ Whether you want ColdFusion to have access to the stored procedure’s Return Code.
Now take a look at the following typical example of database connection parameters in the
CFSTOREDPROC tag:
<cfstoredproc procedure=”sp_SomeStoredProcedure”
datasource=”CFMXBible”
blockfactor=”10”
returncode=”Yes”>
After you are pointing to the right datasource, you specify the name of the stored procedure
to execute through the procedure parameter, as follows:
13546228 ch10.F 1/30/03 10:50 AM Page 223
224 Part II ✦ Using ColdFusion MX with Databases
<cfstoredproc procedure=”sp_SomeStoredProcedure”
datasource=”CFMXBible”
blockfactor=”10”
returncode=”Yes”>
You may need to prefix the stored procedure object with the owner’s name, followed
by a period.
BLOCKFACTOR controls how many rows at a time are returned from the database server to
ColdFusion server. The range of values is from 1 to 100. Most developers never touch the
BLOCKFACTOR parameter, because its default value of 1 is perfectly serviceable in most cases.
If you increase the value of BLOCKFACTOR, you increase the size of each block of data
transmitted between your database server and ColdFusion server and similarly decrease
the number of fetches required to service the stored procedure request. This sounds
pretty enticing, because fewer fetches mean less time wasted in mechanical overhead, and
processing larger blocks of data is more efficient than processing smaller blocks of data, but
you face a tradeoff if you do so.
First, depending on the size of the individual rows returned from your stored procedure,
you may run into memory issues with a large BLOCKFACTOR value. Your application may also suf-
fer under heavy multi-user load with a large BLOCKFACTOR value. The optimum BLOCKFACTOR
value is the largest setting that your application can consistently handle, considering memory
constraints and multi-user load under production conditions. The following, for instance, code
shows how to specify 10 rows at a time to be fetched from the database:
<cfstoredproc procedure=”sp_SomeStoredProcedure”
datasource=”CFMXBible”
blockfactor=”10”
returncode=”Yes”>
If you have a good load-testing tool, such as Empirix e-TEST Suite, start your stored
procedure with a BLOCKFACTOR of 1, perform load tests on the section of your application
that makes use of the stored procedure, and then increase BLOCKFACTOR in increments of
10 between each load test until your Performance Per User graph begins to plateau.
Finally, you can control whether your stored procedure passes its Return Code back to
ColdFusion server by using the following code:
<cfstoredproc procedure=”sp_SomeStoredProcedure”
datasource=”CFMXBible”
blockfactor=”10”
returncode=”Yes”>
The Return Code is programmatically set within the stored procedure so that it can be used to
communicate any number of things, such as the status of the stored procedure’s execution,
whether one option is taken as opposed to another, and so on.
Although it is called a Return Code — which is exactly what it is — it is referred to by
ColdFusion server as a Status Code — specifically, CFSTOREDPROC.StatusCode. See the
section “Using the Return Code,” later in this chapter, to see how you can increase the
flexibility of your ColdFusion apps.
CFPROCPARAM
After you have your call from ColdFusion to your stored procedure established, you need to
establish the interface between your ColdFusion variables and the arguments used by your
stored procedure.
Note
13546228 ch10.F 1/30/03 10:50 AM Page 224
225Chapter 10 ✦ Using Advanced Database Techniques
Understand first, however, that not all stored procedures take arguments. Listing 10-16, for
example, creates a stored procedure that simply lists all companies in Georgia.
Listing 10-16: A simple stored procedure
CREATE PROCEDURE sp_GetGeorgiaCompanies
AS
SELECT
CompanyName,
ZipCode
FROM
Company
WHERE
State = ‘GA’
ORDER BY
ZipCode ASC
RETURN
Now all that you need to do is to call the stored procedure, as shown in Listing 10-17.
Listing 10-17: Call the simple stored procedure
<cfstoredproc procedure=”sp_GetGeorgiaCompanies”
datasource=”CFMXBible”>
Input parameters
Listing 10-16 doesn’t require any values to be passed to it, but most stored procedures
require one or more input parameters to be supplied by ColdFusion. An input parameter is a
value that is passed to a function or procedure. If you adapt Listing 10-16 to use an input
parameter in place of the hard coded GA value, for example, you end up with Listing 10-18
(which is identical to Listing 10-10).
Listing 10-18: Adapting sp_GetGeorgiaCompanies to accept
an argument
CREATE PROCEDURE sp_GetCompanies (
@State CHAR(2)
)
AS
SELECT
Continued
13546228 ch10.F 1/30/03 10:50 AM Page 225
226 Part II ✦ Using ColdFusion MX with Databases
Listing 10-18 (continued)
CompanyName,
ZipCode
FROM
Company
WHERE
State = @State
ORDER BY
ZipCode ASC
RETURN
The code in Listing 10-19 used to call the new stored procedure in Listing 10-18 is almost
identical to the stored procedure call in Listing 10-17, except for adding a single CFPROCPARAM
tag to supply the state abbreviation to the stored procedure. You should recognize this call as
the same call in Listing 10-11.
Listing 10-19: Calling sp_GetCompanies from ColdFusion
<cfstoredproc procedure=”sp_GetCompanies”
datasource=”CFMXBible”>
<cfprocparam type=”In”
cfsqltype=”CF_SQL_CHAR”
dbvarname=”@State”
value=”#Trim(FORM.State)#”
maxlength=”2”
null=”No”>
Here’s a basic breakdown of what you’re seeing. ColdFusion takes the value from the input
form that posted to this template and supplies that value to the stored procedure through
CFPROCPARAM’s VALUE attribute. The value is formally defined as being of the database’s CHAR
data type by specifying CFSQLTYPE=”CF_SQL_CHAR”, and its maximum length is limited to
2 via the MAXLENGTH attribute.
If the parameter had been a NUMERIC data type, SCALE would be used to specify the number
of decimal places to which the number is accurate. MAXLENGTH is used to specify the overall
length of string data types, but in some cases, MAXLENGTH can also be used to limit the size of
a numeric parameter being fed to a stored procedure and, thereby, prevent out-of-range
errors from being thrown, as shown in Figure 10-2.
Because you are not supplying this stored procedure parameter as a NULL value, you specify
NULL=”No”. If you had wanted to supply a NULL value in this stored procedure parameter, you
would have specified NULL=”Yes”, and the VALUE attribute of the CFPROCPARAM tag would
have been ignored.
13546228 ch10.F 1/30/03 10:50 AM Page 226
227Chapter 10 ✦ Using Advanced Database Techniques
The TYPE attribute specifies whether the parameter is being supplied to the stored procedure,
received from the stored procedure, or both. In this case, you are just supplying the parameter
to the stored procedure, so the value of the TYPE attribute is IN— it is being sent from
ColdFusion into the stored procedure.
Figure 10-2: Visualizing the communication between ColdFusion input parameters and
their corresponding stored procedure parameters.
And that’s basically how you pass parameters from ColdFusion to a stored procedure. Just
think of it as a more formal way to supply ColdFusion values to a query, where the strings
supplied by ColdFusion are formally bound to specific data types native to your database
server and are given specific size limits within which to fit.
After you know how to pass individual ColdFusion values to a stored procedure by using
input parameters, you can move on to passing individual values from a stored procedure
back to ColdFusion server by using output parameters.
Output parameters
You’re probably used to receiving values back from stored procedures as multirow result
sets, but how should you handle receiving values that are not associated with table rows?
For that situation, you use output parameters that, unlike input parameters that send values
from ColdFusion to stored procedures, receive values from stored procedures and bind them
to ColdFusion variables so that they can be used by ColdFusion applications.
Listing 10-20 shows a stored procedure that calculates the average salary of all employees
and returns the result as a single output parameter:
Listing 10-20: A stored procedure that returns a single value in
an output parameter
CREATE PROCEDURE sp_GetAvgSalary (
@AverageSalary Numeric(12,2) OUTPUT
)
AS
Continued
<cfstoredproc procedure="sp_GetCompanies"
datasource="CFMXBible">
<cfprocparam type="In"
cfsqltype="CF_SQL_CHAR"
dbvarname="@State"
value="#Trim(FORM.State)#"
maxlength="2"
null="No">
<cfprocresult name="GetCompanies"
resultset="1">
CREATE PROCEDURE sp_GetCompanies(
@State CHAR(2)
)
AS
SELECT
CompanyName,
ZipCode
FROM
Company
WHERE
State = @State
ORDER BY
ZipCode ASC
RETURN
13546228 ch10.F 1/30/03 10:50 AM Page 227
228 Part II ✦ Using ColdFusion MX with Databases
Listing 10-20 (continued)
SELECT
@AverageSalary = Avg(Salary)
FROM
Employee
RETURN
The OUTPUT qualifier next to a parameter is what instructs the stored procedure to expose
that parameter to the calling program (in this case, your ColdFusion application) after it has
finished executing.
Listing 10-21 shows the ColdFusion code that executes the stored procedure in Listing 10-20
and displays the output parameter.
Listing 10-21: Calling the stored procedure from Listing 10-20 and
displaying the output parameter that it returns
<cfstoredproc procedure=”sp_GetAvgSalary”
datasource=”CFMXBible”>
<cfprocparam type=”Out”
cfsqltype=”CF_SQL_NUMERIC”
variable=”AverageSalary”
dbvarname=”@AverageSalary”
scale=”2”
null=”No”>
#AverageSalary#
The output parameter is exposed in ColdFusion as a simple local variable that is not associated
with the stored procedure that created it. It is as if you had created the local variable by using a
simple CFSET call.
You are not restricted to only one output parameter; you can have as many output parameters
as you want. Figure 10-3 shows what the communications between ColdFusion and the stored
procedure look like.
InOut parameters
An InOut parameter can be sent to a stored procedure containing one value and then returned to
the calling program containing a different value. Say, for example, that you have a stored proce-
dure that receives a gift certificate coupon code and an amount due (for a sales order, inventory
item, or any other monetary amount), and if the gift certificate if still valid, the Amount Due
value is decreased by the amount of the certificate and is returned in the same parameter to the
calling ColdFusion template. If the coupon doesn’t exist (@@ROWCOUNT != 1), then the Amount
Due remains unchanged. Listing 10-22 contains a perfect application of an InOut parameter.
13546228 ch10.F 1/30/03 10:50 AM Page 228
229Chapter 10 ✦ Using Advanced Database Techniques
Listing 10-22: Calling a stored procedure that contains an
InOut parameter
CREATE PROCEDURE sp_GetAmountDue (
@CouponCode Varchar(16),
@AmountDue Numeric(12,2) OUTPUT
)
AS
DECLARE
@Redeemed Bit,
@PercentDiscount Numeric(12,2)
SELECT
@Redeemed = Redeemed,
@PercentDiscount = PercentDiscount
FROM
Coupon
WHERE
CouponCode = @CouponCode
IF (@@ROWCOUNT != 1)
BEGIN
RETURN -- Coupon doesn’t exist; no price change
END
IF (@Redeemed = 1)
BEGIN
RETURN -- Coupon already redeemed; no price change
END
ELSE
BEGIN
SELECT @AmountDue = @AmountDue * (1 - @PercentDiscount / 100)
END
RETURN
Figure 10-3: Visualizing the communication between ColdFusion output parameters and
their corresponding stored procedure parameters.
<cfstoredproc procedure="sp_GetAvgSalary"
datasource="CFMXBible">
<cfprocparam type="Out"
cfsqltype="CF_SQL_NUMERIC"
variable="AverageSalary"
dbvarname="@AverageSalary"
scale="2"
null="No">
#AverageSalary#
CREATE PROCEDURE sp_GetAvgSalary(
@AverageSalary Numeric(12,2) OUTPUT
)
AS
SELECT
@AverageSalary = Avg(Salary)
FROM
Employee
RETURN
13546228 ch10.F 1/30/03 10:50 AM Page 229
230 Part II ✦ Using ColdFusion MX with Databases
Listing 10-23 shows the ColdFusion code that executes the stored procedure and then
displays the InOut parameter.
Listing 10-23: Calling the stored procedure from Listing 10-22
and displaying the InOut parameter
<cfstoredproc procedure=”sp_GetAmountDue”
datasource=”CFMXBible”>
<cfprocparam type=”In”
cfsqltype=”CF_SQL_VARCHAR”
dbvarname=”@CouponCode”
value=”#Trim(FORM.CouponCode)#”
maxlength=”16”
null=”No”>
<cfprocparam type=”InOut”
cfsqltype=”CF_SQL_NUMERIC”
dbvarname=”@AmountDue”
value=”#Val(FORM.AmountDue)#”
variable=”AmountDue”
scale=”2”
null=”No”>
Figure 10-4 shows a conceptual diagram of how InOut parameters communicate with stored
procedures.
Although both SQL Server and Oracle support InOut type CFPROCPARAMs, only Oracle
supports IN OUT qualifiers in the stored procedure definition itself. To use InOut parameters
with SQL Server stored procedures, specify InOut as the CFPROCPARAM type and use the
OUTPUT qualifier next to the names of the parameters in the stored procedure itself. For
Oracle stored procedures, specify InOut as the CFPROCPARAM type and use the IN OUT
qualifier next to the names of the parameters in the stored procedure itself.
Passing parameters by position rather than by name
Various documentation has mentioned that parameters may be passed to stored procedures
in any order and that the binding that you specify by using DBVARNAME correctly maps Value
attributes to their corresponding database variables, but this is not the case in practice.
You should always pass parameters to stored procedures in exactly the same order as they
appear in the stored procedure’s interface; otherwise, ColdFusion throws an exception.
CFPROCRESULT
Many, but not all, database servers can return result sets from stored procedures. For those that
can, CFPROCRESULT binds those result sets to ColdFusion queries, which makes them available
for use in your ColdFusion applications just as if they were returned from a CFQUERY call. And
although some database servers can return multiple result sets from a single CFSTOREDPROC call,
the driver with which ColdFusion accesses the database must support such a capability as well.
For Oracle, you need a Type 4 JDBC Driver, such as the one that ships standard with ColdFusion
MX Enterprise, or the free OIC driver from Oracle if you’re running ColdFusion MX Professional.
Note
13546228 ch10.F 1/30/03 10:50 AM Page 230
231Chapter 10 ✦ Using Advanced Database Techniques
Figure 10-4: Visualizing the communication between ColdFusion InOut parameters and
their corresponding stored procedure parameters.
Look back for a moment at Listing 10-17. You expected back only one result set from this
stored procedure, and you bound that result set to a ColdFusion query object named
GetGeorgiaCompanies by using the following CFPROCRESULT tag:
This tag is instructing ColdFusion Server to take the first result set returned from the stored
procedure and bind it to a ColdFusion query object named GetGeorgiaCompanies. After this
tag executes, your ColdFusion application can use that GetGeorgiaCompanies query object
just as if it came from a CFQUERY call.
CFPROCRESULT binds result sets to ColdFusion query objects in the order in which they are
created within the stored procedure. Mapping result sets in the stored procedure to query
objects in ColdFusion may seem an obvious and trivial task — just number them in the order
they appear, top to bottom — but this task can become confusing unless you follow the rules,
as follows.
< cfstoredproc procedure= "sp_GetAmountDue"
datasource="CFMXBibIe">
<cfprocparam type="In"
cfsqItype="CF_SQL_VARCHAR"
"dbvarname= "@ CouponCode"
vaIue= "#Trim( FORM.CouponCode)#"
maxIength="16"
nuII="No">
<cfprocparam type="InOut"
cfsqltype="CF_SQL_NUMERIC
"dbvarname= "@Amount Due"
value="#VaI(FORM.Amount Due)#"
variable="AmountDue"
scaIe="2"
nuIl="No">
CREATE PROCEDURE sp_GetAmountDue (
@ CouponCode Varchar(16),
@AmountDue Numeric(12,2) OUTPUT
)
AS
DECLARE
@Redeemed Bit,
@PercentDiscount Numeric(12,2)
SELECT
@Redeemed = Redeemed,
@ Percent Discount = PercentDiscount
FROM
Coupon
WHERE
CouponCode = @CouponCode
IF (@@ROWCOUNT != 1)
BEGIN
RETURN
END
IF (@Redeemed = 1)
BEGIN
RETURN
END
ELSE
BEGIN
SELECT @AmountDue =
@AmountDue * (1 -
@PercentDiscount/ 100)
END
RETURN
↑
13546228 ch10.F 1/30/03 10:50 AM Page 231
232 Part II ✦ Using ColdFusion MX with Databases
In Transact/SQL, the SQL language used to write SQL Server stored procedures, variables
are modified by using the same SELECT statement used to produce result sets. Modifying a
variable does not count as a result set, so in reading through your stored procedure and
enumerating the result sets, pass up any SELECT statements that simply create variables or
change their values. By the same token, don’t confuse the SELECT of a single column from a
single row as anything other than a full-fledged result set — just as if it was 20 columns wide
and a thousand rows deep.
Figure 10-5 shows an example of correctly enumerated result sets among variable operations.
Remember: You never “skip over” anything in a stored procedure in enumerating its result
sets; you should never leave any gaps in the enumeration sequence.
Figure 10-5: If a SELECT statement produces output, it is an enumerated
result set, regardless of its dimensions.
Oracle stored procedures and packages
In this section, you adapt Listing 10-13 to produce exactly the same results in Oracle. As you
soon see, the differences are significant.
This is Resultset 1 because it is
the first SELECT statement
that produces output
This is not a ResultSet
because it does not produce output
This is Resultset 2 because it is
the second SELECT statement
that produces output
This is not a Resultset
because it does not produce output
This is Resultset 3 because it is
the third SELECT statement
that produces output
SELECT
CompanyName,
ZipCode
FROM
Company
WHERE
State = @State
ORDER BY
ZipCode ASC
SELECT @myVar = 125000
SELECT @myVar AS aliasForColdFusionUse
SELECT @yourVar = @myVar * 1.5
SELECT
Firstname,
L
Các file đính kèm theo tài liệu này:
- Using ColdFusion MX with Databases.pdf