Главная » Java, JavaBeans » Пример использования Entity-Компонента Bank

0

Пример bank иллюстрирует использование Entity-Компонента. В нем предусмотрены две реализации одного и того же remote-интерфейса Account: одна реализация использует сохранение, управляемое Контейнером (СМР), другая – сохранение, управляемое Компонентом (BMP).

Счета клиентов (saving accounts) моделируются с помощью Компонента, который называется SavingAccount. Для него используется BMP. Когда мы будем подробнее знакомиться с кодом Компонента, вы увидите, что он содержит JDBC-вызовы к базе данных.

Контрольные счета (checking accounts) моделируются с помощью Компонента, который называется CheckingAccount. Сохранение его состояния возложено на Контейнер, т.е. он иллюстрирует использование СМР.

Третий вид Компонента – Оператор (Teller), который занимается переводом средств с одного счета на другой. Он представляет из себя stateless Session-Компонент, и его назначение – показать, как группу обращений к нескольким Entity-Компонентам можно заключить в управляемую Контейнером транзакцию. Так, если операция записи на счет выполняется раньше операции списания со счета и произошел сбой во время операции списания, транзакция будет откачена, и ни одно из операций не будет выполнена.

Ноте-интерфейс Entity-Компонента

Несколько entity-Компонентов могут реалнзовывать один и тот же интерфейс, даже если они используют различный вид управления состоянием (СМР или BMP). И SavingsAccount, и CheckingAccount используют один и тот же home-интерфейс (AccountHome) и remote- интерфейс (Account).

Ноте-интерфейсы для entity- и session-Компонентов очень похожи. Они наследуют один и тот же интерфейс javax.ejb.EJBHome. Ноте- интерфейс для entity-Компонента обязан объявить хотя бы один метод поиска; кроме того, наличие create-методов не является обязательным.

Пример Кода 7.4 содержит определение интерфейса AccountHome.

Пример Кода 7.4 Интерфейс AccountHome

public interface AccountHome extends javax.ejb.EJBHome { Account create(String name, float balance)

throws java.rmi.RemoteException, javax.ejb.CreateException; Account findByPrimaryKey(AccountPK primaryKey)

throws java.rmi.RemoteException, javax.ejb.FinderException; java.util.Enumeration findAccountsLargerThan(float balance) throws java.rmi.RemoteException, javax.ejb.FinderException;

}

Интерфейс AccountHome объявляет три метода. Хотя наличие метода create () не является обязательным, такой метод объявлен в нашем примере. Этот метод позволяет добавить новую запись в сопоставленную с Компонентом таблицу БД. При добавлении новых данных Entity-Компонент может полагаться на систему управления этой БД (DBMS) или на другое приложение – в этом случае метод create () не используется.

В нашем примере метод create () имеет два аргумента – номер счета (string) и его начальное состояние (float). Реализация метода использует значения этих аргументов для инициализации объекта, т.е. присвоения начальных значений номера счета и его состояния для вновь созданного объекта. Список возможных исключений должен включать в себя java.rmi.RemoteException и java.ejb.CreateException; если это необходимо, в throw-список метода create() можно добавить и другие исключения.

Entity-Компонент обязан объявить метод поиска findByPrimarykey (); он находится в интерфейсе AccountHome. Этот метод имеет единственный аргумент – значение главного ключа типа AccountPK, и возвращает ссылку на remote-интерфейс Account. Разумеется, метод используются для поиска только одного Компонента.

В home-интерфейсе AccountHome объявлен еще один метод поиска, findAccountLargerThan (), хотя это и не является обязательным. Этот метод возвращает набор Компонентов (Java Enumeration), которые

удовлетворяют условию поиска (на счету средств больше, чем указанная величина).

Все методы поиска обязаны содержать java.rmi.RemoteException и

java.ejb.FinderException в их throws-списке исключений.

Remote-интерфейс Entity-Компонента

Несколько Entity-Компонентов могут реализовывать один и тот же remote-интерфейс, даже если они используют различные способы сохранения своего состояния (BMP или СМР). Оба Entity-Компонента примера bank реализуют один и тот же интерфейс Account.

