Как сохранить чистое разделение слоев с помощью Hibernate / ORM? - PullRequest
14 голосов
/ 04 ноября 2011

Как сохранить чистые слои с помощью Hibernate / ORM (или других ORM ...)?

То, что я имею в виду под чистым разделением слоев, это, например, сохранение всего материала Hibernate в слое DAO .

Например, при создании большого CSV потока экспорта мы часто должны выполнять некоторые операции Hibernate, такие как evict, чтобы избежать OutOfMemory ... Заполнение выходного потока принадлежит представлению, но evict принадлежит ДАО.

Что я имею в виду, так это то, что мы не должны помещать операции выселения в интерфейс / службу, и мы не должны помещать бизнес-логику в DAO ... Итак, что мы можем делать в таких ситуациях?

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

Так как же вы сохраняете четкое разделение слоев с помощью инструмента ORM, такого как Hibernate?


Edit: то, что мне не нравится ни на работе, так это то, что у нас есть собственный абстрактный DAO, который позволяет сервису предоставлять критерий Hibernate в качестве аргумента. Это практично, но для меня в теории служба, которая называет это DAO, не должна знать критерий. Я имею в виду, что мы не должны каким-либо образом импортировать материалы Hibernate в логику бизнес / представление.


Есть ответ, простой или нет?

Ответы [ 11 ]

6 голосов
/ 23 ноября 2011

Если под «чистым» вы подразумеваете, что верхние уровни не знают о реализациях нижних уровней, вы обычно можете применить Скажи, не спрашивай принцип. Для вашего примера потоковой передачи CSV это будет что-то вроде:

// This is a "global" API (meaning it is visible to all layers). This is ok as
// it is a specification and not an implementation.
public interface FooWriter {
    void write(Foo foo);
}

// DAO layer
public class FooDaoImpl {
    ...
    public void streamBigQueryTo(FooWriter fooWriter, ...) {
        ...
        for (Foo foo: executeQueryThatReturnsLotsOfFoos(...)) {
            fooWriter.write(foo);
            evict(foo);
        }
    } 
    ...
}

// UI layer
public class FooUI {
    ...
    public void dumpCsv(...) {
        ...
        fooBusiness.streamBigQueryTo(new CsvFooWriter(request.getOutputStream()), ...);
        ...
    }
}

// Business layer
public class FooBusinessImpl {
    ...
    public void streamBigQueryTo(FooWriter fooWriter, ...) {
        ...
        if (user.canQueryFoos()) {
            beginTransaction();
            fooDao.streamBigQueryTo(fooWriter, ...);
            auditAccess(...);
            endTransaction();
        }
        ...
    }
}

Таким образом, вы можете свободно обращаться с вашим конкретным ORM. Недостаток этого подхода «обратного вызова»: если ваши слои находятся на разных виртуальных машинах JVM, то это может быть не очень работоспособным (в этом примере вам потребуется возможность сериализации CsvFooWriter).

Об общих DAO: я никогда не чувствовал необходимости, большинство шаблонов доступа к объектам, которые я нашел, достаточно различны, чтобы сделать желательной конкретную реализацию. Но, безусловно, разделение уровней и принуждение бизнес-уровня к созданию критериев Hibernate являются противоречивыми путями. Я бы указывал разные методы запросов на уровне DAO для каждого отдельного запроса, а затем позволял бы реализации DAO получать результаты любым способом, который мог бы выбрать (критерии, язык запросов, необработанный SQL, ...). Так что вместо:

public class FooDaoImpl extends AbstractDao<Foo> {
    ...
    public Collection<Foo> getByCriteria(Criteria criteria) {
        ...
    }
}

public class FooBusinessImpl {
    ...
    public void doSomethingWithFoosBetween(Date from, Date to) {
        ...
        Criteria criteria = ...;

        // Build your criteria to get only foos between from and to

        Collection<Foo> foos = fooDaoImpl.getByCriteria(criteria);
        ...
    }

