2012年3月28日星期三

ADF_117:VO高级使用技巧之二:编程实现基于Ref Cursor的VO

开发运行环境:JDevloper 11.1.2.4.0+ Oracle Database 11gR2 XE

本文最后一次修改日期:2013-07-01

在Fusion Order Demo的StandaloneExamples中的AdvancedViewObjectsExamples中的ViewObjectOnRefCursor,介绍了如何使用ResultSet查询结果集实现View Object。
其中,Ref Cursor是使用PL-SQL实现的。
关于Fusion Order Demo,请参考《发布与运行 Oracle Fusion Order Demo》。
关于VO中的重要方法的介绍,请参考《VO高级使用技巧之一:可重写的重要的方法》。

1. PL-SQL代码

REM  For security reasons, the FOD user is not given create procedure privileges
REM  To run this script, issue the following statement as system:
REM  grant create procedure to fod;

REM  To back out this change, issue the following as FOD:
REM  drop package RefCursorExample;
REM  And run the following as system:
REM  revoke create procedure from fod;

CREATE OR REPLACE PACKAGE RefCursorExample IS
  TYPE ref_cursor IS REF CURSOR;
  FUNCTION get_orders_for_customer(p_email VARCHAR2) RETURN ref_cursor;
  FUNCTION count_orders_for_customer(p_email VARCHAR2) RETURN NUMBER;
END RefCursorExample;
.
/
show errors
CREATE OR REPLACE PACKAGE BODY RefCursorExample IS
  FUNCTION get_orders_for_customer(p_email VARCHAR2) RETURN ref_cursor IS
    the_cursor ref_cursor;
  BEGIN
    OPEN the_cursor FOR
      SELECT o.order_id, o.order_status_code, o.order_total
        FROM orders o, persons p
     WHERE o.customer_id = p.person_id
       AND p.email = p_email;
    RETURN the_cursor;
  END get_orders_for_customer;

  FUNCTION count_orders_for_customer(p_email VARCHAR2) RETURN NUMBER IS
    the_count NUMBER;
  BEGIN
    SELECT COUNT(*)
      INTO the_count
      FROM orders o, persons p
     WHERE o.customer_id = p.person_id
       AND p.email = p_email;
    RETURN the_count;
  END count_orders_for_customer;
END RefCursorExample;
.
/
show errors

2. OrdersForCustomerImpl.java代码

package devguide.advanced.refcursor;

import java.math.BigDecimal;

import java.sql.CallableStatement;
import java.sql.ResultSet;

import java.sql.SQLException;

import java.sql.Types;
import java.sql.Timestamp;

import oracle.jbo.JboException;
import oracle.jbo.domain.Date;
import oracle.jbo.domain.Number;
import oracle.jbo.server.SQLBuilder;
import oracle.jbo.server.ViewObjectImpl;
import oracle.jbo.server.ViewRowImpl;
import oracle.jbo.server.ViewRowSetImpl;

import oracle.jdbc.OracleTypes;
// ---------------------------------------------------------------------
// ---    File generated by Oracle ADF Business Components Design Time.
// ---    Custom code may be added to this class.
// ---    Warning: Do not modify method signatures of generated methods.
// ---------------------------------------------------------------------
public class OrdersForCustomerImpl extends ViewObjectImpl {
    /**This is the default constructor (do not remove).
     */
    public OrdersForCustomerImpl() {
    }

    /**
     * Overridden framework method.
     *
     * Executed when the framework needs to issue the database query for
     * the query collection based on this view object. One view object
     * can produce many related result sets, each potentially the result
     * of different bind variable values. If the rowset in query is involved
     * in a framework-coordinated master/detail viewlink, then the params array
     * will contain one or more framework-supplied bind parameters. If there
     * are any user-supplied bind parameter values, they will *PRECEED* the
     * framework-supplied bind variable values in the params array, and the
     * number of user parameters will be indicated by the value of the
     * numUserParams argument.
     */
    protected void executeQueryForCollection(Object qc, Object[] params, int numUserParams) {
        storeNewResultSet(qc, retrieveRefCursor(qc, params));
        super.executeQueryForCollection(qc, params, numUserParams);
    }

