Статьи

Разработка утилиты экспорта данных с PrimeFaces

Моя дневная работа связана с интенсивным использованием данных. Мы используем реляционные базы данных для хранения всего, потому что мы полагаемся на управление данными на уровне предприятия. Иногда полезно иметь возможность извлекать данные в простой формат, такой как электронная таблица, чтобы мы могли манипулировать ими по мере необходимости. В этом посте описаны шаги, которые я предпринял для создания эффективной и простой в использовании утилиты экспорта данных на основе JSF с использованием PrimeFaces 5.0. Утилита экспорта создает электронную таблицу, включая заголовки столбцов. Пользователь имеет возможность выбрать, какие поля базы данных экспортировать, и в каком порядке они должны быть экспортированы.

Мы хотим, чтобы у нас был чистый, интуитивно понятный пользовательский интерфейс. По этой причине я решил не отображать данные на экране. Скорее, пользовательский интерфейс содержит компонент PrimeFaces PickList, в котором перечислены различные поля данных на выбор, а также кнопка для экспорта. Давайте начнем с настройки инфраструктуры базы данных, чтобы сделать эту утилиту экспорта возможной.

Для этого поста я усовершенствовал приложение AcmePools, которое было разработано в моей статье, опубликованной в OTN под названием PrimeFaces in Enterprise . Утилита экспорта позволяет экспортировать данные клиентов в электронную таблицу. Данные о клиентах включены в пример базы данных, которая установлена ​​в Apache Derby NetBeans, или вы можете использовать сценарий SQL для этого поста. Чтобы продолжить создание этой утилиты экспорта, загрузите или создайте проект AcmePools в своей среде.

Утилита экспорта данных состоит из двух частей, первая из которых представляет собой компонент PrimeFaces PickList, с помощью которого пользователь выбирает поля для экспорта, а вторая — кнопку экспорта, которая извлекает содержимое выбранного поля в электронную таблицу. Конечный результат будет похож на пользовательский интерфейс, который выглядит как на рисунке 1.

Рисунок 1: Утилита экспорта данных

Рисунок 1: Утилита экспорта данных

Разработка компонента PickList

Для начала создайте инфраструктуру данных для поддержки компонента PickList. Он состоит из одной таблицы базы данных для хранения имен столбцов и меток для данных сущностей, которые вы хотите экспортировать, и, необязательно, последовательности базы данных для заполнения первичного ключа для этой таблицы. В этом случае таблица базы данных называется COLUMN_MODEL, и мы заполняем таблицу именами полей сущностей, которые соответствуют именам столбцов базы данных для таблицы базы данных CUSTOMER.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
-- Add support for data export
create table column_model(
id                  int primary key,
column_name         varchar(30),
column_label        varchar(150));
-- Optional sequence for primary key generation
create sequence column_model_s
start with 1
increment by 1;
-- Load with field (database column) names
insert into column_model values(
1,
'addressline1',
'Address Line 1');
 
insert into column_model values(
2,
'addressline2',
'Address Line 2');
 
insert into column_model values(
3,
'city',
'City');
 
insert into column_model values(
4,
'creditLimit',
'Credit Limit');
 
insert into column_model values(
5,
'customerId',
'Customer Id');
 
insert into column_model values(
6,
'discountCode',
'Discount Code');
 
insert into column_model values(
7,
'email',
'Email');
 
insert into column_model values(
8,
'fax',
'Fax');
 
insert into column_model values(
9,
'name',
'Name');
 
insert into column_model values(
10,
'phone',
'Phone');
 
insert into column_model values(
11,
'state',
'State');
 
insert into column_model values(
12,
'zip',
'Zip');

Затем создайте класс сущностей, который можно использовать для доступа к данным столбца из компонента. Если вы используете IDE, например NetBeans, это можно сделать очень просто с помощью мастера. При использовании NetBeans щелкните правой кнопкой мыши пакет com.acme.acmepools.entity и выберите «Создать» -> «Классы сущностей из базы данных», а затем выберите источник данных для нашей базы данных примера. Когда список таблиц заполнится, выберите таблицу COLUMN_MODEL, как показано на рисунке 2. Наконец, выберите «Далее» и «Готово», чтобы создать класс сущности.