    public void doSomethingWithActiveFoos() {
        ...
        Criteria criteria = ...;

        // Build your criteria to filter out passive foos

        Collection<Foo> foos = fooDaoImpl.getByCriteria(criteria);
        ...
    }
    ...
}

Я бы сделал:

public class FooDaoImpl {
    ...
    public Collection<Foo> getFoosBetween(Date from ,Date to) {
        // build and execute query according to from and to
    }

    public Collection<Foo> getActiveFoos() {
        // build and execute query to get active foos
    }
}

public class FooBusinessImpl {
    ...
    public void doSomethingWithFoosBetween(Date from, Date to) {
        ...      
        Collection<Foo> foos = fooDaoImpl.getFoosBetween(from, to);
        ...
    }

    public void doSomethingWithActiveFoos() {
        ...
        Collection<Foo> foos = fooDaoImpl.getActiveFoos();
        ...
    }
    ...
}

Хотя кто-то может подумать, что я подталкиваю какую-то бизнес-логику к уровню DAO, мне кажется, это лучший подход: изменить реализацию ORM на альтернативную было бы проще. Представьте, например, что по соображениям производительности вам нужно прочитать Foo s, используя необработанный JDBC, чтобы получить доступ к какому-либо конкретному расширению поставщика: при использовании общего подхода DAO вам потребуется изменить как бизнес-уровень, так и уровень DAO. При таком подходе вы просто переопределяете уровень DAO.

5 голосов
/ 04 ноября 2011

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

И тогда ваш уровень обслуживания сможетэтот метод без каких-либо предположений о том, что делается DAO под капотом.Конкретная реализация, использующая прямые вызовы JDBC, ничего не будет делать в этом методе.

4 голосов
/ 04 ноября 2011

Обычно уровень DAO для обертывания логики доступа к данным необходим.В других случаях это просто EntityManager, который вы хотите использовать для операций CRUD, в этих случаях я бы не использовал DAO, поскольку это добавило бы ненужную сложность к коду.

Как использовать EntityManagerв красиво отделенном слое обслуживания и уровне доступа к данным?

2 голосов
/ 23 ноября 2011

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

  • ожидается, что ваш DAO будет повторно использован в другом контексте, кроме экспорт данных CSV?

  • имеет ли смысл иметь другую реализацию того же DAO
    интерфейс без спящего режима?

если оба ответа были отрицательными, возможно, небольшая связь между постоянством и представлением данных в порядке. Мне нравится решение обратного вызова, предложенное выше.

ИМХО, иногда строгая реализация шаблона имеет более высокую стоимость в удобочитаемости, управляемости и т. Д., И это те самые проблемы, которые мы пытались решить, приняв шаблон в первую очередь

2 голосов
/ 04 ноября 2011

Если вы не хотите связывать свой код с Hibernate, вы можете вместо этого использовать Hibernate через JPA и не слишком беспокоиться об абстрагировании всего в ваших DAO.Вы реже переключаетесь с JPA на что-то другое, чем на замену Hibernate.

1 голос
/ 19 июня 2012

Вы получили здесь несколько хороших ответов, я хотел бы добавить свои мысли по этому поводу (кстати, об этом также следует позаботиться в нашем коде). Я также хотел бы сосредоточиться на проблеме наличия аннотаций Hibernate. / JPA аннотации к сущностям, которые вам, возможно, понадобится использовать за пределами вашего DAL (то есть - по бизнес-логике или даже отправить на сторону клиента) -