    /**
     * Overridden framework method.
     *
     * Wipe out all traces of a built-in query for this VO
     */
    protected void create() {
        getViewDef().setQuery(null);
        getViewDef().setSelectClause(null);
        setQuery(null);
    }

    /**
     * Overridden framework method.
     *
     * The role of this method is to "fetch", populate, and return a single row
     * from the datasource by calling createNewRowForCollection() and populating
     * its attributes using populateAttributeForRow().
     */
    protected ViewRowImpl createRowFromResultSet(Object qc, ResultSet rs) {
        /*
       * We ignore the JDBC ResultSet passed by the framework (null anyway) and
       * use the resultset that we've stored in the query-collection-private
       * user data storage
       */
        rs = getResultSet(qc);

        /*
       * Create a new row to populate
       */
        ViewRowImpl r = createNewRowForCollection(qc);

        try {
            /*
         * Populate new row by attribute slot number for current row in Result Set
         */
            populateAttributeForRow(r, 0, rs.getLong(1));
            populateAttributeForRow(r, 1, rs.getString(2));
            populateAttributeForRow(r, 2, rs.getString(3));
        } catch (SQLException s) {
            throw new JboException(s);
        }
        return r;
    }

    /**
     * Overridden framework method.
     *
     * Return true if the datasource has at least one more record to fetch.
     */
    protected boolean hasNextForCollection(Object qc) {
        ResultSet rs = getResultSet(qc);
        boolean nextOne = false;
        try {
            nextOne = rs.next();
            /*
         * When were at the end of the result set, mark the query collection
         * as "FetchComplete".
         */
            if (!nextOne) {
                setFetchCompleteForCollection(qc, true);
                /*
           * Close the result set, we're done with it
           */
                rs.close();
            }
        } catch (SQLException s) {
            throw new JboException(s);
        }
        return nextOne;
    }

    /**
     * Overridden framework method.
     *
     * The framework gives us a chance to clean up any resources related
     * to the datasource when a query collection is done being used.
     */
    protected void releaseUserDataForCollection(Object qc, Object rs) {
        /*
       * Ignore the ResultSet passed in since we've created our own.
       * Fetch the ResultSet from the User-Data context instead
       */
        ResultSet userDataRS = getResultSet(qc);
        if (userDataRS != null) {
            try {
                userDataRS.close();
            } catch (SQLException s) {
                /* Ignore */
            }
        }
        super.releaseUserDataForCollection(qc, rs);
    }

    /**
     * Overridden framework method
     *
     * Return the number of rows that would be returned by executing
     * the query implied by the datasource. This gives the developer a
     * chance to perform a fast count of the rows that would be retrieved
     * if all rows were fetched from the database. In the default implementation
     * the framework will perform a SELECT COUNT(*) FROM (...) wrapper query
     * to let the database return the count. This count might only be an estimate
     * depending on how resource-intensive it would be to actually count the rows.
     */
    public long getQueryHitCount(ViewRowSetImpl viewRowSet) {
        Long result =
            (Long)callStoredFunction(NUMBER, "RefCursorExample.count_orders_for_customer(?)", viewRowSet.getParameters(true));
        return result.longValue();
    }
    // ------------- PRIVATE METHODS ----------------


    /**
     * Return a JDBC ResultSet representing the REF CURSOR return
     * value from our stored package function.
     */
    private ResultSet retrieveRefCursor(Object qc, Object[] params) {
        ResultSet rs =
            (ResultSet)callStoredFunction(OracleTypes.CURSOR, "RefCursorExample.get_orders_for_customer(?)", new Object[] { getNamedBindParamValue("CustEmail",
                                                                                                                                                   params) });
        return rs;
    }

    private Object getNamedBindParamValue(String varName, Object[] params) {
        Object result = null;
        if (getBindingStyle() == SQLBuilder.BINDING_STYLE_ORACLE_NAME) {
            if (params != null) {
                for (Object param : params) {
                    Object[] nameValue = (Object[])param;
                    String name = (String)nameValue[0];
                    if (name.equals(varName)) {
                        return (String)nameValue[1];
                    }
                }
            }
        }
        throw new JboException("No bind variable named '" + varName + "'");
    }

