Расширение данных сущности JPA во время выполнения - PullRequest
4 голосов
/ 19 мая 2009

Мне нужно разрешить пользователям-клиентам расширять данные, содержащиеся в сущности JPA во время выполнения. Другими словами, мне нужно добавить виртуальный столбец в таблицу сущностей во время выполнения. Этот виртуальный столбец будет применим только к определенным строкам данных , и таких виртуальных столбцов может быть довольно много. Поэтому я не хочу создавать фактический дополнительный столбец в базе данных, а хочу использовать дополнительные объекты, которые представляют эти виртуальные столбцы .

В качестве примера рассмотрим следующую ситуацию. У меня есть объект Company с полем, помеченным Владелец , который содержит ссылку на Владелец Company . Во время выполнения пользователь клиента решает, что все Компании , принадлежащие определенному Владельцу , должны иметь дополнительное поле, помеченное ContactDetails .

Мой предварительный проект использует две дополнительные сущности для достижения этой цели. Первый в основном представляет виртуальный столбец и содержит такую ​​информацию, как имя поля и тип ожидаемого значения. Другой представляет фактические данные и соединяет строку сущности с виртуальным столбцом . Например, первая сущность может содержать данные «ContactDetails», а вторая сущность содержит, скажем, «555-5555».

Это правильный способ сделать это? Есть ли лучшая альтернатива? Кроме того, что было бы самым простым способом автоматически загрузить эти данные при загрузке исходного объекта? Я хочу, чтобы мой вызов DAO возвратил сущность вместе с добавочными .

РЕДАКТИРОВАТЬ: я изменил пример с поля, помеченного Тип , которое может быть Партнер или Заказчик на текущую версию, поскольку это сбивало с толку.

Ответы [ 5 ]

3 голосов
/ 15 июля 2009

Возможно, более простой альтернативой может быть добавление столбца CLOB к каждой компании и сохранение расширений в виде XML. Здесь есть другой набор компромиссов по сравнению с вашим решением, но до тех пор, пока дополнительные данные не должны быть доступны в SQL (без индексов, fkeys и т. Д.), Это, вероятно, будет проще, чем то, что вы делаете сейчас.

Это также означает, что если у вас есть какая-то причудливая логика в отношении дополнительных данных, вам нужно будет реализовать их по-другому. Например, если вам нужен список всех возможных типов расширений, вам придется вести его отдельно. Или, если вам нужны возможности поиска (найдите клиента по номеру телефона), вам потребуется lucene или аналогичное решение.

Я могу уточнить, если вам интересно.

РЕДАКТИРОВАТЬ:

Чтобы включить поиск, вам нужно что-то вроде lucene , которое является отличным механизмом для поиска произвольного текста по произвольным данным. Существует также hibernate-search , который напрямую интегрирует lucene с hibernate с помощью аннотаций и тому подобного - я не использовал его, но слышал о нем много хорошего.

Для извлечения / записи / доступа к данным вы в основном имеете дело с XML, поэтому следует применять любой метод XML. Лучший подход действительно зависит от реального контента и того, как он будет использоваться. Я бы посоветовал обратиться к XPath для доступа к данным и, возможно, посмотреть на определение своего собственного спящего режима usertype , чтобы весь доступ был инкапсулирован в класс, а не просто в String.

0 голосов
/ 18 марта 2010

Использование шаблона EAV - ИМХО плохой выбор из-за проблем с производительностью и проблем с отчетностью (многие объединения) В поисках решения я нашел здесь кое-что еще: http://www.infoq.com/articles/hibernate-custom-fields

0 голосов
/ 09 июня 2009

У меня возникло больше проблем, чем я надеялся, и поэтому я решил не указывать требования для своей первой итерации. В настоящее время я пытаюсь разрешить такие Расширения только для всей сущности Company , другими словами, я отбрасываю все требование Владелец . Таким образом, проблему можно перефразировать как «Как я могу добавить виртуальные столбцы (записи в другом объекте, которые действуют как дополнительный столбец) к объекту во время выполнения?"

Моя текущая реализация выглядит следующим образом (несущественные части отфильтрованы):

@Entity
class Company {
  // The set of Extension definitions, for example "Location"
  @Transient
  public Set<Extension> getExtensions { .. }