Рисунок 2. Новые классы сущностей NetBeans из базы данных

Рисунок 2. Новые классы сущностей NetBeans из базы данных

После завершения класс сущности под названием ColumnModel должен выглядеть следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
package com.acme.acmepools.entity;
 
import java.io.Serializable;
import java.math.BigDecimal;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import javax.xml.bind.annotation.XmlRootElement;
 
/**
 *
 * @author Juneau
 */
@Entity
@Table(name = "COLUMN_MODEL")
@XmlRootElement
@NamedQueries({
    @NamedQuery(name = "ColumnModel.findAll", query = "SELECT c FROM ColumnModel c"),
    @NamedQuery(name = "ColumnModel.findById", query = "SELECT c FROM ColumnModel c WHERE c.id = :id"),
    @NamedQuery(name = "ColumnModel.findByColumnName", query = "SELECT c FROM ColumnModel c WHERE c.columnName = :columnName"),
    @NamedQuery(name = "ColumnModel.findByColumnLabel", query = "SELECT c FROM ColumnModel c WHERE c.columnLabel = :columnLabel")})
public class ColumnModel implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @Basic(optional = false)
    @NotNull
    @Column(name = "ID")
    private BigDecimal id;
    @Size(max = 30)
    @Column(name = "COLUMN_NAME")
    private String columnName;
    @Size(max = 150)
    @Column(name = "COLUMN_LABEL")
    private String columnLabel;
 
    public ColumnModel() {
    }
 
    public ColumnModel(BigDecimal id) {
        this.id = id;
    }
 
    public BigDecimal getId() {
        return id;
    }
 
    public void setId(BigDecimal id) {
        this.id = id;
    }
 
    public String getColumnName() {
        return columnName;
    }
 
    public void setColumnName(String columnName) {
        this.columnName = columnName;
    }
 
    public String getColumnLabel() {
        return columnLabel;
    }
 
    public void setColumnLabel(String columnLabel) {
        this.columnLabel = columnLabel;
    }
 
    @Override
    public int hashCode() {
        int hash = 0;
        hash += (id != null ? id.hashCode() : 0);
        return hash;
    }
 
    @Override
    public boolean equals(Object object) {
        // TODO: Warning - this method won't work in the case the id fields are not set
        if (!(object instanceof ColumnModel)) {
            return false;
        }
        ColumnModel other = (ColumnModel) object;
        if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
            return false;
        }
        return true;
    }
 
    @Override
    public String toString() {
        return "com.acme.acmepools.entity.ColumnModel[ id=" + id + " ]";
    }
     
}

Затем создайте сессионный компонент EJB для вновь созданного класса сущностей, чтобы компонент мог запрашивать данные столбца. Вы также можете использовать свою IDE для этого, если хотите. При использовании NetBeans щелкните правой кнопкой мыши пакет com.acme.acmepools.session и выберите «Создать» -> «Сессионные компоненты для классов сущностей». Как только диалоговое окно откроется, выберите класс сущности «com.acme.acmepools.entity.ColumnModel» из списка слева и нажмите «Готово» (рисунок 3).

Рисунок 3: Диалоговое окно сессионных компонентов NetBeans для классов сущностей

Рисунок 3: Диалоговое окно сессионных компонентов NetBeans для классов сущностей

После того, как сессионный компонент создан, добавьте метод с именем findId (), который можно использовать для возврата значения идентификатора столбца на основе указанного имени столбца. Полные исходники для ColumnModelFacade должны выглядеть следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.acme.acmepools.session;
 
import com.acme.acmepools.entity.ColumnModel;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
 
/**
 *
 * @author Juneau
 */