Remote-интерфейсы для Session- и Entity-Компонентов практически идентичны друг другу – оба они наследуют интерфейс javax.ejb.EJBObject и объявляют те бизнес-методы, который должны быть доступны для клиента.

Пример Кода 7.5 содержит реализацию remote-интерфейса:

Пример Кода 7.5 Remote-интерфейс Account

public interface Account extends javax.ejb.EJBObject {

public float getBalance() throws java.rmi.RemoteException; public void credit(float amount) throws

j ava.rmi.RemoteException; public void debit(float amount) throws java.rmi.RemoteException;

}

Интерфейс Account объявляет три бизнес-метода: getBalance () , credit(0 и debit().

Entity-Компонент с CMP

ebjPassivate(), ejbLoad() , ejbStore(), ejbRemove(), setEntityContext() и unsetEntityContext (). Впрочем, обязательной является их тривиальная реализация, хотя, разумеется, разработчик может добавить в них любой код. В нашем примере для Компонента CheckingAccount выполняется сохранение контекста, возвращаемого методом setEntityContext (), и его сброс в методе unsetEntityContext (). Остальные методы не содержат никакого дополнительного кода.

•          Для нашего Компонента предусмотрена реализация метода ejbCreate () (поскольку мы позволяем клиенту создавать новые счета), и для инициализации полей нового объекта используются значения аргументов этого метода. В качестве результата метод ejbCreate () возвращает значение null, так как создание нужной ссылки для возврата ее клиенту при использовании СМР выполняется Контейнером.

•          Для нашего Компонента в методе ejbPostCreate () выполняется минимум операций, хотя, в принципе, этот метод мог бы выполнять значительную часть действий по инициализации Компонента. Для Компонента с СМР важно выполнить только самые необходимые действия, так как вызов метода ejbPostCreate () выполняется как уведомление (notification callback) о выполняемых действиях. Обратите внимание, что это же справедливо и для других методов, унаследованных от интерфейса EntityBean.

Пример Кода 7.6 Реализация Entity-Компонента с СМР.

import javax.ejb.[2];

import java.rmi.RemoteException;

public class CheckingAccount implements EntityBean { private javax.ejb.EntityContext context; public String name; public float balance; public float getBalance () { return balance;

}

public void debit(float amount) { if(amount > balance) {

// Пометить текущую транзакцию для последующего отката context.setRollbackOnly ();

}

else {

balance = balance – amount;

}

}

public void credit(float amount) { balance = balance + amount;

}

public AccountPK ejbCreate(String name, float balance) { this.name = name; this.balance = balance; return null;

}

public void ejbPostCreate(String name, float balance) {} public void ejbRemove() {}

public void setEntityContext(EntityContext context) { _context = context;

}

public void unsetEntityContext() { context = null;

}

public void ejbActivate!) {} public void ejbPassivate() {} public void ejbLoad() {} public void ejbStore() {} public String toStringO {

return "CheckingAccount[name=" + name + ",balance=" + balance + "]";

}

}

Entity-Компонент с BMP

•          Разработчик для Компонентов как с BMP, так и с СМР может поместить дополнительный код в метод ejbPostCreate (). В нашем примере такой код отсутствует.

•          Необходимо написать реализации методов ejbLosd() nejbStore(). Для Компонента с СМР вполне подходят тривиальные (пустые) реализации, так как взаимодействие с БД берет на себя Контейнер. Entity-Компонент с BMP должен сам выполнять чтение информации из БД в поля Компонента (метод ejbLosd()) и запись в нее внесенных изменений (метод ejbStoreO).

•          Должна присутствовать реализация всех методов поиска. Компонент SavingsAccount содержит два таких метода: обязательный –

ejbFindByPrimaryKey () и произвольный – ejbFindAccountsLargerThan ().

•          Entity-Компонент с BMP обязан реализовать метод ejbRemove (), объявленный в интерфейсе EntityBean. Поскольку Компонент является просто объектной моделью информации в БД, он обязан реализовать этот метод, чтобы позволить выполнить удаление данных (т.е. Entity-Компонента) из БД. Компоненту с СМР достаточно иметь пустую реализацию этого метода, так как все необходимые действия выполняются Контейнером.