    /**
     * Store a new result set in the query-collection-private user-data context
     */
    private void storeNewResultSet(Object qc, ResultSet rs) {
        ResultSet existingRs = getResultSet(qc);
        // If this query collection is getting reused, close out any previous rowset
        if (existingRs != null) {
            try {
                existingRs.close();
            } catch (SQLException s) {
            }
        }
        setUserDataForCollection(qc, rs);
        hasNextForCollection(qc); // Prime the pump with the first row.
    }

    /**
     * Retrieve the result set wrapper from the query-collection user-data
     */
    private ResultSet getResultSet(Object qc) {
        return (ResultSet)getUserDataForCollection(qc);
    }

    /**
     * Return either null or a new oracle.jbo.domain.Date
     */
    private static Date nullOrNewDate(Timestamp t) {
        return t != null ? new Date(t) : null;
    }

    /**
     * Return either null or a new oracle.jbo.domain.Number
     */
    private static Number nullOrNewNumber(BigDecimal b) {
        try {
            return b != null ? new Number(b) : null;
        } catch (SQLException s) {
        }
        return null;
    }
    //----------------[ Begin Helper Code ]------------------------------
    public static int NUMBER = Types.NUMERIC;
    public static int DATE = Types.DATE;
    public static int VARCHAR2 = Types.VARCHAR;

    /**
     * Simplifies calling a stored function with bind variables
     *
     * You can use the NUMBER, DATE, and VARCHAR2 constants in this
     * class to indicate the function return type for these three common types,
     * otherwise use one of the JDBC types in the java.sql.Types class.
     *
     * NOTE: If you want to invoke a stored procedure without any bind variables
     * ====  then you can just use the basic getDBTransaction().executeCommand()
     *
     * @param sqlReturnType JDBC datatype constant of function return value
     * @param stmt stored function statement
     * @param bindVars Object array of parameters
     * @return function return value as an Object
     */
    protected Object callStoredFunction(int sqlReturnType, String stmt, Object[] bindVars) {
        CallableStatement st = null;
        try {
            st = getDBTransaction().createCallableStatement("begin ? := " + stmt + "; end;", 0);
            st.registerOutParameter(1, sqlReturnType);
            if (bindVars != null) {
                for (int z = 0; z < bindVars.length; z++) {
                    st.setObject(z + 2, bindVars[z]);
                }
            }
            st.executeUpdate();
            return st.getObject(1);
        } catch (SQLException e) {
            throw new JboException(e);
        }
    }

    /**getEstimatedRowCount - overridden for custom java data source support.
     */
    public long getEstimatedRowCount() {
        long value = super.getEstimatedRowCount();
        return value;
    }

    /**Gets the bind variable value for CustEmail
     */
    public String getCustEmail() {
        return (String)getNamedWhereClauseParam("CustEmail");
    }

    /**Sets <code>value</code> for bind variable Email
     */
    public void setCustEmail(String value) {
        setNamedWhereClauseParam("CustEmail", value);
    }
}

3. 运行
(1)以system用户连接数据库,然后执行:grant create procedure to fod;
(2)以fod用户连接数据库,然后执行数据库脚本:CreateRefCursorPackage.sql
(3)运行AM



说明:本例还是一个典型的使用ResultSet实现VO的例子,你只需要修改私有方法retrieveRefCursor返回你的ResultSet就可以了。

参考文献:
1. Fusion Developer's Guide for ADF 11.1.2.4 之 42.8节:Using Programmatic View Objects for Alternative Data Sources
2. http://codingwithpassion.blogspot.jp/2010/10/oracle-adf-advanced-techniques-custom.html
3. http://adfpractice-fedor.blogspot.jp/2011/01/adf-bc-programmatically-populated-vo.html
4. http://adfpractice-fedor.blogspot.jp/2010/12/adf-bc-plsql-procedure-params.html

没有评论: