この章では、JDeveloperでアプリケーション・サービスを作成して使用する方法について説明します。
この章の内容は次のとおりです。
アプリケーションのモデル部分を開発する場合、ビジネス・サービス、セッション・ファサード実装用のEJBセッションBean、およびデータ・コントロールを通じた機能公開のために、TopLinkを使用してPOJO(Plain Old Java Objects)を永続化することをお薦めします。Oracle JDeveloperには、モデル・プロジェクトを簡単かつ迅速に作成できる複数のウィザードが付属しています。
TopLink ADFの使用方法の詳細は、第19章「TopLinkに関する高度なトピック」を参照してください。
Oracle TopLinkの詳細は、『Oracle TopLink開発者ガイド』およびOracle TopLinkのJavadocを参照してください。
ヒント: ほとんどの開発チームでは、ソース・コントロール管理(SCM)のための独自の手順、ポリシー、およびSCMシステムのトランザクション(または作業ユニット)の構成要素に関する共通の見解を保持しています。ポリシーが存在しない場合は、複数の論理的な変更を1つのトランザクションにグループ化する必要があります。また、変更をチーム内の他のメンバーと共有する場合、自分の加えた変更をコミットする必要もあります。一般的に、変更を正常にコンパイルできない場合や、変更用に作成されたユニット・テストに失敗した場合は、それらの変更をコミットしないことをお薦めします。 |
セッションBeanは、ビジネス・レイヤーの機能をクライアントに公開します。
注意: TopLinkエンティティのメソッドをビジネス・サービスとして直接公開することもできますが、この方法はWebアプリケーションのベスト・プラクティスではありません。このモデルは、基本的なCRUD機能に対応しますが、ビジネス・レイヤー・オブジェクト間の対話を含む単純な操作の場合にも、複雑で扱いにくいカスタム・コードを必要とします。 |
セッションBeanは、J2EEデザイン・パターンのセッション・ファサードを実装する際に最もよく使用されます。セッション・ファサードは、データを集約し、それらのデータをモデル・レイヤーを通じてアプリケーションに提供するセッションBeanです。セッション・ファサードには、エンティティにアクセスするメソッドと、サービスをクライアントに公開するメソッドが含まれます。セッションBeanでは、コンテナを通じてトランザクション・コンテキストが保持されるため、基本的なCRUD機能が自動的にサポートされます。
セッションBeanを作成するには、セッションBean作成ウィザードを使用します。このウィザードは、「新規ギャラリ」の「Business Tier」カテゴリから起動できます。
セッションBean作成ウィザードでは、EJBバージョン、ステートフルまたはステートレス・セッション、リモートまたはローカル・インタフェース(あるいはその両方)、コンテナ管理またはBean管理のトランザクション(CMTまたはBMT)、セッション・ファサード・メソッドの実装方法の選択など、様々なオプションが提供されます。TopLinkプロジェクト用のセッションBeanを作成する場合は、EJB 3.0バージョンのセッションBeanとステートレス・セッションを選択する必要があります。また、コンテナ管理のトランザクション(CMT)も選択する必要があります(Bean管理のトランザクション(BMT)は、このマニュアルの対象範囲外です)。セッションBean作成ウィザードのその他のオプションは、後続の項で説明します。
必要なインタフェースのタイプは、クライアントに応じて変化します。クライアントが同じ仮想マシン(VM)上で稼働している場合、通常はローカル・インタフェースが最適です。クライアントが別のVM上で稼働している場合、リモート・インタフェースが必要です。ほとんどのWebアプリケーション(JSF、JSPおよびサーブレット)では、同じVMでクライアントとサービスが実行されるため、ローカル・インタフェースを選択するのが最適です。別のVMで実行されるJavaクライアント(ADF Swing)には、リモート・インタフェースが必要です。
セッション・ファサードには、トランザクションの中核となるCRUDメソッドと、エンティティにアクセスするメソッドが含まれます。セッション・ファサード・メソッドを生成するには、セッションBean作成ウィザードの「セッション・ファサード・メソッドの生成」チェック・ボックスを選択し、次のページで生成するメソッドを指定します。JDeveloperでは、プロジェクト内のすべてのエンティティが自動的に検出されるため、セッション・ファサード・メソッドを作成する対象のエンティティおよびメソッドを選択できます。
同じプロジェクトのすべてのエンティティにセッション・ファサード・メソッドを作成できます。これは、テスト目的としては役に立つ可能性がありますが、一般的に単一のセッションBeanには多すぎます。通常、セッションBeanは、特定のタスクに特化されるため、そのタスクに不要な情報は格納しません。ツリー・コントロールを使用して、生成するメソッドを明示的に選択してください。
セッションBeanクラスには、セッション全体に適用されるフィールドおよびサービス・メソッドが含まれます。セッションBeanを作成すると、Beanクラスと、ローカル・インタフェースまたはリモート・インタフェース(あるいはその両方)に対応する個別ファイルが自動的に生成されます。リモート・インタフェースはセッションBeanの名前(SRAdminFacade.javaなど)となり、BeanクラスはそれにBean.javaが、ローカル・インタフェースはそれにLocal.javaが追加された名前となります。インタフェース・ファイルは、直接変更する必要がないため、アプリケーション・ナビゲータに表示されません。インタフェース・ファイルを参照するには、システム・ナビゲータまたは構造ウィンドウを使用します。
例3-1 SRAdminFacade.javaインタフェース
package oracle.srdemo.model; import java.util.List; import javax.ejb.Local; import oracle.srdemo.model.entities.ExpertiseArea; import oracle.srdemo.model.entities.Product; import oracle.srdemo.model.entities.User; import oracle.toplink.sessions.Session; @Local public interface SRAdminFacade { Object mergeEntity(Object entity); Object persistEntity(Object entity); Object refreshEntity(Object entity); void removeEntity(Object entity); List<ExpertiseArea> findExpertiseByUserId(Integer userIdParam); ExpertiseArea createExpertiseArea(Product product, User user, Integer prodId, Integer userId, String expertiseLevel, String notes); Product createProduct(Integer prodId, String name, String image, String description); List<User> findAllStaffWithOpenAssignments(); User createUser(Integer userId, String userRole, String email, String firstName, String lastName, String streetAddress, String city, String stateProvince, String postalCode, String countryId); void updateStaffSkills(Integer userId, List<Integer> prodIds); }
例3-2 SRAdminFacadeBean.java Beanクラス
package oracle.srdemo.model; import java.util.ArrayList; import java.util.List; import java.util.Vector; import javax.ejb.Stateless; import oracle.srdemo.model.entities.ExpertiseArea; import oracle.srdemo.model.entities.Product; import oracle.srdemo.model.entities.User; import oracle.toplink.sessions.Session; import oracle.toplink.sessions.UnitOfWork; import oracle.toplink.util.SessionFactory; @Stateless(name="SRAdminFacade") public class SRAdminFacadeBean implements SRAdminFacade { private SessionFactory sessionFactory; public SRAdminFacadeBean() { this.sessionFactory = new SessionFactory("META-INF/sessions.xml", "SRDemo"); } /** * Constructor used during testing to use a local connection * @param sessionName */ public SRAdminFacadeBean(String sessionName) { this.sessionFactory = new SessionFactory("META-INF/sessions.xml", sessionName); } public Object mergeEntity(Object entity) { UnitOfWork uow = getSessionFactory().acquireUnitOfWork(); Object workingCopy = uow.readObject(entity); if (workingCopy == null) throw new RuntimeException("Could not find entity to update"); uow.deepMergeClone(entity); uow.commit(); return workingCopy; } public Object persistEntity(Object entity) { UnitOfWork uow = getSessionFactory().acquireUnitOfWork(); Object newInstance = uow.registerNewObject(entity); uow.commit(); return newInstance; } public Object refreshEntity(Object entity) { Session session = getSessionFactory().acquireUnitOfWork(); Object refreshedEntity = session.refreshObject(entity); session.release(); return refreshedEntity; } public void removeEntity(Object entity) { UnitOfWork uow = getSessionFactory().acquireUnitOfWork(); Object workingCopy = uow.readObject(entity); if (workingCopy == null) throw new RuntimeException("Could not find entity to update"); uow.deleteObject(workingCopy); uow.commit(); } private SessionFactory getSessionFactory() { return this.sessionFactory; } public List<ExpertiseArea> findExpertiseByUserId(Integer userIdParam) { List<ExpertiseArea> result = null; if (userIdParam != null){ Session session = getSessionFactory().acquireSession(); Vector params = new Vector(1); params.add(userIdParam); result = (List<ExpertiseArea>)session.executeQuery("findExpertiseByUserId", ExpertiseArea.class, params); session.release(); } return result; } public ExpertiseArea createExpertiseArea(Product product, User user, Integer prodId, Integer userId, String expertiseLevel, String notes) { UnitOfWork uow = getSessionFactory().acquireUnitOfWork(); ExpertiseArea newInstance = (ExpertiseArea)uow.newInstance(ExpertiseArea.class); if (product == null) { product = (Product)uow.executeQuery("findProductById", Product.class, prodId); } if (user == null){ user = (User)uow.executeQuery("findUserById", User.class, userId); } newInstance.setProduct(product); newInstance.setUser(user); newInstance.setProdId(prodId); newInstance.setUserId(userId); newInstance.setExpertiseLevel(expertiseLevel); newInstance.setNotes(notes); uow.commit(); return newInstance; } public Product createProduct(Integer prodId, String name, String image, String description) { UnitOfWork uow = getSessionFactory().acquireUnitOfWork(); Product newInstance = (Product)uow.newInstance(Product.class); newInstance.setProdId(prodId); newInstance.setName(name); newInstance.setImage(image); newInstance.setDescription(description); uow.commit(); return newInstance; } public List<User> findAllStaffWithOpenAssignments() { Session session = getSessionFactory().acquireSession(); List<User> result = (List<User>)session.executeQuery("findAllStaffWithOpenAssignments", User.class); session.release(); return result; } public User createUser(Integer userId, String userRole, String email, String firstName, String lastName, String streetAddress, String city, String stateProvince, String postalCode, String countryId) { UnitOfWork uow = getSessionFactory().acquireUnitOfWork(); User newInstance = (User)uow.newInstance(User.class); newInstance.setUserId(userId); newInstance.setUserRole(userRole); newInstance.setEmail(email); newInstance.setFirstName(firstName); newInstance.setLastName(lastName); newInstance.setStreetAddress(streetAddress); newInstance.setCity(city); newInstance.setStateProvince(stateProvince); newInstance.setPostalCode(postalCode); newInstance.setCountryId(countryId); uow.commit(); return newInstance; } public void updateStaffSkills(Integer userId, List<Integer> prodIds) { List<Integer> currentSkills; if (userId != null) { List<ExpertiseArea> currentExpertiseList = findExpertiseByUserId(userId); currentSkills = new ArrayList(currentExpertiseList.size()); //Look for deletions for(ExpertiseArea expertise:currentExpertiseList){ Integer prodId = expertise.getProdId(); currentSkills.add(prodId); if (!prodIds.contains(prodId)){ removeEntity(expertise); } } //Look for additions for (Integer newSkillProdId: prodIds){ if(!currentSkills.contains(newSkillProdId)){ //need to add this.createExpertiseArea(null, null,newSkillProdId,userId,"Qualified",null); } } } } }
通常は、アプリケーションのすべての論理ユニットに対応する1つのセッション・ファサードを作成します。ただし、インスタンスの役割に応じて広い範囲でタスクを定義することも可能です(管理者クライアント操作のためのセッション・ファサードと顧客クライアント操作のためのセッション・ファサードを個別に作成するなど)。複数のセッション・ファサードを作成して名前を付けることにより、UI開発が促進されるため、特定のタスク専用のセッション・ファサードを作成してそのタスクを説明する名前を使用することは、推奨される方法です。
セッション・ファサード・メソッドを生成すると、デフォルトで各エンティティにfindAll()メソッドが作成されます。このメソッドを作成しない場合は、「セッション・ファサード・オプション」ページのツリー・コントロールでこのメソッドの選択を解除します。
セッション・ファサード・メソッドを作成または編集する場合、TopLinkエンティティとEJBエンティティの両方を選択することはできません。プロジェクトがTopLinkエンティティ用に有効化されている場合、セッション・ファサード・メソッドとして使用できるのは、TopLinkエンティティのみです。1つのセッション・ファサードでのTopLinkエンティティとEJBエンティティの統合は、将来のリリースでサポートされる予定です。
新規のセッションBeanは、ウィザードを使用していつでも作成できます。ただし、カスタムの実装コードが含まれる既存のセッションBeanを、新規の永続データ・オブジェクトまたはメソッドで更新することも可能です。
既存のセッションBeanを更新するには、そのセッションBeanを右クリックし、「セッション・ファサードの編集」を選択します。「セッション・ファサード・オプション」ダイアログを使用して、公開するエンティティおよびメソッドを選択します。ただし、新規エンティティを作成した場合、「セッション・ファサード・オプション」ダイアログには、同じプロジェクトの新規エンティティは表示されますが、異なるプロジェクトのエンティティは表示されません。
TopLinkマップ(.mwp
ファイル)には、データベース表をJavaクラスとして表現するのに必要な情報が含まれます。このデータを作成するには、TopLinkマップの作成ウィザードまたはマッピング・エディタを使用するか、JavaとTopLink APIを使用して手動でファイルを記述します。
この情報(メタデータ)を使用して、構成情報を実行時環境に渡します。実行時環境では、この情報を永続エンティティ(JavaオブジェクトまたはEJBエンティティBean)およびTopLink APIで記述されたコードと組み合せて、アプリケーション機能を実行します。
ディスクリプタ
ディスクリプタには、Javaクラスとデータソース表現の関連を記述します。ディスクリプタにより、データ・モデル・レベルでオブジェクト・クラスがデータソースと関連付けられます。たとえば、永続クラスの属性は、データベース列にマップされます。
TopLinkでは、特定クラスのインスタンスがデータソースでどのように表現されるかを記述した情報を格納するために、ディスクリプタを使用します(3.4項「表へのクラスのマッピング」を参照)。ほとんどのディスクリプタ情報は、TopLinkで定義して、実行時にプロジェクトのXMLファイルから読み取ることができます。
永続クラス
TopLinkのデータベース・セッションにディスクリプタを登録するクラスは、永続クラスと呼ばれます。TopLinkでは、データベースに格納されたprivateまたはprotected属性に対して、永続クラスでpublicアクセッサ・メソッドを提供する必要はありません。
データベース表から自動的にJavaクラスを作成するには、表からのJavaオブジェクトの作成ウィザードを使用します。このウィザードでは、次のものを作成できます。
各表のJavaクラス
TopLinkマップ
各表の列にマップされた属性
初期JavaクラスとTopLinkマッピングを作成したら、マッピング・エディタを使用して情報をカスタマイズします。詳細は、Oracle JDeveloperのオンライン・ヘルプを参照してください。
表からのJavaオブジェクトの作成ウィザードが完了すると、JDeveloperによってTopLinkマップが作成され、プロジェクトに追加されます。
このウィザードでは、(データベースの構造と関連の定義に従って)各Java属性のTopLinkディスクリプタおよびマッピングも作成されます。
データベース表からJavaクラスを作成した後で、生成されたTopLinkディスクリプタおよびマッピングを変更できます。この項には、次の内容に関する情報が含まれます。
表からのJavaオブジェクトの作成ウィザードにより、TopLinkディスクリプタと特定のデータベース表が関連付けられます。
マッピング・エディタの「複数表の情報」タブ(図3-7を参照)を使用して、修正メソッドをディスクリプタに関連付けます。
実行時にディスクリプタがロードされるときにコールされるようJavaのstaticメソッドを関連付けることができます。このメソッドにより、ディスクリプタのJavaコードAPIを介して、実行時のディスクリプタ・インスタンスを修正できます。このメソッドを使用すると、TopLinkで現在サポートされていない高度な構成オプションを作成できます。
このJavaメソッドには、次の要件があります。
public staticメソッドとする必要があります。
oracle.toplink.descriptors.ClassDescriptor
型の単一のパラメータを使用する必要があります。
マッピング・エディタの「ロード後」タブ(図3-8を参照)を使用して、修正メソッドをディスクリプタに関連付けます。
表からのJavaオブジェクトの作成ウィザードを使用すると、Oracle JDeveloperにより、Javaクラスの基本コードが自動的に生成されます。
TopLinkの最も優れた機能の1つとして、オブジェクト表現とデータソース固有の表現の間でデータを変換できることがあげられます。マッピングと呼ばれるこの変換は、TopLinkプロジェクトの中心機能です。
1つのマッピングは、ドメイン・オブジェクトの単一のデータ・メンバーに対応します。マッピングにより、オブジェクト・データ・メンバーがデータソース表現に関連付けられ、オブジェクトとデータソース間で双方向変換を実行するための手段が定義されます。
TopLinkでは、オブジェクトとBeanをデータソースにどのようにマップするかを記述するのに、マッピング・エディタで生成されるメタデータを使用します。このアプローチにより、永続性情報とオブジェクト・モデルが分離されるため、開発者は扱いやすい独自のオブジェクト・モデルを、DBAは扱いやすい独自のスキーマをそれぞれ自由に設計できます。
ADF内のTopLinkでは、リレーショナル・マッピングとオブジェクト・リレーショナル(O/R)・マッピングがサポートされます。
リレーショナル・マッピング: 任意のオブジェクト・データ・メンバー・タイプを、サポートされるリレーショナル・データベース内で対応するリレーショナル・データベース(SQL)のデータソース表現に変換するマッピング。リレーショナル・マッピングにより、オブジェクト・モデルをリレーショナル・データ・モデルにマップできます。
オブジェクト・リレーショナル(O/R)・マッピング: 特定のオブジェクト・データ・メンバー・タイプを、Oracleデータベースなどの特別なオブジェクト・リレーショナル・データベースの記憶域にとって最適となる構造化されたデータソース表現に変換するマッピング。オブジェクト・リレーショナル・マッピングにより、オブジェクト・モデルをオブジェクト・リレーショナル・データ・モデルにマップできます。
Javaクラスをデータベース表に直接マップするには、構造ウィンドウのTopLinkマップでJava属性を選択します。Oracle JDeveloperにより、選択した属性に使用可能なマッピングのリストが表示されます(図3-9を参照)。
TopLinkの自動マップ機能を使用して、特定のJavaクラスまたはパッケージの属性を自動的にマップすることも可能です。詳細は、Oracle JDeveloperのオンライン・ヘルプを参照してください。
例3-4は、「フィールドへ直接」を使用してダイレクト・マッピングを作成したときに、Oracle JDeveloperによって生成されるJavaコードを示しています。この例では、Products
クラスのdescription
属性が、データベース表のフィールドに直接マップされます。
マッピング・エディタを使用すると、TopLinkマッピングをカスタマイズできます。ダイレクト・マッピングにおける一般的なカスタマイズは、次のとおりです。
マッピングを読取り専用として指定します。これらのマッピングは、更新操作や削除操作に含まれなくなります。
カスタムのget
およびset
メソッドを使用します。
デフォルト値を定義します。データベースの実際のフィールドがNULLの場合、この値が使用されます。
図3-10は、マッピング・エディタの「フィールドへ直接」マッピングの「一般」タブを示しています。各ダイレクト・マッピング(3.4.2項「ダイレクト・マッピング」を参照)には、追加の特別オプションが含まれる場合もあります。詳細は、Oracle JDeveloperのオンライン・ヘルプを参照してください。
リレーショナル・マッピングでは、永続オブジェクトが他の永続オブジェクトを参照する方法を定義します。TopLinkでは、次の関連マッピングが提供されます。
リレーショナル・マッピングとオブジェクト・リレーショナル・マッピングを混同しないでください。オブジェクト・リレーショナル・マッピングでは、オブジェクト・モデルをOracleデータベースなどのオブジェクト・リレーショナル・データ・モデルにマップできます。TopLinkでは、次のマッピングを作成できます。
オブジェクト・リレーショナル構造マッピング: オブジェクト・リレーショナル集約構造(JDBCのStruct
型とOracleデータベースのOBJECT
型)にマップします。
オブジェクト・リレーショナル参照マッピング: オブジェクト・リレーショナル参照(JDBCのRef
型とOracleデータベースのREF
型)にマップします。
オブジェクト・リレーショナル配列マッピング: プリミティブ・データのコレクションをオブジェクト・リレーショナル配列データ型(JDBCのArray型とOracleデータベースのVARRAY型)にマップします。
オブジェクト・リレーショナル・オブジェクト配列マッピング: オブジェクト・リレーショナル配列データ型(JDBCのArray型とOracleデータベースのVARRAY型)にマップします。
オブジェクト・リレーショナル・ネスト表マッピング: オブジェクト・リレーショナル・ネスト表(JDBCのArray型とOracleデータベースのNESTED TABLE型)にマップします。
これらのマッピングは、Oracle TopLinkランタイムでサポートされますが、Javaコードで作成する必要があります。マッピング・エディタは使用できません。
ダイレクト・マッピング(3.4.3項「ダイレクト・マッピングの作成方法」を参照)の場合と同様に、Javaクラスをデータベース表に直接マップするには、構造ウィンドウのTopLinkマップでJava属性を選択します。
関連マッピングのマッピング・エディタには、データベース表の関連を定義(または作成)するための「表参照」タブが含まれます。
詳細は、Oracle JDeveloperのオンライン・ヘルプを参照してください。
例3-5は、「フィールドへ直接」を使用してダイレクト・マッピングを作成したときに、Oracle JDeveloperによって生成されるJavaコードを示しています。この例では、ServiceRequest
クラスのaddress
属性が、別のクラスであるUser
に対して1対1の関連を保持しています(つまり、各ServiceRequest
は、1つのUser
により作成されています)。
例3-5 関連マッピングのJavaコード
package oracle.srdemo.model; /**Map createdBy <-> oracle.srdemo.model.Users * @associates <{oracle.srdemo.model.Users}> */ private ValueHolderInterface createdBy; public Users getCreatedBy() { return (Users)this.createdBy.getValue(); } public void setCreatedBy(Users createdBy) { this.createdBy.setValue(createdBy); }
マッピング・エディタを使用すると、TopLinkマッピングをカスタマイズできます。関連マッピングにおける一般的なカスタマイズは、次のとおりです。
マッピングを読取り専用として指定します。これらのマッピングは、更新操作や削除操作に含まれなくなります。
カスタムのget
およびset
メソッドを使用します。
デフォルト値を定義します。データベースの実際のフィールドがNULLの場合、この値が使用されます。
インダイレクションを使用します。TopLinkでインダイレクションを使用すると、参照オブジェクトのプレースホルダとしてインダイレクション・オブジェクトが使用されます。TopLinkでは、特定の属性に対するアクセスが発生するまで依存オブジェクトの読取りを遅延します。
プライベートな関連または独立した関連を構成します。プライベートな関連では、ターゲット・オブジェクトがソース・オブジェクトのプライベート・コンポーネントとなります。ソース・オブジェクトを破棄すると、ターゲット・オブジェクトも破棄されます。独立した関連では、ソース・オブジェクトとターゲット・オブジェクトが相互に独立して存在します。一方のオブジェクトの破棄は、必ずしも他方の破棄につながりません。
双方向関連を指定します。この場合、関連内の2つのクラスが、1対1マッピングを使用して相互に参照を行います。
図3-12は、マッピング・エディタの1対1マッピングの「一般」タブを示しています。「表参照」タブ(図3-13を参照)を使用すると、マッピングの外部キー参照を定義できます。各ダイレクト・マッピング(3.5項「関連を使用した関連クラスのマッピング」を参照)には、追加の特別オプションが含まれる場合もあります。詳細は、Oracle JDeveloperのオンライン・ヘルプを参照してください。
TopLinkでは、Object
として主キーを使用する事前定義済のファインダ(findByPrimaryKey
)を提供しています。このファインダは、実行時に定義されます(マッピング・エディタでは定義されません)。
オブジェクトを問い合せるには、TopLink名前付き問合せを作成してから、問合せで指定したクラスのデータ・コントロールを作成します。これにより、TopLink問合せをデータ・コントロールに公開できます。
名前付き問合せは、後から取得および実行するために作成して保存するTopLink問合せです。名前付き問合せ(および関連するすべてのサポート・オブジェクト)は、一度準備しておくと後で効率的に再利用することが可能であり、頻繁に実行される操作に適しているため、その使用によりアプリケーションのパフォーマンスが向上します。
次の問合せを作成できます。
ReadAllQuery
ReadObjectQuery
TopLink式ビルダー、SQL式またはEJB QL式を使用して、TopLink名前付き問合せを作成できます。マッピング・エディタ(図3-14を参照)を使用すると、ディスクリプタ・レベルまたはセッション・レベルで問合せを構成できます。
Query By Exampleでは、問合せに使用する属性のみを入力可能なサンプル・オブジェクト・インスタンスのフォームに問合せ選択基準を指定できます。Query By Exampleを定義するには、ReadObjectQuery
またはReadAllQuery
に対して、サンプルの永続オブジェクト・インスタンスおよび(オプションで)Query By Exampleポリシーを指定します。
ADFでは、TopLinkのQuery By Exampleにより実行されるのは、メモリー内問合せのみです。
TopLink問合せのソート基準は、Oracle JDeveloperからは構成できません。ディスクリプタ修正メソッドを使用して、Javaメソッドを記述する必要があります。詳細は、3.3.3.2項「修正メソッドの使用」を参照してください。
データベースのトランザクションは、単一の操作として成功するか失敗する複数の操作(作成、読取り、更新または削除)のセットです。失敗したトランザクションは、データベースにより破棄(ロールバック)されるため、その場合データベースは元の状態のままとなります。
TopLinkでは、トランザクションは作業ユニット・オブジェクトに格納されます。作業ユニットは、セッションから取得するか、そのAPIを使用して取得します。トランザクションは、直接制御するか、Java Transaction API(JTA)などのJava 2 Enterprise Edition(J2EE)アプリケーション・サーバー・トランザクション・コントローラを使用して制御します。
作業ユニットにより、トランザクション内の変更は、正常にデータベースにコミットされるまで他のスレッドから隔離されます。他のトランザクション・メカニズムとは異なり、作業ユニットでは、トランザクション内のオブジェクトに対する変更、変更の順序、および他のTopLinkキャッシュを無効化する可能性のある変更を自動的に管理できます。
作業ユニットは、これらの問題を管理するために、最小限の変更セットの計算、参照整合性規則への準拠とデッドロック回避を目的とするデータベース・コールの順序付け、および変更済オブジェクトの共有キャッシュへのマージを行います。クラスタ化された環境の場合、作業ユニットは、調整されたキャッシュ内で他のサーバーとの変更の同期も実行します。
例3-7は、クライアントのセッション・オブジェクトから作業ユニットを取得する方法を示しています。
例3-7 作業ユニットの取得
public UnitOfWork acquireUnitOfWork() { Server server = getServer(); if (server.hasExternalTransactionController()) { return server.getActiveUnitOfWork(); server.acquireUnitOfWork(); }
作業ユニットで新規オブジェクトを作成する場合、コミット時に作業ユニットがそのオブジェクトをデータベースに書き込むことができるように、registerObject
メソッドを使用します。
作業ユニットは、1対1マッピングと1対多マッピングの外部キー情報を使用して、コミット順序を計算します。コミット・トランザクション中に制約問題が発生した場合は、マッピング定義を確認してください。registerObject
メソッドを使用してオブジェクトを登録する場合の順序は、コミット順序には影響しません。
例3-8と例3-9は、作業ユニットのregisterObject
メソッドにより戻されるクローンを使用して、(関連のない)単純なオブジェクトを作成および永続化する方法を示しています。
例3-8 オブジェクトの作成: 推奨される方法
UnitOfWork uow = session.acquireUnitOfWork(); Pet pet = new Pet(); Pet petClone = (Pet)uow.registerObject(pet); petClone.setId(100); petClone.setName("Fluffy"); petClone.setType("Cat"); uow.commit();
例3-9は、もう1つの一般的な方法を示しています。
例3-9 オブジェクトの作成: もう1つの方法
UnitOfWork uow = session.acquireUnitOfWork(); Pet pet = new Pet(); pet.setId(100); pet.setName("Fluffy"); pet.setType("Cat"); uow.registerObject(pet); uow.commit();
どちらの方法でも、次のSQLが生成されます。
INSERT INTO PET (ID, NAME, TYPE, PET_OWN_ID) VALUES (100, 'Fluffy', 'Cat', NULL)
例3-8の方法をお薦めします。この方法は、クローンの操作パターンを理解するのに効果的で、将来的なコード変更に対する柔軟性が最も高いためです。新規オブジェクトとクローンを組み合せて操作すると、両方を混同して予期しない結果が発生する可能性があります。
クライアント・アプリケーションは、セッション・オブジェクトから作業ユニットを取得します。
クライアント・アプリケーションは、変更しようとするキャッシュ・オブジェクトをTopLinkに問い合せて取得し、そのキャッシュ・オブジェクトを作業ユニットに登録します。
作業ユニットは、オブジェクトの変更ポリシーに従ってオブジェクトを登録します。
デフォルトでは、オブジェクトが登録されるたびに、作業ユニットはセッション・キャッシュまたはデータベースのオブジェクトにアクセスし、バックアップ・クローンと作業クローンを作成します。作業ユニットは、クライアント・アプリケーションに作業クローンを戻します。
クライアント・アプリケーションは、作業ユニットから戻された作業オブジェクトを変更します。
クライアント・アプリケーション(または外部トランザクション・コントローラ)は、トランザクションをコミットします。
作業ユニットは、オブジェクトの変更ポリシーに従って、各登録済オブジェクトの変更セットを計算します。
デフォルトでは、コミット時に、作業ユニットにより作業クローンとバックアップ・クローンが比較され、変更セットが計算されます(つまり、必要とされる最小限の変更が決定されます)。同じオブジェクトに対する同時変更が不正な変更と識別されないように、この比較はバックアップ・クローンを対象に実行されます。その後、作業ユニットにより、新規オブジェクトまたは変更オブジェクトのデータベースへのコミットが試行されます。
コミット・トランザクションに成功すると、作業ユニットは、変更を共有セッション・キャッシュにマージします。コミットに失敗した場合、共有キャッシュのオブジェクトに対する変更は発生しません。変更がない場合、作業ユニットは新規トランザクションを開始しません。
例3-10は、コードでのデフォルトのライフサイクルを示しています。
// The application reads a set of objects from the database Vector employees = session.readAllObjects(Employee.class); // The application specifies an employee to edit . . . Employee employee = (Employee) employees.elementAt(index); try { // Acquire a unit of work from the session UnitOfWork uow = session.acquireUnitOfWork(); // Register the object that is to be changed. Unit of work returns a clone // of the object and makes a backup copy of the original employee Employee employeeClone = (Employee)uow.registerObject(employee); // Make changes to the employee clone by adding a new phoneNumber. // If a new object is referred to by a clone, it does not have to be // registered. Unit of work determines it is a new object at commit time PhoneNumber newPhoneNumber = new PhoneNumber("cell","212","765-9002"); employeeClone.addPhoneNumber(newPhoneNumber); // Commit the transaction: unit of work compares the employeeClone with // the backup copy of the employee, begins a transaction, and updates the // database with the changes. If successful, the transaction is committed // and the changes in employeeClone are merged into employee. If there is an // error updating the database, the transaction is rolled back and the // changes are not merged into the original employee object uow.commit(); } catch (DatabaseException ex) { // If the commit fails, the database is not changed. The unit of work should // be thrown away and application-specific action taken } // After the commit, the unit of work is no longer valid. Do not use further
例3-11では、作業ユニットの前にPet
が読み取られます。変数pet
は、このPet
のキャッシュ・コピー・クローンです。このコードでは、作業ユニット内でキャッシュ・コピーを登録して作業コピー・クローンを取得します。次に、作業コピー・クローンを変更して作業ユニットをコミットします。
例3-11 オブジェクトの変更
// Read in any pet
Pet pet = (Pet)session.readObject(Pet.class);
UnitOfWork uow = session.acquireUnitOfWork();
Pet petClone = (Pet) uow.registerObject(pet);
petClone.setName("Furry");
uow.commit();
例3-12では、作業ユニットを通じた問合せによりクローンを戻すことができるという事実を利用して登録手順を省略しています。ただし、この方法には、キャッシュ・コピー・クローンのハンドルを取得できないというデメリットがあります。
コミット・トランザクション後に、更新されたPet
を操作する場合は、セッションに問い合せて取得する必要があります(作業ユニットがコミットされると、そのクローンは無効となるため、使用できないことに注意してください)。
例3-12 オブジェクトの変更: 登録手順の省略
UnitOfWork uow = session.acquireUnitOfWork(); Pet petClone = (Pet) uow.readObject(Pet.class); petClone.setName("Furry"); uow.commit();
どちらの方法でも、次のSQLが生成されます。
UPDATE PET SET NAME = 'Furry' WHERE (ID = 100)
作業ユニットを通じた問合せは、慎重に使用してください。問合せで読み取られるすべてのオブジェクトは、作業ユニットに登録されるため、コミット時に変更をチェックされます。アプリケーション設計では、作業ユニットを通じてReadAllQuery
を実行するより、セッションを通じてReadAllQuery
を実行し、変更が必要なオブジェクトのみを作業ユニットに登録した方が、パフォーマンス的には有利です。
作業ユニットのオブジェクトを削除するには、deleteObject
またはdeleteAllObjects
メソッドを使用します。まだ作業ユニットに登録されていないオブジェクトを削除すると、作業ユニットによってそのオブジェクトが自動的に登録されます。
オブジェクトを削除すると、TopLinkにより、そのオブジェクトがプライベートに所有する子の部分も削除されます。これらの部分は、所有する(親の)オブジェクトがないと存在できないためです。コミット時に、作業ユニットにより、データベース制約に従いながらオブジェクトを削除するSQLが生成されます。
プライベートに所有された関連内でガベージ・コレクションの対象とならないオブジェクト(特にオブジェクト・モデルのルート・オブジェクト)が存在する場合、例3-13のようにdeleteObject
APIを使用して、オブジェクトを表現している行を明示的に削除できます。
TopLinkの作業ユニットは、強力なトランザクション・モデルです。この項に記載した内容の追加情報は、『Oracle TopLink開発者ガイド』のTopLinkのトランザクションに関する章を参照してください。
作業ユニットは、オブジェクトのディスクリプタに構成された変更ポリシーに基づいて、登録されたオブジェクトの変更を追跡します。変更がない場合、作業ユニットは新規トランザクションを開始しません。
表3-1に、TopLinkが提供する変更ポリシーを示します。
TopLinkを使用して次の作業ユニットを作成できます。
作業ユニット(子)は、別の作業ユニット(親)内でネストできます。ネストされた作業ユニットは、変更をデータベースにコミットしません。かわりに、変更は親の作業ユニットに渡され、コミット時に親によって変更のコミットが試行されます。作業ユニットをネストすることで、規模の大きなトランザクションを小さな個別のトランザクションに分割できます。この場合、次の処理が保証されます。
ネストされた各作業ユニットの変更は、1つのグループとしてコミットされるか、または失敗します。
ネストされた作業ユニットの失敗は、親の作業ユニットで発生する他の操作のコミットまたはロールバック・トランザクションに影響しません。
変更は、単一のトランザクションとしてデータベースに提供されます。
式やSQL文字列のかわりに、StoredProcedureCall
オブジェクトを問合せに指定できます。ただし、プロシージャでは、問合せ対象のクラスのインスタンスを作成するのに必要なすべてのデータが戻される必要があります。
例3-14 ストアド・プロシージャでのReadAll問合せ
ReadAllQuery readAllQuery = new ReadAllQuery(); call = new StoredProcedureCall(); call.setProcedureName("Read_All_Employees"); readAllQuery.setCall(call); Vector employees = (Vector) session.executeQuery(readAllQuery);
StoredProcedureCall
を使用して、次の操作を実行できます。
注意: StoredProcedureCall をOUT またはINOUT パラメータと組み合せて使用する場合、DatabaseQuery のbindAllParameters メソッドを使用する必要はなくなりました。ただし、すべてのOUT およびINOUT パラメータに対して常にJava型を指定する必要があります。指定しない場合、デフォルトでString 型が使用されることに注意してください。 |
例3-15では、StoredProcedureCall
のaddNamedArgument
メソッドを使用して、入力パラメータとしてPOSTAL_CODE
パラメータを指定しています。引数の値は、addNamedArgumentValue
メソッドを使用して指定できます。
例3-15 入力パラメータ付きのストアド・プロシージャ・コール
StoredProcedureCall call = new StoredProcedureCall(); call.setProcedureName("CHECK_VALID_POSTAL_CODE"); call.addNamedArgument("POSTAL_CODE"); call.addNamedArgumentValue("L5J1H5"); call.addNamedOutputArgument( "IS_VALID", // procedure parameter name "IS_VALID", // out argument field name Integer.class // Java type corresponding to type returned by procedure ); ValueReadQuery query = new ValueReadQuery(); query.setCall(call); Number isValid = (Number) session.executeQuery(query);
引数を追加する順序は、引数の値を追加する順序と一致している必要があります。例3-16では、引数NAME
が値Juliet
に、引数SALARY
が値80000
にバインドされます。
出力パラメータにより、ストアド・プロシージャで追加の情報を戻すことが可能になります。オブジェクトの作成に必要とされるすべてのフィールドを戻す場合、出力パラメータを使用してreadObjectQuery
を定義できます。
例3-17では、StoredProcedureCall
のaddNamedOutputArgument
メソッドを使用して、出力パラメータとしてIS_VALIDパラメータを指定しています。
例3-17 出力パラメータ付きのストアド・プロシージャ・コール
StoredProcedureCall call = new StoredProcedureCall(); call.setProcedureName("CHECK_VALID_POSTAL_CODE"); call.addNamedArgument("POSTAL_CODE"); call.addNamedOutputArgument( "IS_VALID", // procedure parameter name "IS_VALID", // out argument field name Integer.class // Java type corresponding to type returned by procedure ); ValueReadQuery query = new ValueReadQuery(); query.setCall(call); query.addArgument("POSTAL_CODE"); Vector parameters = new Vector(); parameters.addElement("L5J1H5"); Number isValid = (Number) session.executeQuery(query,parameters);
注意: データを戻す出力パラメータの使用に対応していないデータベースもあります。ただし、そのようなデータベースでは、一般的にストアド・プロシージャから結果セットを戻すことができるため、出力パラメータは必要ありません。 |
Oracleデータベースを使用している場合、TopLinkのカーソル問合せ結果およびストリーム問合せ結果を利用できます。
例3-18では、StoredProcedureCall
のaddNamedInOutputArgumentValue
メソッドを使用して、入出力パラメータとしてLENGTH
パラメータを指定し、ストアド・プロシージャに渡す引数の値を指定しています。引数の値を指定しない場合は、addNamedInOutputArgument
メソッドを使用します。
例3-18 入出力パラメータ付きのストアド・プロシージャ・コール
StoredProcedureCall call = new StoredProcedureCall(); call.setProcedureName("CONVERT_FEET_TO_METERs"); call.addNamedInOutputArgumentValue( "LENGTH", // procedure parameter name new Integer(100), // in argument value "LENGTH", // out argument field name Integer.class // Java type corresponding to type returned by procedure ) ValueReadQuery query = new ValueReadQuery(); query.setCall(call); Integer metricLength = (Integer) session.executeQuery(query);
TopLinkでは、データベースでサポートされる出力パラメータ・イベントを管理できます。たとえば、アプリケーションでエラー状態をチェックすることが可能なエラー・コードがストアド・プロシージャから戻される場合、TopLinkでセッション・イベントのOutputParametersDetected
を起動し、アプリケーションでそれらの出力パラメータを処理できます。
例3-19 リセット設定と出力パラメータ・エラー・コード付きのストアド・プロシージャ
StoredProcedureCall call = new StoredProcedureCall(); call.setProcedureName("READ_EMPLOYEE"); call.addNamedArgument("EMP_ID"); call.addNamedOutputArgument( "ERROR_CODE", // procedure parameter name "ERROR_CODE", // out argument field name Integer.class // Java type corresponding to type returned by procedure ); ReadObjectQuery query = new ReadObjectQuery(); query.setCall(call); query.addArgument("EMP_ID"); ErrorCodeListener listener = new ErrorCodeListener(); session.getEventManager().addListener(listener); Vector args = new Vector(); args.addElement(new Integer(44)); Employee employee = (Employee) session.executeQuery(query, args);
StoredProcedureCall
を使用すると、ストアド・プロシージャをサポートするデータベースに定義されたストアド・プロシージャを起動できます。また、StoredFunctionCall
を使用すると、ストアド・ファンクションをサポートするデータベース(DatabasePlatform
のsupportsStoredFunctions
メソッドでtrue
が戻されるデータベース)に定義されたストアド・ファンクションを起動できます。
通常は、ストアド・プロシージャとストアド・ファンクションの両方で、入力パラメータ、出力パラメータおよび入出力パラメータを指定できます。ただし、ストアド・プロシージャは値を戻す必要がありませんが、ストアド・ファンクションは常に単一の値を戻します。
StoredFunctionCall
クラスは、setResult
という新規メソッドを追加することでStoredProcedureCall
を拡張しています。このメソッドを使用して、TopLinkでストアド・ファンクションの戻り値を格納する場所の名前(または名前と型の両方)を指定します。
TopLinkでStoredFunctionCall
の準備が行われる場合、そのSQLが検証され、次の場合にはValidationException
がスローされます。
現在のプラットフォームでストアド・ファンクションがサポートされない場合
ユーザーが戻り型の指定に失敗した場合
例3-20では、StoredFunctionCall
のsetProcedureName
メソッドを使用してストアド・ファンクションの名前を設定していることに注意してください。
例3-20 StoredFunctionCallの作成
StoredFunctionCall functionCall = new StoredFunctionCall(); functionCall.setProcedureName("READ_EMPLOYEE"); functionCall.addNamedArgument("EMP_ID"); functionCall.setResult("FUNCTION_RESULT", String); ReadObjectQuery query = new ReadObjectQuery(); query.setCall(functionCall); query.addArgument("EMP_ID"); Vector args = new Vector(); args.addElement(new Integer(44)); Employee employee = (Employee) session.executeQuery(query, args);
ユーザー・インタフェースにサービスをバインドする最も簡単な方法は、ADFデータ・コントロールを使用することです。
この項には、次の内容に関する情報が含まれます。
EJBセッションBeanからADFデータ・コントロールを作成するには、ナビゲータでセッションBeanを右クリックして「データ・コントロールの作成」を選択するか、セッションBeanをデータ・コントロール・パレットにドラッグします。
注意: Oracle固有のライブラリに依存しないJ2EE開発者は、ADFデータ・コントロールのかわりにマネージドBeanを使用できます。この方法は、複雑であり、このマニュアルの対象範囲外です。 |
EJB 3.0セッションBeanからデータ・コントロールを作成すると、いくつかのXMLファイルが生成され、ナビゲータに表示されます。次からの各項で、生成されるファイルとデータ・コントロール・パレットについて説明します。
データ・コントロールを作成すると、次のXMLファイルがモデルに生成されます。
UpdateableSingleValue.xml: 設計時XMLファイル
これらのファイルの相互関係と使用方法の詳細は、付録A「ADF XMLファイルのリファレンス」を参照してください。
DataControls.dcxファイルは、ビジネス・サービスのデータ・コントロールを登録すると作成されます。.dcxファイルは、クライアントと使用可能なビジネス・サービス間の対話を支援するOracle ADF Modelレイヤーのアダプタ・クラスを識別します。EJB、WebサービスおよびBeanベースのデータ・コントロールの場合、プロパティ・インスペクタでこのファイルを編集し、パラメータの追加と削除、およびデータ・コントロール設定の変更を行えます。たとえば、.dcxファイルを使用して様々な項目のグローバル・プロパティを設定できます(ソートの有効化と無効化など)。
セッションBeanをOracle ADFデータ・コントロールとして登録すると、すべてのセッションBeanに対応するXML定義ファイルがModelプロジェクトに作成されます。このファイルは、一般的に構造定義ファイルと呼ばれます。構造定義ファイルには、セッションBeanと同じ名前が付けられますが、拡張子は.xmlです。
構造定義は、次の3つのタイプのオブジェクトで構成されます。
属性
アクセッサ
操作
クライアント開発者は、データ・コントロール・パレットを使用して、データ・バインドされたHTML要素(JSPページ用)、データ・バインドされたFaces要素(JSF JSPページ用)、およびデータ・バインドされたSwing UIコンポーネント(ADF Swingパネル用)を作成できます。データ・コントロール・パレットには、次の2つの選択リストがあります。
使用可能なビジネス・オブジェクト、メソッド、およびデータ・コントロール操作を階層状に表示したリスト
指定したビジネス・オブジェクト用に選択して表示中のクライアント文書にドロップできる、適切なビジュアル要素のドロップダウン・リスト
また、Webアプリケーション開発者は、データ・コントロール・パレットを使用してビジネス・サービスの提供するメソッドを選択し、それらをページ・フローのデータ・ページやデータ・アクション上にドロップできます。
パレットは前述の各項で説明したXMLファイルを直接反映しているため、ファイルを編集するとパレット内の要素も変更されます。
データ・コントロール・パレットに表示されるビジネス・サービスの階層構造は、Modelプロジェクトのデータ・コントロールに登録されているビジネス・サービスによって決定されます。パレットには、登録されたビジネス・サービスごとに個別のルート・ノードが表示されます。
データ・コントロール・パレットのルート・ノードは、ビジネス・サービス用に登録されたデータ・コントロールを表します。データ・コントロールのルート・ノードの下には、Beanベースのビジネス・サービスが、コンストラクタ、属性、アクセッサまたは操作として表示されます。
コンストラクタ: 作成可能な型は、「コンストラクタ」ノード内に格納されます。これらの型は、オブジェクトのデフォルト・コンストラクタをコールします。
属性: Beanプロパティなど。これらのプロパティで、単純なスカラー値オブジェクト、構造化オブジェクト(Bean)またはコレクションを定義できます。
アクセッサ: get()メソッドとset()メソッド。
操作: Beanメソッドなど。これらのメソッドは、値を戻す場合と戻さない場合があり、メソッド・パラメータも使用する場合と使用しない場合があります。Webサービスの場合、データ・コントロール・パレットには操作のみが表示されます。
データ・コントロール・パレットの使用方法の詳細は、第5章「ページでのデータの表示」を参照してください。データ・コントロール・ファイルとその相互関係の詳細は、付録A「ADF XMLファイルのリファレンス」を参照してください。
Modelプロジェクトのデータ・コントロール定義をすでに作成済の場合、ビジネス・サービスの変更後にデータ・コントロールを更新できます。データ・コントロール定義をリフレッシュすると、ADFアプリケーションでビジネス・サービスの最新の変更を使用できます。データ・コントロール定義をリフレッシュするための操作は、Modelプロジェクトに対する変更のタイプに応じて異なります。
パレットが表示されていない場合は、「表示」メニューの「データ・コントロール・パレット」を選択します。パレットが表示されている場合は、パレット内で右クリックして「リフレッシュ」を選択します。
Modelプロジェクトで、作成するBeanまたは他のビジネス・サービスの新規プロパティを定義します。.javaファイルをコンパイルすると、対応する.xmlファイルにビジネス・サービスのメタデータが再生成されます。変更されたビジネス・サービスがBeanベース(EJBセッションBeanなど)の場合は、Beanの.xmlファイルを右クリックして「リフレッシュ」を選択します。
注意: ADF Business Componentsの場合、データ・コントロール定義は、ADF BCのプロジェクト・ファイルが変更されるたびに自動的に更新されます。
データ・コントロール定義を削除するには、ビュー・プロジェクトでDataBindings.dcxファイルを選択し、構造ウィンドウでModelプロジェクトに表示されなくなったビジネス・サービスを表すデータ・コントロール・ノードを選択します。そのデータ・コントロール・ノードを右クリックし、「削除」を選択します。
JDeveloperにより、Modelプロジェクトのデータ・コントロール定義ファイル(DataBindings.dcx)が更新されます。DataBindings.dcxファイルは、クライアントと使用可能なビジネス・サービス間の対話を支援するOracle ADF Modelレイヤーのアダプタ・クラスを識別します。