•          Каждый метод, имеющий отношение к взаимодействию с базой данных – ejbCreate () , ejbRemove (), ejbLoad(), ejbStoreO, ejbFindByPrimaryKey () и другие методы поиска, а также бизнес- методы Компонента – должны содержать соответствующий код. В общем случае все эти методы содержат команды соединения с базой данных и команды создания и выполнения операторов SQL, которые и реализуют функциональность Компонента. После выполнения всех SQL-операторов, код метода должен "закрыть" эти операторы и выполнить отсоединение от базы данных.

Пример Кода 7.7 содержит наиболее интересные фрагменты реализации класса SavingsAccount. В примере не показаны методы с тривиальной реализацией – ejbActivate () , ejbPassivate () и др.

Давайте подробнее ознакомимся с методом ejbLoad() для изучения того, каким образом Компонент с BMP взаимодействует с базой данных. Обращаем ваше внимание, что все другие методы, реализованные в классе SavingsAcoounts, выполняют примерно те же действия. Сначала устанавливается соединение с базой данных, для чего используется вызов метода getConnection, который использует DataSource для получения JDBC-соединения с базой данных из пула JDBC-соединений. После того, как соединение установлено, метод ejbLoad() создает объект типа PreparedStatement и сопоставленный с ним SQL-оператор. Поскольку метод ejbLoad() отвечает за чтение данных из БД в поля Компонента, он формирует, а затем выполняет оператор SELECT, который читает состояние для указанного счета (по его названию). Если запрос возвращает результат, то из него извлекается количество средств на счету. Затем метод ejbLoad() закрывает объект PreparedStatement и отсоединятся от базы данных. Обратите внимание, что не происходит настоящего закрытия соединения с БД – вместо этого соединение возвращается в пул соединений.

Пример Кода 7.7 Реализация Entity-Компонента с BMP

import java.sql.*;

import javax.ejb.*;

import java.util.

import java.rmi.RemoteException;

public class SavingsAccount implements EntityBean { private EntityContext context; private String name; private float balance; public float getBalance() { return balance;

}

public void debit(float amount) { if(amount > balance) {

// Пометить транзакцию для последующего отката context setRollbackOnly(); } else {

balance = balance – amount;

}

}

public void credit(float amount) { balance = balance + amount;

}

// Пустая реализация методов setEntityContext!),

unsetEntityContext(), // ejbActivate(), ejbPassivate(), ejbPostCreate() не показана

public AccountPK ejbCreate(String name, float balance) throws RemoteException, CreateException { name = name; _balance = balance; try {

Connection connection = getConnection (); PreparedStatement statement = connection.prepareStatement

("INSERT INTO Savings_Accounts (name, balance) VALUES (?, ?)");

statement.setString(1, name); statement.setFloat (2, _balance); if(statement.executeUpdate() != 1) {

throw new CreateException("Could not create: " + name);

}

statement.close(); connection.close(); return new AccountPK(name);

} catch(SQLException е) {

throw new RemoteException("Could not create: " + name, e);

}

}

public void ejbRemove() throws RemoteException, RemoveException { try {

Connection connection = getConnection (); PreparedStatement statement = connection.prepareStatement

("DELETE FROM Savings_Accounts WHERE name = ?"); statement.setString(1, name); if(statement.executeUpdate() != 1) {

throw new RemoveException("Could not remove: " + name);

}

statement.close(); connection.close(); } catch(SQLException e) {

throw new RemoteException("Could not remove: " + name, e);

}

}

public AccountPK ejbFindByPrimaryKey(AccountPK key) throws RemoteException, FinderException { try {

Connection connection = getConnection(); PreparedStatement statement = connection.prepareStatement

("SELECT name FROM Savings_Accounts WHERE name = ?"); statement.setString(1, key.name); ResultSet resultSet = statement.executeQuery(); if(!resultSet.next ()) {

throw new FinderException("Could not find: " + key);

}

statement.close(); connection.close(); return key; } catch(SQLException e) {

throw new RemoteException("Could not find: " + key, e);

}

}

public.java.util.Enumeration ejbFindAccountsLargerThan(float balance)

throws RemoteException, FinderException { try {

Connection connection = getConnection(); PreparedStatement statement = connection.prepareStatement

("SELECT name FROM Savings_Accounts WHERE balance > ?"); statement.setFloat(1, balance); ResultSet resultSet = statement.executeQuery(); Vector keys = new Vector(); while(resultSet.next()) {

String name = resultSet.getString(1); keys.addElement(new AccountPK(name));

}

statement.close(); connection.close(); return keys.elements(); } catch(SQLException e) {

throw new RemoteException("Could not findAccountsLargerThan: " + balance, e);

}

}

public void ejbLoad() throws RemoteException { // Получение имени из primary key name = ((AccountPK) context.getPrimaryKey()).name; try {

Connection connection = getConnection (); PreparedStatement statement = connection.prepareStatement

("SELECT balance FROM Savings_Accounts WHERE name = ?"); statement.setString(1, name); ResultSet resultSet = statement.executeQuery(); if(!resultSet.next()) {

throw new RemoteException("Account not found: " + name);

}

_balance = resultSet.getFloat(1); statement.close(); connection.close(); } catch(SQLException e) {

throw new RemoteException("Could not load: " + name, e);

}

}