@Stateless
public class ColumnModelFacade extends AbstractFacade {
    @PersistenceContext(unitName = "com.acme_AcmePools_war_AcmePools-1.0-SNAPSHOTPU")
    private EntityManager em;
 
    @Override
    protected EntityManager getEntityManager() {
        return em;
    }
 
    public ColumnModelFacade() {
        super(ColumnModel.class);
    }
 
    public ColumnModel findId(String columnName){
        return (ColumnModel) em.createQuery("select object(o) from ColumnModel as o " +
                              "where o.columnName = :columnName")
                              .setParameter("columnName", columnName)
                              .getSingleResult();
    }
     
}

Затем создайте несколько вспомогательных классов, которые будут использоваться для загрузки и управления данными в компоненте PickList. Первый класс называется ColumnBean и используется для хранения данных сущностей, которые впоследствии передаются в PickList для использования. Код для ColumnBean — это простой POJO: <

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package com.acme.acmepools.bean;
 
import java.math.BigDecimal;
 
/**
 *
 * @author juneau
 */
public class ColumnBean {
 
    private BigDecimal id;
    private String columnName;
    private String columnLabel;
 
    public ColumnBean(BigDecimal id, String columnName, String columnLabel){
        this.id = id;
        this.columnName = columnName;
        this.columnLabel = columnLabel;
    }
 
    /**
     * @return the id
     */
    public BigDecimal getId() {
        return id;
    }
 
    /**
     * @param id the id to set
     */
    public void setId(BigDecimal id) {
        this.id = id;
    }
 
    /**
     * @return the columnName
     */
    public String getColumnName() {
        return columnName;
    }
 
    /**
     * @param columnName the columnName to set
     */
    public void setColumnName(String columnName) {
        this.columnName = columnName;
    }
 
    /**
     * @return the columnLabel
     */
    public String getColumnLabel() {
        return columnLabel;
    }
 
    /**
     * @param columnLabel the columnLabel to set
     */
    public void setColumnLabel(String columnLabel) {
        this.columnLabel = columnLabel;
    }
 
}

Компонент PickList должен использовать PrimeFaces DualListModel для доступа и обновления данных. Следовательно, мы должны реализовать класс, который можно использовать для приведения данных сущностей в наш POJO ColumnBean, а затем сохранить их в DualListModel, чтобы они могли использоваться компонентом PickList. В следующем классе, названном PickListBean, конструктор принимает List <ColumnModel>, который является данными объекта в качестве аргумента, выполняет приведение, а затем сохраняет его в коллекции DualListModel <ColumnBean> для использования компонентом.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.acme.acmepools.bean;
 
/**
 *
 * @author juneau
 */
 
import java.util.ArrayList;
import java.util.List;
import com.acme.acmepools.entity.ColumnModel;
 
import org.primefaces.model.DualListModel;
 
public class PickListBean {
 
    private DualListModel<ColumnBean> columns;
 
    private List<ColumnBean> source = null;
    private List<ColumnBean> target = null;
 
 
    public PickListBean(List<ColumnModel> columnModelList) {
        //Columns 
        source = new ArrayList<ColumnBean>();
        target = new ArrayList<ColumnBean>();
    
        for(ColumnModel column:columnModelList){
            ColumnBean bean = new ColumnBean(column.getId(), column.getColumnName(), column.getColumnLabel());
            source.add(bean);
        }
         
 
        columns = new DualListModel<ColumnBean>(source, target);
 
    }
 
    public DualListModel<ColumnBean> getColumns() {
        return columns;
    }
 
    public void setColumns(DualListModel<ColumnBean> columns) {
        this.columns = columns;
    }
 
    
}

Наконец, нам нужно создать класс контроллера для доступа ко всем этим данным. Для этого создайте класс с именем ColumnModelController в пакете com.acme.acmepools.jsf и сделайте его управляемым компонентом CDI, аннотируя его с помощью @Named и @SessionScoped. Сделайте класс реализующим Serializable. Исходный класс контроллера должен выглядеть следующим образом (мы будем обновлять его позже, чтобы включить методы для облегчения экспорта):

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
@Named
@SessionScoped
public class ColumnModelController implements Serializable {
 
