Доброе время суток читатели. Моя предыдущая статья была небольшим интро в ADF. И так как по результатам опроса я вижу, что тема оказалась интересна, то я продолжаю писать об ADF.
Теперь после небольшого рассказа о данном фреймворке, можно «ринуться в бой» и рассмотреть фичи ADF более конкретно. Данная статья будет об ADF Business Components. О том как работать с ними декларативно и программно.
Общие понятия
ADF Business Components (далее BC) – это часть фреймворка для работы с БД, предоставляющая визуальную и декларативную разработку. Конфигурация BC хранится в XML файлах, но при желании можно сгенерировать Java классы и добавить и/или переопределить логику.
BC делятся на 5 основных частей:
- Entity Objects (EO)
- View Objects (VO)
- Associations
- View Links
- Application Module
EO представляет собой таблицу из БД, и, соответственно, экземпляр EO – это строка из таблицы.
Данный компонент инкапсулирует в себе данные, правила валидации и логику персистентности.
EO associations определяют связи между двумя сущностями (таблицами)
VO – компонент, ответственный за чтение данных из data source, а также включает в себя операции по их обновлению.
View Links – определяют связи между VO (по аналогии с associations)
Application Module – это уровень сервиса, предоставляющий работу с бизнес компонентами (а именно с VO и View Links). Также в него можно добавить дополнительные методы и вкладывать другие Application Modules. В конечном счете Application Module используется, как Data Control.
IDE time
Для тестовых БД данных я буду использовать employees таблицу из oracle HR схемы.
После прохождения визарда «Business Components from tables», в результате получим следующий набор файлов.
Здесь Employees – EO, EmployeesView – VO, EmployeesAppModule – Application Module, EmpManagerFkAssoc – ассоциация между работниками и их менеджерами, EmpManagerFkLink – View link для аналогичной связи, но для VO, Business Components Diagram — диаграмма компонентов.
А на панели Data Controls создастся соответствующий data control.
Взглянем на диаграмму.
Можно увидеть, что EmployeesView создан на основе Employees EO. А в ApplicationModule попали два инстанса EmployeesView. Один отвечает за менеджера, а второй за его работников.
Краткий обзор сгенерированных файлов.
Employees
В сгенерированном EO можно посмотреть какие атрибуты в него входят, отредактировать, добавить свои или удалить.
Также можно управлять правилами валидации данных.
EmployeesView
Из атрибутов VO видно, что они берутся из Employees EO.
Можно посмотреть запрос VO, его переменные и критерии (named where clauses)
EmployeesAppModule
Application Module, как мы видели раньше, содержит два экземпляра EmployeesView
EmpManagerFkAssoc
Связь между сущностями.
EmpManagerFkLink
Одинаковые Source и Destination, говорят о том, что View Link построена на базе Associations.
CRUD
Да-да. CRUD будет реализован полностью декларативным путем, мною не будет написано ни одной строчки кода.
Посмотрим какой будет результат.
Вначале будет показана таблица с сотрудниками.
При нажатии на Create employee, произойдет переход на создание нового сотрудника.
При нажатии на Save новый сотрудник добавится в БД, и мы вернемся к таблице.
При нажатии на Update employee, произойдет переход на редактирование информации сотрудника.
После сохранения изменений, снова возврат к таблице.
Ну и наконец при нажатии на Delete employee, сотрудник испарится.
Ходом операций управляет bounded task flow. О том, как работать с task flows, будет отдельная статья. Пока просто посмотрим на диаграмму.
ViewEmployees, createEmployee и updateEmployee это view activities с JSFF, отвечающие за отображение таблицы и форм.
Для отображения сотрудников в таблице, нужно перенести EmployeeView из Data Control на страницу и выбрать в опциях создания – нужный вид таблицы (в моем случае – это read only)
Для вывода форм на страницах createEmployee и updateEmployee нужно сделать аналогичную операцию, но выбрать из категории Form (в моем случае – это ADF Form)
Все остальные activities – это операции над DataControl’ом. Перенесены эти операции также с Data Controls панели.
CreateInsert создает в итераторе новую строку и переводит курсор итератора на нее.
Delete удаляет строку в итераторе, на которой в данный момент находится курсор.
Чтобы действия CreateInsert и Delete вступили в силу, необходимо вызвать операцию Commit, а для того, чтобы откатить изменения – Rollback.
На этом реализация CRUD’а завершена.
CRUD v2
Теперь рассмотрим случай, когда мы не отказываемся полностью от декларативного пути, но операции хотим вызывать в каком-либо managed bean’e.
Для простоты рассмотрим только реализацию вставки, так как остальные выполняются аналогично.
Первым шагом необходимо создать managed bean.
Код bean’a рассмотрим в самом конце.
Я создам новую страницу и перенесу на нее таблицу, чтобы можно было увидеть новые данные, и форму для добавления нового сотрудника.
В input компонентах проставлю связи value на managed bean. И в конце добавлю кнопку, используя в качестве action listener’a метод managed bean'а.
В итоге разметка jspx выглядит следующим образом:
<af:form id="f1">
<af:table value="#{bindings.EmployeesView1.collectionModel}" var="row"
rows="#{bindings.EmployeesView1.rangeSize}"
emptyText="#{bindings.EmployeesView1.viewable ? 'No data to display.' : 'Access Denied.'}"
fetchSize="#{bindings.EmployeesView1.rangeSize}" rowBandingInterval="0" id="t1">
<af:column sortProperty="#{bindings.EmployeesView1.hints.FirstName.name}" sortable="false"
headerText="#{bindings.EmployeesView1.hints.FirstName.label}" id="c1">
<af:outputText value="#{row.FirstName}" id="ot1"/>
</af:column>
<af:column sortProperty="#{bindings.EmployeesView1.hints.LastName.name}" sortable="false"
headerText="#{bindings.EmployeesView1.hints.LastName.label}" id="c2">
<af:outputText value="#{row.LastName}" id="ot2"/>
</af:column>
<af:column sortProperty="#{bindings.EmployeesView1.hints.Email.name}" sortable="false"
headerText="#{bindings.EmployeesView1.hints.Email.label}" id="c3">
<af:outputText value="#{row.Email}" id="ot3"/>
</af:column>
<af:column sortProperty="#{bindings.EmployeesView1.hints.PhoneNumber.name}" sortable="false"
headerText="#{bindings.EmployeesView1.hints.PhoneNumber.label}" id="c4">
<af:outputText value="#{row.PhoneNumber}" id="ot4"/>
</af:column>
<af:column sortProperty="#{bindings.EmployeesView1.hints.JobId.name}" sortable="false"
headerText="#{bindings.EmployeesView1.hints.JobId.label}" id="c5">
<af:outputText value="#{row.JobId}" id="ot5"/>
</af:column>
</af:table>
<af:panelFormLayout id="pfl1">
<af:inputText value="#{backingBeanScope.employeeBean.employeeId}" label="#{bindings.EmployeeId.hints.label}"
required="#{bindings.EmployeeId.hints.mandatory}"
columns="#{bindings.EmployeeId.hints.displayWidth}"
maximumLength="#{bindings.EmployeeId.hints.precision}"
shortDesc="#{bindings.EmployeeId.hints.tooltip}" id="it1">
<f:validator binding="#{bindings.EmployeeId.validator}"/>
<af:convertNumber groupingUsed="false" pattern="#{bindings.EmployeeId.format}"/>
</af:inputText>
<af:inputText value="#{backingBeanScope.employeeBean.firstName}" label="#{bindings.FirstName.hints.label}"
required="#{bindings.FirstName.hints.mandatory}"
columns="#{bindings.FirstName.hints.displayWidth}"
maximumLength="#{bindings.FirstName.hints.precision}"
shortDesc="#{bindings.FirstName.hints.tooltip}" id="it2">
<f:validator binding="#{bindings.FirstName.validator}"/>
</af:inputText>
<af:inputText value="#{backingBeanScope.employeeBean.lastName}" label="#{bindings.LastName.hints.label}"
required="#{bindings.LastName.hints.mandatory}"
columns="#{bindings.LastName.hints.displayWidth}"
maximumLength="#{bindings.LastName.hints.precision}"
shortDesc="#{bindings.LastName.hints.tooltip}" id="it3">
<f:validator binding="#{bindings.LastName.validator}"/>
</af:inputText>
<af:inputText value="#{backingBeanScope.employeeBean.email}" label="#{bindings.Email.hints.label}"
required="#{bindings.Email.hints.mandatory}"
columns="#{bindings.Email.hints.displayWidth}"
maximumLength="#{bindings.Email.hints.precision}"
shortDesc="#{bindings.Email.hints.tooltip}" id="it4">
<f:validator binding="#{bindings.Email.validator}"/>
</af:inputText>
<af:inputText value="#{backingBeanScope.employeeBean.phoneNumber}" label="#{bindings.PhoneNumber.hints.label}"
required="#{bindings.PhoneNumber.hints.mandatory}"
columns="#{bindings.PhoneNumber.hints.displayWidth}"
maximumLength="#{bindings.PhoneNumber.hints.precision}"
shortDesc="#{bindings.PhoneNumber.hints.tooltip}" id="it5">
<f:validator binding="#{bindings.PhoneNumber.validator}"/>
</af:inputText>
<af:inputDate value="#{backingBeanScope.employeeBean.hireDate}" label="#{bindings.HireDate.hints.label}"
required="#{bindings.HireDate.hints.mandatory}"
columns="#{bindings.HireDate.hints.displayWidth}"
shortDesc="#{bindings.HireDate.hints.tooltip}" id="id1">
<f:validator binding="#{bindings.HireDate.validator}"/>
<af:convertDateTime pattern="#{bindings.HireDate.format}"/>
</af:inputDate>
<af:inputText value="#{backingBeanScope.employeeBean.jobId}" label="#{bindings.JobId.hints.label}"
required="#{bindings.JobId.hints.mandatory}"
columns="#{bindings.JobId.hints.displayWidth}"
maximumLength="#{bindings.JobId.hints.precision}"
shortDesc="#{bindings.JobId.hints.tooltip}" id="it6">
<f:validator binding="#{bindings.JobId.validator}"/>
</af:inputText>
<af:commandButton text="Create Employee" id="cb1"
actionListener="#{backingBeanScope.employeeBean.createEmployee}"/>
</af:panelFormLayout>
</af:form>
А описание страницы будет выглядеть так.
Код managed bean’a (get’ры и set’ры опустим).
Поля:
private int employeeId;
private String firstName;
private String lastName;
private String email;
private String phoneNumber;
private Timestamp hireDate;
private String jobId;
private BindingContainer bindings;
Метод вставки, использующийся кнопкой на странице:
public void createEmployee(ActionEvent actionEvent) {
// Получаем binding контейнер
BindingContainer bindings = getBindings();
// Выполняем операцию создания новой строки
OperationBinding createOperation =
bindings.getOperationBinding("CreateInsert");
createOperation.execute();
// Забиваем атрибуты данными
AttributeBinding employeeId =
(AttributeBinding)bindings.getControlBinding("EmployeeId");
employeeId.setInputValue(this.employeeId);
AttributeBinding firstName =
(AttributeBinding)bindings.getControlBinding("FirstName");
firstName.setInputValue(this.firstName);
AttributeBinding lastName =
(AttributeBinding)bindings.getControlBinding("LastName");
lastName.setInputValue(this.lastName);
AttributeBinding phoneNumber =
(AttributeBinding)bindings.getControlBinding("PhoneNumber");
phoneNumber.setInputValue(this.phoneNumber);
AttributeBinding email =
(AttributeBinding)bindings.getControlBinding("Email");
email.setInputValue(this.email);
AttributeBinding hireDate =
(AttributeBinding)bindings.getControlBinding("HireDate");
hireDate.setInputValue(this.hireDate);
AttributeBinding jobId =
(AttributeBinding)bindings.getControlBinding("JobId");
jobId.setInputValue(this.jobId);
// Коммитим и тем самым сохраняем новую строку
OperationBinding commitOperation =
bindings.getOperationBinding("Commit");
commitOperation.execute();
}
Проверим работу managed bean’a, убедившись, что в таблицу попала новая запись.
CRUD v3
И последний пример, в котором будет только использование кода.
Для начала нужно подготовить классы у Business Components.
Для этого откроем EmployeeView перейдем на вкладку Java и сгенерируем следующие классы.
EmployeesViewImpl необходим для работы с запросами, а EmployeesViewRowImpl будет представлять из себя строку с атрибутами.
Таким же путем сгенерируем класс для Application Module.
Осталось написать новый метод для добавления строки в БД в EmployeesAppModuleImpl, создать TO класс для передачи данных в этот метод, и вызвать данный метод в managed bean’e.
Метод в Application Module:
public void createEmployee(EmployeeInfo employeeInfo) {
// Получаем ViewObject
EmployeesViewImpl employeeView = getEmployeesView1();
// Готовим новую строку.
EmployeesViewRowImpl employee = (EmployeesViewRowImpl)employeeView.createRow();
employee.setEmployeeId(employeeInfo.getEmployeeId());
employee.setEmail(employeeInfo.getEmail());
employee.setPhoneNumber(employeeInfo.getPhoneNumber());
employee.setFirstName(employeeInfo.getFirstName());
employee.setLastName(employeeInfo.getLastName());
employee.setHireDate(new Timestamp(employeeInfo.getHireDate()));
employee.setJobId(employeeInfo.getJobId());
// Производим операцию вставки.
employeeView.insertRow(employee);
// Коммитим
getDBTransaction().commit();
}
Новый метод bean’a:
public void createEmployee2(ActionEvent actionEvent) {
// Получаем application module
String applicationModuleClass = "com.matim.forhabr.model.EmplyeesAppModuleImpl";
String config = "EmplyeesAppModuleLocal";
EmplyeesAppModuleImpl appModule = (EmplyeesAppModuleImpl)
Configuration.createRootApplicationModule(applicationModuleClass, config);
// Забиваем данными TO
EmployeeInfo employeeInfo = new EmployeeInfo();
employeeInfo.setEmail(this.email);
employeeInfo.setEmployeeId(this.employeeId);
employeeInfo.setFirstName(this.firstName);
employeeInfo.setHireDate(this.hireDate);
employeeInfo.setJobId(this.jobId);
employeeInfo.setLastName(this.lastName);
employeeInfo.setPhoneNumber(this.phoneNumber);
// Вызываем метод по созданию нового сотрудника
appModule.createEmployee(employeeInfo);
// Освобождаем ресурсы
Configuration.releaseRootApplicationModule(appModule, false);
// Так как вставка идет на стороне AppModule,
// то только для целей, обновления таблицы
// выполним операцию Execute
BindingContainer bindings = getBindings();
OperationBinding executeIterator =
bindings.getOperationBinding("Execute");
executeIterator.execute();
}
Проверяем работу:
На этом сегодня все. Много всего еще осталось за бортом по этой теме, поэтому оставлю ссылку на дополнительную информацию здесь.
P.S. Следующая статья будет о task flows.
Автор: matim