public void ejbStoref) throws RemoteException { try {

Connection connection = getConnection(); PreparedStatement statement = connection.prepareStatement

("UPDATE Savings_Accounts SET balance = ? WHERE name = ?"); statement.setFloat(1, _balance); statement.setString(2, name); statement.executeUpdate(); statement.close();

connection.close(); } catch(SQLException e) {

throw new RemoteException("Could not store: " + name, e);

}

}

private Connection getConnection() throws SQLException { Properties properties = context.getEnvironment(); String url = properties.getProperty("db.url"); String username = properties.getProperty("db.username"); String password = properties.getProperty("db.password"); if(username != null) {

return DriverManager.getConnection (url, username, password); } else {

return DriverManager.getConnection(url);

}

}

public String toString() {

return "SavingsAccount[name=" + name + ",balance=" + balance +"]";

}

}

Дескриптор Поставки Entity-Компонента

Дескриптор Поставки из примера bank содержит информацию о трех типах Компонентов – Session-Компоненте Teller, Entity-Компоненте с СМР CheckingAccount и Entity-Компоненте с BMP SavingsAccount. Подробная информация о поставке Компонентов приведена в главе 9 "Поставка Компонентов EJB". В этом разделе рассматривается то, что относится к Entity-Компонентам.

Информация в Дескрипторе Поставки (обычно используется термин свойства, properties) используется для описания интерфейсов Компонента, атрибутов транзакций и пр. точно так же, как и для Session-Компонентов, но для Entity-Компонентов появляются дополнительные свойства. Пример Кода 7.8 на стр. 7-25 демонстрирует использование типичных тегов, применяемых для описания Entity- Компонентов с СМР. Если вы сравните два Дескриптора для Entity- Компонентов с разными системами управления состоянием, то увидите, что при использовании СМР Дескриптор получается более сложным.

Дескриптор Поставки для Entity-Компонента содержит следующую информацию:

• Тип Компонента, который определяется как <entity>. Обратите внимание, что первый тег в секции <enterprise-bean> в Примерах Кода 7.8 и 7.9 говорит о том, что используется Entity-Компонент.

•          Имена сопоставленных с Компонентом интерфейсов (home и remote) и имя класса реализации. Для их задания используются теги <home>,

<remove> и <ejb-class>, соответственно.

•          JNDI-имена, под которыми Компонент зарегистрирован в службе имен и которые использует клиент при установке связи с Компонентом.

•          Атрибуты и уровни изоляции транзакций Компонента. Обычно эта информация содержится в разделе <assembly-descriptor> Дескриптора Поставки.