  // The actual entry, for example "Atlanta"
  @OneToMany(fetch = FetchType.EAGER)
  @JoinColumn(name = "companyId")
  public Set<ExtensionEntry> getExtensionEntries { .. }
}

@Entity
class Extension {
  public String getLabel() { .. }
  public ValueType getValueType() { .. } // String, Boolean, Date, etc.
}

@Entity
class ExtensionEntry {
  @ManyToOne(fetch = FetchType.EAGER)
  @JoinColumn(name = "extensionId")
  public Extension getExtension() { .. }

  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "companyId", insertable = false, updatable = false)
  public Company getCompany() { .. }

  public String getValueAsString() { .. }
}

Реализация в том виде, в каком она есть, позволяет загрузить сущность Company , и Hibernate гарантирует, что все ее ExtensionEntries также загружены и что я могу получить доступ к Extensions соответствующие этим ExtensionEntries . Другими словами, если бы я хотел, например, отобразить эту дополнительную информацию на веб-странице, я мог бы получить доступ ко всей необходимой информации следующим образом:

Company company = findCompany();
for (ExtensionEntry extensionEntry : company.getExtensionEntries()) {
  String label = extensionEntry.getExtension().getLabel();
  String value = extensionEntry.getValueAsString();
}

Однако есть ряд проблем с этим. Во-первых, при использовании FetchType.EAGER с @OneToMany Hibernate использует внешнее объединение и, как таковое, будет возвращать дубликаты компаний (по одной для каждого ExtensionEntry). Эту проблему можно решить с помощью Criteria.DISTINCT_ROOT_ENTITY, но это, в свою очередь, приведет к ошибкам в моей нумерации страниц и, следовательно, является неприемлемым ответом. Альтернативой является изменение FetchType на LAZY, но это означает, что мне всегда придется «вручную» загружать ExtensionEntries . Насколько я понимаю, если бы, например, я загрузил список 100 компаний , мне пришлось бы зацикливаться и запрашивать каждый из них, генерируя 100 операторов SQL, что не приемлемо для производительности. мудрый.

Другая проблема, с которой я столкнулся, заключается в том, что в идеале я хотел бы загружать все Extensions всякий раз, когда загружается Company . Имея это в виду, я хотел бы, чтобы получатель @Transient с именем getExtensions () возвращал все расширения для любой компании . Проблема здесь заключается в том, что не существует отношения внешнего ключа между Company и Extension , поскольку Extension не применимо ни к одной Company экземпляр, а точнее для всех них. В настоящее время я могу преодолеть это с помощью кода, подобного представленному ниже, но это не будет работать при доступе к ссылочным объектам (если, например, у меня есть объект Employee , который имеет ссылку на Company , Company , которую я получаю через employee.getCompany (), не будет загружен Extensions :

List<Company> companies = findAllCompanies();
List<Extension> extensions = findAllExtensions();
for (Company company : companies) {
  // Extensions are the same for all Companies, but I need them client side
  company.setExtensions(extensions); 
}

Так вот, где я сейчас нахожусь, и я понятия не имею, как поступить, чтобы обойти эти проблемы. Я думаю, что весь мой дизайн может быть ошибочным, но я не уверен, как еще попытаться приблизиться к нему.

Любые идеи и предложения приветствуются!

0 голосов
/ 13 июля 2009

Используйте шаблонный декоратор и скрывайте свою сущность внутри decoratorClass bye

0 голосов
/ 21 мая 2009

Пример с Company, Partner и Customer на самом деле является хорошим приложением для полиморфизма, которое поддерживается посредством наследования с JPA: у вас будет одна из следующих 3 стратегий на выбор: одна таблица, таблица на класс и объединение. Ваше описание больше похоже на объединенную стратегию, но не обязательно.

Вместо этого вы можете рассмотреть только отношение один к одному (или ноль). Тогда вам нужно будет иметь такие отношения для каждого значения вашего виртуального столбца, поскольку его значения представляют разные сущности. Следовательно, у вас будут отношения с сущностью Партнера и другие отношения с сущностью Клиента, и то и другое, или ни одно, не может быть нулевым.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...