О. Если вы используете шаблон GenericDAO для данной сущности, вы можете обнаружить, что ваша сущность аннотирована с помощью Hibernate (или, возможно, аннотации JPA), например @Table, @ManyToOne и т. Д. -
это означает что ваш клиентский код может содержать аннотации Hibernate / JPA, и вам потребуется соответствующий jar для его компиляции или иметь некоторую другую поддержку в вашем клиентском коде, например, если вы используете GWT в качестве клиента (который может иметь поддержку аннотаций JPA) для компиляции сущностей) и совместного использования сущностей между сервером и клиентским кодом, или если вы пишете клиент Java, который выполняет поиск компонента с помощью InitialContext на сервере приложений Java (в этом случае вам потребуется JAR

Б. Другой подход, который вы можете использовать, - это работать с аннотированным кодом Hibernate / JPA на стороне сервера и предоставлять веб-службы (скажем, веб-сервис RESTFul или SOAP) - таким образом, клиент работает с «интерфейсом», который не предоставляет знания. в Hibernate / JPA (например, WSDL в случае SOAP определяет контракт между клиентом службы и самой службой).
Разбивая архитектуру на сервисно-ориентированную, вы получаете все виды преимуществ, таких как слабая связь, простота замены фрагментов кода, и вы можете сосредоточить всю логику DAL в одном сервисе, который обслуживает остальные ваши сервисы, и позже самостоятельно замените DAL, если это необходимо для другой службы.

C. Вы можете использовать структуру отображения «объект-объект», такую ​​как dozer , чтобы отобразить объекты классов с аннотациями Hibernate / JPA на то, что я называю «истинными» POJO - т.е. - Java-бины без каких-либо аннотаций на их.

D. Наконец, в отношении аннотаций - зачем вообще использовать аннотации? Hibernate использует файлы hbm xml как альтернативу «магии ORM» - так ваши классы могут остаться без аннотаций.

E. Последнее замечание - я хотел бы предложить вам взглянуть на то, что мы сделали на Ovirt - вы можете загрузить код с помощью git clone нашего репозитория. Там вы найдете в engine / backend / manager / modules / bll - проект maven, содержащий нашу логику bll, и в engine / backend / manager / moduled / dal - наш уровень DAL (хотя в настоящее время реализован с помощью Spring-JDBC и некоторого спящего режима) эксперименты, вы получите некоторые хорошие идеи о том, как использовать его в своем коде. Я хотел бы добавить, что если вы идете к аналогичному решению, я предлагаю вам внедрить DAO в ваш код, а не держать их в синглтоне, как мы сделали с методами getXXXDao (это устаревший код, который мы должны стремиться удалить и перейти к инъекциям).

1 голос
/ 23 ноября 2011

Hibernate (как SessionManager или JPA EntityManager) является DAO.Шаблон Repository , насколько я видел, является лучшей отправной точкой.* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *1004* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 100 * *.слой имеет интерфейсы, которые являются явными бизнес-действиями или значениями.Бизнес-правила находятся в доменной модели , а такие вещи, как Hibernate, живут в инфраструктуре .Сервисы определяются на уровне домена как интерфейсы и реализуются в инфраструктуре в моем случае.Это означает, что для данного объекта домена Foo (совокупного корня в терминологии DDD) я обычно получаю Foo от FooService, а FooService общается с FooRepository, что позволяет find Фу на основе некоторых критериев.Этот критерий выражается через параметры метода (возможно, сложные типы объектов), которые на стороне реализации, например, в HibernateFooRepository, будут преобразованы в критерий HQL или Hibernate.

Если вам требуется пакетная обработка, она должна существоватьна уровне приложений и использовать доменные службы для облегчения этого.StartBatchTransaction / EndBatchTransaction.Hibernate может прослушивать события начала / окончания, чтобы координировать очистку, загрузку и т. Д.

В конкретном случае сериализации объектов домена, однако, я не вижу ничего плохого в том, чтобы брать набор критериев и повторять их одинвремя от времени (от корневых сущностей).

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

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

1 голос
/ 18 ноября 2011

вы можете добиться разделения слоев, внедрив шаблон DAO и выполнив все связанные с Hibernate / JDBC / JPA вещи в самом Dao

например:

Вы можете указать общий интерфейс Дао как

public interface GenericDao <T, PK extends Serializable> {

/** Persist the newInstance object into database */
PK create(T newInstance);

/** Retrieve an object that was previously persisted to the database using
 *   the indicated id as primary key
 */
T read(PK id);

/** Save changes made to a persistent object.  */
void update(T transientObject);

/** Remove an object from persistent storage in the database */
void delete(T persistentObject);
}

и его реализация как

  public class GenericDaoHibernateImpl <T, PK extends Serializable>
implements GenericDao<T, PK>, FinderExecutor {
private Class<T> type;

public GenericDaoHibernateImpl(Class<T> type) {
    this.type = type;
}

public PK create(T o) {
    return (PK) getSession().save(o);
}

public T read(PK id) {
    return (T) getSession().get(type, id);
}

public void update(T o) {
    getSession().update(o);
}

public void delete(T o) {
    getSession().delete(o);
}

}

поэтому всякий раз, когда классы обслуживания вызывают любой метод на любом Дао без какого-либо предположения о внутренней реализации метода

посмотрите на GenericDao ссылку

0 голосов
/ 23 ноября 2011

Я бы порекомендовал вам позволить базе данных обрабатывать операции экспорта в CSV, а не создавать ее самостоятельно в Java, это не так эффективно. ORM не следует использовать для этих крупномасштабных пакетных операций, поскольку ORM следует использовать только для манипулирования транзакционными данными.

Крупномасштабные пакетные операции Java действительно должны выполняться JDBC напрямую с отключенной поддержкой транзакций.

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

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

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

Чтобы ответить на вопрос о слоях. Хотя я не люблю использовать слои со словами, потому что они обычно подразумевают одно поверх другого. Я бы предпочел использовать слово «компоненты», и у меня есть следующие группы компонентов.

применение

  1. домен - только аннотированные классы JPA, никакой логики постоянства, обычно простой файл JAR, но я рекомендую просто добавить его в пакет в EJB, вместо того, чтобы иметь дело с проблемами пути к классам
  2. контракты - файлы WSDL и XSD, определяющие интерфейс между различными компонентами, будь то веб-службы или просто пользовательский интерфейс.
  3. Сценарии транзакций - EJB-компоненты без сохранения состояния, в которые будут вставлены единицы транзакции и персистентности, которые будут манипулировать объектами домена и сохранять их. Они могут реализовывать интерфейсы, генерируемые контрактами.
  4. UI - отдельный WAR-проект, в который вставлены EJB-компоненты.

база

  1. Диаграмма O / R - это договор, согласованный приложением и группой данных для обеспечения МИНИМАЛЯ, который предоставит база данных. Не обязательно показывать все.
  2. DDL - это реализация схемы O / R на стороне базы данных, которая будет содержать все, но, как правило, никого не должно волновать, потому что это детали реализации.
  3. batch - пакетные операции, такие как экспорт или репликация
  4. отчетность - предоставляет запросы для получения отчетов о стоимости бизнеса из системы.

Наследие

  1. контракты на обмен сообщениями - это контракты, используемые системами обмена сообщениями, такими как JMS или WS-Notification или стандартными веб-сервисами.
  2. их реализация
  3. сценарии преобразования - используются для преобразования одного контракта в другой.
0 голосов
/ 23 ноября 2011

Что ж, чтобы получить чистое разделение задач или сказать чистое разделение слоев, вы можете добавить слой Service в свое приложение, которое находится между вами FrontEnd и DaoLayer.

С помощью Hibernate вы можете поместить свою бизнес-логику в слой Service и связанные с базой данных вещи в слой Dao.

Таким образом, если вам нужно что-то изменить в своей бизнес-логике, вы можете отредактировать уровень обслуживания без изменения DAO, а если вы хотите изменить уровень Dao, вы можете обойтись без изменения фактической бизнес-логики, то есть уровня обслуживания.

...