•          Имя primary key-класса Компонента. В нашем примере, это класс AccountPK, и он описан в теге <prim-key-class>.

•          Тип сохранения состояния Компонента. Entity-Компонент может использовать как BMP, так и СМР. Компонент CheckingAccount использует СМР, поэтому в теге <presistence-type> Дескриптора Поставки содержится значение Container.

•          Признак, является ли Компонент реентерабельным или нет. Ни CheckingAccount, ни SavingsAccount не являются реентерабельными, поэтому для обоих в теге <reentrant> задано значение False.

•          Поля, сохранением состояния которых управляет Контейнер, если используется СМР. Компонент с BMP (SavingsAccount в нашем примере) не должен указывать эту информацию. Entity-Компонент с СМР обязан указать имена полей, сохранением состояния которых должен управлять Контейнер. Для этого используется комбинация тегов <cmp-field> и <field-name>. Первый их этих тегов, <cmp-field>, говорит о том, что Контейнер управляет состоянием данного поля. Внутри этого тега, тег <field-name> определяет имя поля. Например, Дескриптор Поставки для Компонента CheckingAccount определяет, что Контейнер управляет состоянием поля balance, с помощью следущей строки:

<cmp-fieldxfield-name>balance</field-name></cmp-field>

•          Информация, относящаяся к сопоставленной с Компонентом базой данных. В частности, эта информация идентифицирует базу данных (включая информацию о пользователе) и определяет таблицу, в которой хранятся Entity-объекты. Для этого используется специфическая для реализации фирмы Inprise секция <datasource>. Более подробно это обсуждается в главе 9 "Поставка Компонентов EJB" в разделе "Источники данных" на стр. 9-9

•          Дополнительная информация оь управлемых Контейнером полях, которая необходима для генерации Контейнером методов поиска (только в случае использования СМР). Для этого также используется специфическая для Inprise-реализации секция <datasource>. См. раздел "Источники данных" на стр. 9-9 в главе 9 "Поставка Компонентов EJB".

Приведенный ниже Пример Кода 7.8 показывает основные части

Дескриптора Поставки для Entity-Компонента с BMP. Поскольку

Компонент сам осуществляет обмен данных между БД и своими полями (как при чтении, так и при записи), а не полагается при этом на Контейнер, то Дескриптор Поставки не содержит перечисления таких полей. Кроме того, в Дескрипторе не содержится никакой информации для генерации методов поиска, так как в классе Компонента содержатся их готовые реализации.

Пример Кода 7.8 Дескриптор Поставки для Entity-Компонента с BMP

<enterprise-beans> <entity>

<description>This Entity bean is an example of Bean Managed

Persistence</description> <ejb-name>savings</ejb-name> <home>AccountHome</home> <remote>Account</remote> <ejb-class>SavingsAccount</ejb-class> <persistence-type>Bean</persistence-type> <prim-key-class>AccountPK</prim-key-class> <reentrant>False</reentrant> </entity>

</enterprise beans> <assembly-descriptor>

<container-transaction> <method>

<ejb-name>savings</ejb-name> <method-name>>t</method-name> </method>

<trans-attribute>Required</trans-attribute> </container-transaction> </assembly-descriptor> Пример Кода 7.9 показывает основные части Дескриптора Поставки для Entity-Компонента с СМР. Поскольку Компонент полагается на Контейнер при чтении данных из БД и сохранении внесенных изменений, в Дескрипторе Поставки указаны соответствующие имена полей.

Пример Кода 7.9 Дескриптор Поставки для Entity-Компонента с СМР

<enterprise-beans> <entity>

<description>

This Entity bean is an example of Container Managed Persistence </description> <ejb-name>checking</ejb-name> <home>AccountHome</home>

<remote>Account</remote>

<ejb-class>CheckingAccount</ejb-class>

<persistence-type>Container</persistence-type>

<prim-key-class>AccountPK</prim-key-class>

<reentrant>False</reentrant>

<cmp-field>

<field-name>name</field-name> </cmp-field> <cmp-field>

<field-name>balance</field-name> </cmp-field> </entity>

</enterprise beans> <assembly-descriptor>

