Избегать лишних чтений БД в методе getAsObject класса конвертера путем кэширования данных на стороне клиента? - PullRequest
5 голосов
/ 05 февраля 2012

Я показываю список предлагаемых элементов в элементе ввода autocomplete.Для этого мне нужно реализовать converter для преобразования entity<entityName, entityId> в entityName и наоборот.Однако, реализуя это, я понял, что мне нужно прочитать БД более 1 раза, чтобы найти соответствующий entityId для выбранного entityName (пока getAsObject()), мне интересно, почему это не хранится где-то на стороне клиента, так что entityId можно пропустить при выборе entityname.

Можно ли как-нибудь избежать этого дополнительного чтения?

Ответы [ 2 ]

6 голосов
/ 06 февраля 2012

Это действительно "задумано" и, возможно, немного опущено в спецификации JSF.Теоретически вы можете избежать этого, извлекая элементы из аргумента UIComponent и сравнивая их.Это, однако, немного работы.Мой коллега Арджан Тиймс написал блог об этом: Автоматическое преобразование объектов в JSF selectOneMenu & Co.

Вот выдержка из статьи;ниже приведен базовый конвертер, который вам нужно вместо этого расширить:

public abstract class SelectItemsBaseConverter implements Converter {
    @Override
    public Object getAsObject(FacesContext context, UIComponent component, String value) {        
        return SelectItemsUtils.findValueByStringConversion(context, component, value, this);    
    }    
}

Вот класс SelectItemsUtils, который частично скопирован из источника Мохарры:

public final class SelectItemsUtils {

    private SelectItemsUtils() {}

    public static Object findValueByStringConversion(FacesContext context, UIComponent component, String value, Converter converter) {
        return findValueByStringConversion(context, component, new SelectItemsIterator(context, component), value, converter);        
    }

    private static Object findValueByStringConversion(FacesContext context, UIComponent component, Iterator<SelectItem> items, String value, Converter converter) {
        while (items.hasNext()) {
            SelectItem item = items.next();
            if (item instanceof SelectItemGroup) {
                SelectItem subitems[] = ((SelectItemGroup) item).getSelectItems();
                if (!isEmpty(subitems)) {
                    Object object = findValueByStringConversion(context, component, new ArrayIterator(subitems), value, converter);
                    if (object != null) {
                        return object;
                    }
                }
            } else if (!item.isNoSelectionOption() && value.equals(converter.getAsString(context, component, item.getValue()))) {
                return item.getValue();
            }
        }        
        return null;
    }

    public static boolean isEmpty(Object[] array) {
        return array == null || array.length == 0;    
    }

    /**
     * This class is based on Mojarra version
     */
    static class ArrayIterator implements Iterator<SelectItem> {

        public ArrayIterator(SelectItem items[]) {
            this.items = items;
        }

        private SelectItem items[];
        private int index = 0;

        public boolean hasNext() {
            return (index < items.length);
        }

        public SelectItem next() {
            try {
                return (items[index++]);
            }
            catch (IndexOutOfBoundsException e) {
                throw new NoSuchElementException();
            }
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }
    }
}

Вот как выследует использовать его для собственного конвертера, вам нужно только реализовать getAsString() (getAsObject() уже обработан):

@FacesConverter("someEntitySelectItemsConverter")
public class SomeEntitySelectItemsConverter extends SelectItemsBaseConverter {

    @Override
    public String getAsString(FacesContext context, UIComponent component, Object value) {
        return ((SomeEntity) value).getId().toString();
    }
}

Обновление вышеупомянутая концепция закончиласьв служебной библиотеке JSF OmniFaces во вкусе следующих преобразователей:

  • SelectItemsConverter - для <f:selectItem(s)> на основе Object#toString().
  • SelectItemsIndexConverter - для <f:selectItem(s)> на основе индекса предмета.
  • ListConverter - например, <p:autoComplete> на основе Object#toString()
  • ListIndexConverter - например, <p:autoComplete> на основе индекса предмета.
0 голосов
/ 28 июля 2013

Единственный способ сделать так, чтобы вашему конвертеру не требовался доступ к БД, - это сделать конвертер управляемым компонентом, чтобы он мог обращаться к другому компоненту, в котором хранится список предлагаемых значений автозаполнения. компонент.

Примерно так:

@ManagedBean
@RequestScoped
public class EntityConverter implements Converter
{
  @ManagedProperty(value = "#{autoCompleteBean}")
  private AutoCompleteBean autoCompleteBean;

  public void setAutoCompleteBean(AutoCompleteBean autoCompleteBean)
  {
    this.autoCompleteBean = autoCompleteBean;
  }

  @Override
  public Object getAsObject(FacesContext context, UIComponent component,
        String value)
  {
    final List<Entity> entities = autoCompleteBean.getCachedSuggestions();

    for (final Enity entity : entities)
    {
      if (entity.getIdAsString().equals(value))
      {
        return entity;
      }
    }
    throw new IllegalStateException("Entity was not found!");
  }

  @Override
  public String getAsString(FacesContext context, UIComponent component,
        Object value)
  { ... }

На странице jsf убедитесь, что вы ссылаетесь на конвертер как на bean-компонент. то есть:

        <p:autoComplete value="#{autoCompleteBean.selectedEntity}"
          completeMethod="#{autoCompleteBean.getSuggestions}" var="theEntity"
          itemValue="#{theEntity}" itemLabel=#{theEntity.someValue}
          converter="#{entityConverter}">
...