In ACS Java, rather than writing directly to the JDBC API for database access, you have the option of using the ACS Java database API, a high-level language for performing common interactions with the database. Implicit connection management, rapid development, and encapsulation of best practices for JDBC are all goals of the ACS Java database API.
This document enumerates the features of the API and how its macro language interacts with the Java code you will write. Please refer to the ACS Java Database API Guide for introductory design and usage information.
Definitions
db_string
db_list
db_1row
db_multirow
db_foreach
db_dml
db_exec_plsql
db_call_plsql
db_sql
db_nextval
db_transaction
db_abort_transaction
db_xml
where user_id = :user_id, the
PreparedStatement.setObject method is used to bind
the value of user_id before executing the SQL statement.
java.sql.SQLException. This exception may
originate from the JDBC driver, or from the runtime support code
for the macro commands. In the latter case, we use a subclass of
SQLException, com.arsdigita.db.DbException.
| Signature: | db_string statement_name sql_statement |
| Optional switches: | -default <default_string> |
Use this macro to retrieve a single value from the database into a
java String object. If the sql_statement does not return
any information, the default_string will be returned if
it is present; otherwise, a DbException will be thrown.
db_string today {
select sysdate from dual
}
System.out.println("Oracle says the date is: " + today);
In the preceding example, a String variable named
today is declared and initialized with the result of the
query. The macro can also be used in expression context, if you need
control over the variable declaration; for example:
String group_name;
group_name = [db_string group_name {
select group_name from groups
where group_id = :group_id
} -default "Unknown group id!"];
| Signature: | db_list statement_name sql_statement |
If you're querying a single column from a database table, and more
than one row might be returned, use this macro to create an object of
type List in which each element contains the value from
a row in the query results.
db_list all_users {
select user_id from users
}
This macro command will automatically declare a List
variable named all_users, and fill it with the values
from the users table.
| Signature: | db_1row statement_name sql_statement if_no_rows code_block |
| Optional switches: | -nodeclare |
Use this macro to perform a select statement that retrieves values
from a single row of a database table. Each of the columns in the
query will be declared as a local variable, and given the value that
results from the query. The variables created will be of type
Object, unless their type is declared within the SQL
statement. This can be done by appending a colon and a Java class
name to the column name. For example:
db_1row user_info {
select user_id, priv_email from users
where user_id = :user_id
}
This will perform the stated query and create two variables of type
Object, one named user_id and one named
priv_email. If you need to declare the variable as
another type, you can give the macro a hint as follows:
db_1row user_info {
select user_id:BigDecimal, priv_email from users
where user_id = :user_id
}
If the variables are already declared, and you just want to set their
values, use the -nodeclare switch. Finally, if you don't
want to set the variables at all, use the -column_array
switch to name a variable of type Map that will be
created, containing the mapping betwen column names and values.
If the query returns no data, a DbException will be
thrown, unless the if_no_rows clause is used. In this
case, the final parameter is a code block that will be executed in
this case. Here's an example from portrait/index.ajp:
db_1row user_info {
select first_names, last_name
from persons
where person_id=:user_id
} if_no_rows {
throw new WarningException("Account Unavailable",
"We can't find you (user #" + user_id + ") in the users table. " +
"Probably your account was deleted for some reason.");
}
Following this macro, you can be sure that the
first_names and last_name variables contain
the correct values for the given user.
Another side effect of db_1row is that the
ResultSet containing the query results is created as a
local variable. The name used for the ResultSet object
is the macro's statement name (its first parameter). So, in the above
example, a variable of type ResultSet is created and
named user_info. Its next method is automatically
called, so that the values from its first row can be examined. This
object is what would be passed to a template page if the
ad_page_contract macro specified a template property of
user_info:onerow. (see ad_page_contract
documentation).
| Signature: | db_multirow statement_name sql_statement |
Use this macro to perform a query and obtain a ResultSet
object. The specified statement_name is used as the
name of the ResultSet. Alternatively, by using the macro in
expression context, you can declare the variable yourself. Here's an
example from the news package:
db_multirow item {
select news_item_id, title
from news_items_obj
where context_id = :package_id
and sysdate >= release_date
and (expiration_date is null or expiration_date > sysdate)
}
This macro will automatically declare a ResultSet
variable named item, which is then passed to the template
for the news index page.
| Signature: | db_foreach statement_name sql_statement code_block if_no_rows code_block |
| Optional switches: | -column_array <array_name> |
This macro is similar to db_multirow in that it allows a query to be
performed, but it should be used when you want to do something
immediately with the query results. The third parameter is a block of
java code (which must be surrounded by curly braces) that will be
executed once for each row returned in the query results. Within that
code block, the column variables are automatically declared in a
manner identical to that of db_1row. Also like
db_1row, if you use the optional
-column_array switch, you'll have access instead to an
object of type Map with the given name, containing all
of the values for the current row. Here's an example from the
workflow package, which generates an HTML option list from values in
the database:
StringBuffer party_widget = new StringBuffer("<select name=\"party_id\">\n");
db_foreach unassigned_parties {
select p.party_id,
acs_object.name(p.party_id) as name,
p.email
from parties p
where not exists (select 1 from wf_task_assignments ta where ta.task_id = :task_id and ta.party_id = p.party_id)
} {
party_widget.append("<option value=\"");
party_widget.append(party_id);
party_widget.append("\">");
party_widget.append(name);
if (!Utilities.emptyString(email)) {
party_widget.append("(<a href=\"mailto:");
party_widget.append(email);
party_widget.append("\">");
party_widget.append(email);
party_widget.append("</a>)");
}
party_widget.append("</option>\n");
}
party_widget.append("</select>");
Here's another example of using db_foreach to build up a
data structure (a List of Map objects containing query results:
ArrayList panelList = new ArrayList();
db_foreach getPanelRows {
select tp.header,
tp.template_url,
'#dddddd' as bgcolor
from wf_context_task_panels tp,
wf_cases c,
wf_tasks t
where t.task_id = :task_id
and c.case_id = t.case_id
and tp.context_key = c.context_key
and tp.workflow_key = c.workflow_key
and tp.transition_key = t.transition_key
order by sort_key
} -column_array row {
panelList.add(row);
} if_no_rows {
panelList.add(defaultInfoPanel);
}
In this example, the code block which adds the object named
row is executed one time for each row in the query
results. If the query returns no rows, the final code block is
executed.
| Signature: | db_dml statement_name sql_statement
|
| Optional switches: | -blob_file <file_name>
|
Use this macro when you need to perform any SQL statement that is not a query. Inserts, updates, creates, and deletes are all possibilities. Here's an example:
db_dml update_parties {
update parties
set email = :email,
url = :url
where party_id = :user_id
}
The -blob_file switch is a special-purpose feature for
taking the contents of a file and inserting it into a database column
of type BLOB (Binary Large Object). The given file_name
must refer to an object of type java.io.File that has
been initialized with the correct file name. In addition, the SQL
statement used must provide access to the table's BLOB column using
special PL/SQL syntax to declare an output variable. Because of the
use of PL/SQL, we must surround the SQL statement with
begin and end to indicate the presence of a
PL/SQL block. Make sure that within the PL/SQL block, you end your
SQL statements with a semicolon. If you're lost by all this, the
following example (from the ACS user portrait upload code) should make
it clear:
db_dml update_photo {
begin
update cr_revisions
set content = empty_blob()
where revision_id = :revision_id
returning content into :1;
end;
} -blob_file temp_file_info
The -blob_string switch is also used for taking the
contents of a java.lang.String object and using that
instead of a file to insert/update a BLOB in the database. Its
usage is identical to -blob_file, except the referenced
object must be a java.lang.String.
| Signature: | db_exec_plsql statement_name sql_statement
|
Use this macro when you need to have Oracle execute an arbitrary block
of PL/SQL code, or call a PL/SQL function that returns a value. As
explained above for -blob_file, your PL/SQL code must
always be surrounded with begin and end, and
PL/SQL statements must be finished off with semicolons. Return values
are indicated with the special bind variable ":1", which
should be set to the function's return value as in the following example.
Object item_id =
[db_exec_plsql create_item {
begin
:1 := content_item.new(
name => :name,
creation_ip => :creation_ip
);
end;
}];
In this example, the content_item.new function is called,
passing it the name and creation_ip
values that come from the current java environment, and its return
value is assigned to the item_id variable.
Here's a simpler example that illustrates how to call a function if you don't need its return value:
db_exec_plsql delete_group {
begin
acs_group.delete(:group_id);
end;
}
| Signature: | db_call_plsql statement_name function_call
|
Use this macro to call stored PL/SQL functions in Oracle, which return an output value. Typical usage is
String value = [db_call_plsql get_date { function(param_1, param_2, ... ) }];
Note that db_exec_plsqlcan also be used to call stored functions; The above example is the equivalent to
String value = [db_exec_plsql get_date {
begin
? := function(param_1, param_2, ... );
end;
}];
Often, though, the db_call_plsqlform will be more convenient, because you don't need to supply the wrapping
BEGIN ... END;code.
| Signature: | db_sql statement_name sql_fragment
|
In addition to automatic interpolation of bind variables, any SQL
statement passed to the Database API may contain interpolated SQL
fragments. These are variables prefixed with $, and refer to
variables of type com.arsdigita.db.SqlFragment, which can be easily
created by using the db_sql macro. With this mechanism,
it's easy to programmatically build up a SQL statement from smaller
parts. Here's a small, contrived example: for real world examples
showing the full power of this technique, see the acs-admin user
search page, or the acs-workflow task-list page.
SqlFragment where_clause = (user_id != null ?
[db_sql restrict_user { user_id = :user_id }] :
[db_sql no_restriction { 1 = 1 }]
);
db_multirow one_or_all_users {
select user_id from users
where $where_clause
}
| Signature: | db_nextval sequence_name
|
This macro is a simple shortcut for a common task: obtaining the next value from an Oracle sequence object. Its only parameter is the name of the sequence object, and it is translated into a function call that returns the next sequence value. This means you should only use this macro in expression context, as follows:
BigDecimal user_id = [db_nextval acs_object_id_seq];
| Signature: | db_transaction code_block
|
The JDBC Connection objects that are created and managed
by the ACS Java Database API are, by default, in "auto-commit" mode.
Use the db_transaction macro to temporarily turn off
"auto-commit" mode so that a series of SQL statements may be issued
transactionally. If any errors are thrown within the
db_transaction code block, the connection's
rollback method will be used to return the database to
the state it was in when the transaction was begun. If, on the other
hand, all the operations in the transaction succeed, the
commit method will be used to make the alterations
permanent. Here's an example from the workflow package, where a task
object can only be deleted after the references to it have been deleted
from all of its child tables.
db_transaction {
db_dml arcs_delete {
delete from wf_arcs
where workflow_key = :workflow_key
and transition_key = :transition_key
}
... (other db_dml deletions) ...
db_dml transition_delete {
delete from wf_transitions
where workflow_key = :workflow_key
and transition_key = :transition_key
}
}
| Signature: | db_abort_transaction
|
Use this macro, which takes no parameters, within a
db_transaction code block if a condition arises that
makes you want to abort the transaction. If the macro is executed, an
exception is thrown just as if a Database API statement had generated
an error, and the transaction will be automatically rolled back.
| Signature: | db_xml statement_name sql_statement code_block if_no_rows code_block |
| Optional switches: | -doc_name <doc_name> |
| Document Revision # | Action Taken, Notes | When? | By Whom? |
|---|---|---|---|
| 0.1 | Creation | Luke Pond |