<container-transaction> <method>

<ejb-name>checking</ejb-name> <method-name>*</method-name> </method>

<trans-attribute>Required</trans-attribute> </container-transaction> </assembly-descriptor>

Использование секции datasource

Entity-Компонент с CMP для задания собственной конфигурации использует сецифическую для Inprise-реализации Дескриптора Поставки секцию datasource. Эта секция нужна для организации связи с базой данных и говорит программе, как создавать экземпляры источников данных.

Вы можете указать всю необходимую информацию обазе данных – URL, имя пользователя, пароль и т.д. – с помощью секции datasource и элемента <datasource>.

Подробное описание этой секции Дескриптора Поставки приведено в разделе "Источники данных" на стр. 9-9 в главе 9 "Поставка Компонентов EJB".

Поставка Компонентов для примера bank

Поскольку в примере для хранения состояния объектов используется внешняя база данных, то поставка Компонентов для него требует выполнения некоторых дополнительных действий. Подробное описание процесса создания таблиц и внесения необходимых изменений в Дескриптор Поставки находится в разделе "Пример использования Oracle" на стр. 7-27.

Вот шаги, которые необходимо сделать в процессе поставки: 1 Определение базы данных, с которой будет происходить

взаимодействие с помощью JDBC.

1                Создание нового пользователя и предоставление ему прав для создания новых таблиц.

2                Создание в БД двух новых таблиц со следующей структурой полей: строковое поле переменной длины (не менее 5 символов) и поле типа float с точностью не менее 3 знака после запятой. Одна из них должна называться Savings_Accounts, вторая – Checking_Accounts.

3                Внесение изменений в Дескриптор Поставки, хранящийся в файле EJB-JAR.XML в подкаталоге meta-inf в каталоге, где находится пример bank. Необходимо указать URL JDBC-драйвера и базы данных, а также идентификатор и пароль пользователя.

4                Добавление к переменной среды classpath пути к JDBC-драйверу.

5                Запуск Контейнера EJB. Для этого можно использовать следующую командную строку:

prompt% vbj com.inprise.ejb.Container test beans.jar -jts -jns

6                Запуск приложения-клиента с помощью команды

prompt% vbj BankClient Вы должны увидеть следующие результаты:

Peter’s balance: 200

Paul’s balance: 100

Taking from Peter and giving to Paul

Peter’s balance: 200

Paul’s balance: 100

Использование режима отладки

Если у вас при запуске возникли проблемы, используйте стандартный отладочный режим (EJBDebug). Для запуска сервера с включенным режимом отладки, введите следующую команду:

prompt% vbj -DEJBDebug com.inprise.ejb.Container test beans.jar -jts – jns

Пример использования Oracle

В нашем примере поставка Компонента осуществляется для работы с Oracle 7.3.3, запущенном на компьютере "gemini", номер порта 1525, алиас "GEORA733", пользователь "scott" с паролем "tiger".

Создание таблиц:

prompt% sqlplus scott/tiger@GEORA73

create table Savings Accounts (name varchar(lO), balance float (10)); create table Checking Accounts (name varchar(lO), balance float(10));

exit; prompt%

Все необходимые команды должны быть заданы между системными символами prompt. В нашем случае выполняется создание двух таблиц, каждая из которых содержит два поля – строковое длиной до 10 символов и поле типа float.

Применительно к этому примеру, для обоих Компонентов – и SavingsAccount, и CheckingAccount – вы могли бы использовать секцию <datasource> следующего вида:

<deployment-descriptor> <datasource>

<res-ref-name>jdbc/SavingsDataSource</res-ref-name> <url>j dbc:oracle:thin:@gemini:1525:GEORA733</url> <username>scott</username> <password>tiger</password>

<driver-class-name>oracle.j dbc.driver.OracleDriver</driver- class-name> </datasource> </deployment-descriptor>

Возможно, вам потребуется изменить параметры в секции datasource, например, имя хоста, номер порта или JDBC URL.

Источник: Руководство программиста Enterprise JavaBeans

По теме:

  • Комментарии