    @EJB
    ColumnModelFacade ejbFacade;
 
    private PickListBean pickListBean;
    private List<ColumnModel> columns;
 
    public DualListModel<ColumnBean> getColumns() {
        pickListBean = new PickListBean(ejbFacade.findAll());
 
        return pickListBean.getColumns();
    }
     
    public void setColumns(DualListModel<ColumnBean> columns) {
        pickListBean.setColumns(columns);
    }
}

Как видите, метод getColumns () запрашивает сущность ColumnModel, которая заполняет DualListModel <ColumnBean> через конструктор PickListBean.

Это заботится об инфраструктуре базы данных и бизнес-логике… теперь давайте посмотрим на компонент PrimeFaces, который используется для PickList. Следующая выдержка, взятая из представления WebPages / poolCustomer / CustomerExport.xhtml, содержит разметку для компонента PickList:

1
2
3
4
5
6
<p:panel header="Choose Columns for Export">
                   <p:picklist effect="bounce" itemlabel="#{column.columnLabel}" itemvalue="#{column.columnName}" showsourcecontrols="true" showtargetcontrols="true" value="#{columnModelController.columns}" var="column">
                       <f:facet name="sourceCaption">Columns</f:facet>
                       <f:facet name="targetCaption">Selected</f:facet>
                   </p:picklist>
            </p:panel>

Как видите, PickList использует columnModelController.columns для данных, который затем использует поле columnLabel для отображения имен полей сущностей для экспорта. Заголовки для исходного и целевого окон PickList настраиваются через фасет. Добавление функциональности экспорта Теперь, когда мы разработали функциональный список выбора, нам нужно что-то сделать с выбранными данными. В этом упражнении мы будем использовать компонент PrimeFaces DataExporter для извлечения данных и сохранения их в электронной таблице Excel. В действительности нам нужно включить DataTable в представление, чтобы сначала отобразить данные, а затем мы можем использовать компонент DataExporter для экспорта данных, которые находятся в таблице. Чтобы создать DataTable, который будет использоваться для отображения данных, нам нужно добавить несколько методов в класс ColumnModelController. Эти методы позволят нам динамически обрабатывать DataTable, чтобы мы могли создавать столбцы на основе столбцов, выбранных в PickList. В действительности DataTable будет запрашивать все данные клиента, а затем отображать только те столбцы данных, которые выбраны в PickList. (Мы могли бы изменить этот запрос, добавив фильтр, но это выходит за рамки этого поста). Чтобы загрузить таблицу с данными, мы просто вызываем метод get.tems () com.acme.acmepools.jsf.CustomerController, чтобы вернуть все данные… public List <Customer> getItems () {if (items == null) {items = getFacade (). findAll (); } возврат товаров; }… Теперь давайте добавим необходимые методы в ColumnModelController, чтобы мы могли динамически создавать таблицу. Сначала добавьте метод, который будет вызываться при нажатии кнопки «Экспорт». Этот метод будет отвечать за построение списка выбранных столбцов:

1
2
3
4
5
6
7
public void preProcess(Object document) {
 
        System.out.println("starting preprocess");
 
        updateColumns();
 
    }

Далее, давайте посмотрим на код для updateColumns (), который вызывается методом preProcess ():

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
 
     * Called as preprocessor to export (after clicking Excel icon) to capture
 
     * the table component and call upon createDynamicColumns()
 
     */
 
    public void updateColumns() {
 
        //reset table state
 
        UIComponent table = FacesContext.getCurrentInstance().getViewRoot().findComponent(":customerExportForm:customerTable");
 
        table.setValueExpression("sortBy", null);
 
 
 
        //update columns
 
        createDynamicColumns();
 
    }

