Oracle ADF. Business Components

    Доброе время суток хабравчане. Моя предыдущая статья была небольшим интро в 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.
    Поделиться публикацией
    Похожие публикации
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 4
    • 0
      А что с надёжностью и масштабируемостью у этого гиганта Ынтерпрайз мысли?
      А насколько он гибок?
      • 0
        Вполне надежен, пока во что-нибудь не упрется по ресурсам. Масштабируется довольно легко, без каких либо неожиданных последствий.
        Гибкость — понятие относительное. При желании можно переписать многие внутренние механизмы, причем вполне легально. А можно и полностью отказаться от них в пользу любых других.
      • 0
        Неделя oracle adf на хабре
        • 0
          Спасибо за вашу работу, сам недавно начал заниматься этой темой.

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.