Метод updateColumns () связывает UIComponent с таблицей в представлении JSF. Затем он имеет возможность обеспечить сортировку, если он выбран. Впоследствии, давайте теперь посмотрим на метод createDynamicColumns (), который вызывается.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
    private void createDynamicColumns() {
 
        String[] columnKeys = this.getIncludedColumnsByName().split(",");
 
        columns = new ArrayList<>();
 
        for (String columnKey : columnKeys) {
 
            String key = columnKey.trim();
 
            columns.add(new ColumnModel(getColumnLabel(key), key));
 
 
 
        }
 
    }

Метод createDynamicColumns () делает несколько вещей. Во-первых, он захватывает все выбранные столбцы из PickList и сохраняет их в String [] с именем columnKeys. Для этого мы используем вспомогательный метод с именем getIncludedColumnsByName () и разделяем результаты через запятую. Источники для этого метода следующие, и он в основном получает выбранные в данный момент столбцы из PickListBean и добавляет каждый из них в строку, которая затем возвращается вызывающей стороне.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
    public String getIncludedColumnsByName() {
 
        String tempIncludedColString = null;
 
 
 
        System.out.println("Number of included columns:" + pickListBean.getColumns().getTarget().size());
 
        List localSource = pickListBean.getColumns().getTarget();
 
        for (int x = 0; x <= localSource.size() - 1; x++) {
 
            String tempModel = (String) localSource.get(x);
 
            if (tempIncludedColString == null) {
 
                tempIncludedColString = tempModel;
 
            } else {
 
                tempIncludedColString = tempIncludedColString + "," + tempModel;
 
            }
 
        }
 
 
 
        return tempIncludedColString;
 
    }

Затем метод createDynamicColumns () затем использует цикл для анализа каждого из выбранных столбцов в String [] и добавления их в columnList, которое будет использоваться для создания DataTable с соответствующими столбцами.

Теперь давайте посмотрим на разметку, которая используется для создания утилиты DataExport:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
<p:datatable id="customerTable" rendered="false" value="#{customerController.items}" var="item" widgetvar="customerTable">                   
                    <p:columns columnindexvar="colIndex" value="#{columnModelController.dynamicColumns}" var="column">
                        <f:facet name="header">
                            <h:outputtext value="#{column.header}">
                        </h:outputtext></f:facet>
                        <h:outputtext value="#{item[column.property]}">
                    </h:outputtext></p:columns>
                </p:datatable>
                 
 
<hr />
<h:outputtext value="Type of file to export: ">
                <h:commandlink>
 
                    <p:graphicimage value="/faces/resources/images/excel.png">
                    <p:dataexporter filename="customers" id="propertyXlsExport" preprocessor="#{columnModelController.preProcess}" target="customerTable" type="xls">
                </p:dataexporter></p:graphicimage></h:commandlink>
</h:outputtext>

Как вы можете видеть, DataTable настроен как не отображающий, потому что мы действительно не хотим отображать его. Вместо этого мы хотим экспортировать его содержимое с помощью компонента DataExporter. Для динамического создания DataTable столбцы вызывают метод columnModelController.dynamicColumns для возврата списка динамических столбцов. Этот метод выглядит следующим образом:

1
2
3
public List<ColumnModel> getDynamicColumns() {
        return columns;
    }

В служебном компоненте DataExporter метод columnModelController.preProcess назначается атрибуту препроцессора для инициирования динамического списка столбцов. В качестве цели указывается виджет customerTable, который представляет собой DataTable, который мы динамически создали на основе выбранных столбцов. Чтобы экспортировать это в электронную таблицу xls, вы должны добавить зависимость org.apache.poi в POM Maven для проекта следующим образом:

1
2
3
4
5
<dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>3.7</version>
        </dependency>

Вот и все … теперь у вас должна быть полнофункциональная утилита экспорта данных с использованием компонентов PrimeFaces. Полные источники доступны на GitHub по ссылке ниже. Этот код был написан в среде IDE NetBeans 8.0 и развернут в GlassFish 4.0. Я использовал PrimeFaces 5.0 для этого проекта.