Как можно сохранить чистые слои с помощью Hibernate/ORM (или других ORM...)?
То, что я подразумеваю под чистым разделением слоев, заключается в том, чтобы сохранить все Hibernate в DAO.
Как можно сохранить чистые слои с помощью Hibernate/ORM (или других ORM...)?
То, что я подразумеваю под чистым разделением слоев, заключается в том, чтобы сохранить все Hibernate в DAO.
Если под "чистым" вы подразумеваете, что верхние слои не знают о реализации нижних слоев, вы можете обычно применять Скажите, не спрашивайте принцип. Для вашего примера потоковой передачи 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
с использованием необработанного JDBC для доступа к определенному расширению поставщика: с помощью общего подхода DAO вам нужно будет изменить как бизнес, так и уровни DAO. При таком подходе вы просто переопределяете уровень DAO.
Хорошо, вы всегда можете сказать своему слою DAO, что он должен делать, когда захотите. Имея метод, подобный cleanUpDatasourceCache
в вашем DAO-слое, или что-то подобное (или даже набор этих методов для разных объектов), для меня не так уж плохо.
И ваш уровень обслуживания затем сможет вызвать этот метод без каких-либо предположений о том, что сделано DAO под капотом. Конкретная реализация, которая использует прямые вызовы JDBC, ничего не сделает в этом методе.
Обычно необходим слой DAO для переноса логики доступа к данным. Другие времена - это просто EntityManager, что вы хотите использовать для операций CRUD, для этих случаев я бы не использовал DAO, поскольку это добавило бы лишнюю сложность в код.
Как следует использовать EntityManager в хорошо отделенном слое услуг и уровне доступа к данным?
Если вы не хотите связывать свой код с Hibernate, вы можете использовать Hibernate через JPA и не слишком беспокоиться об абстрагировании всего в ваших DAO. Вы с меньшей вероятностью переключаетесь с JPA на что-то другое, вместо замены Hibernate.
мои 2 цента: я думаю, что в большинстве случаев шаблон разделения слоев отлично подходит для начала, но есть точка, в которой мы должны проанализировать каждый конкретный случай приложения и разработать более гибкое решение. что я имею в виду, спросите себя, например:
Ваш DAO должен повторно использоваться в другом контексте, кроме экспортировать данные csv?
имеет смысл иметь другую реализацию того же DAO интерфейс без спящего режима?
если оба ответа не были, возможно, немного взаимосвязь между сохранением и представлением данных в порядке. Мне нравится решение обратного вызова, предложенное выше.
IMHO иногда строгая реализация шаблона имеет более высокую стоимость в удобочитаемости, mantainability и т.д., которые являются теми самыми проблемами, которые мы пытались исправить, приняв шаблон в первую очередь
вы можете добиться разделения слоев путем реализации шаблона DAO и выполнения всех связанных с hibernate/JDBC/JPA элементов в самом Dao
например:
вы можете указать общий интерфейс 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);
}
}
поэтому всякий раз, когда классы сервисов вызывает любой метод на любом Dao без какого-либо предположения о внутренней реализации метода
посмотрите ссылку GenericDao
Спящий режим (либо как SessionManager, либо JPA EntityManager) - это DAO. Шаблон Repository - это, насколько я знаю, лучшее стартовое место. Существует большой image, на DDD Sample Website, который, как я думаю, говорит о том, как вы храните вещи отдельно.
В моем приложении есть интерфейсы, которые являются явными бизнес-действиями или значениями. Бизнес-правила находятся в модели домена, и такие вещи, как Hibernate, живут в инфраструктуре . Сервисы определяются на уровне домена как интерфейсы и реализованы в инфраструктуре в моем случае. Это означает, что для данного объекта домена Foo (совокупный корень в терминологии DDD) я обычно получаю Foo из FooService, а FooService - в FooRepository, что позволяет найти Foo на основе некоторых критериев. Эти критерии выражаются через параметры метода (возможно, сложные типы объектов), которые со стороны реализации, например, в HibernateFooRepository, будут переведены в критерий HQL или Hibernate.
Если вам нужна пакетная обработка, она должна существовать на уровне приложения и использовать службы домена для облегчения этого. StartBatchTransaction/EndBatchTransaction. Hibernate может прослушивать начальные/конечные события, чтобы координировать очистку, загрузку, что угодно.
Однако в конкретном случае сериализации сущностей домена я не вижу ничего плохого в принятии набора критериев и повторении по ним по одному (от корневых сущностей).
Я нахожу, что часто, в стремлении к разлуке, мы часто пытаемся сделать вещи полностью общими. Они не одни в одном - ваше приложение должно что-то делать, и что-то может и должно выражаться довольно явно.
Если вы можете заменить InMemoryFooRepository, где ранее использовался HibernateFooRepository, вы на правильном пути. Естественный поток через единицу и интеграцию, проверяя ваши объекты, поощряет это, когда вы придерживаетесь или, по крайней мере, стараетесь уважать слоирование, изложенное в изображении, которое я связал выше.
У вас есть хорошие ответы здесь, я хотел бы добавить свои мысли об этом (кстати, это то, о чем нужно позаботиться и в нашем коде). Я также хотел бы остановиться на проблеме аннотаций Hibernate/JPA для объектов, которые могут потребоваться для использования вне вашего DAL (т.е. В бизнес-логике или даже отправлять на клиентскую сторону) -
A. Если вы используете шаблон GenericDAO для данного объекта, вы можете обнаружить, что ваш объект аннотируется с помощью Hibernate (или, возможно, JPA-аннотации), например @Таблица, @ManyToOne и т.д. -
это означает, что ваш клиентский код может содержать аннотации Hibernate/JPA, и для его компиляции потребуется соответствующая банка или какая-либо другая поддержка в вашем клиентском коде, например, если вы используйте GWT в качестве своего клиента (который может иметь поддержку аннотаций JPA для получения компиляции сущностей) и совместно использовать сущности между сервером и клиентским кодом, или если вы пишете клиент Java, который выполняет поиск bean, используя InitialContext против сервер приложений Java (в этом случае вам понадобится JAR
B. Еще один подход, который вы можете использовать, - это работа с аннотированным кодом Hibernate/JPA на стороне сервера и публикация веб-служб (скажем, веб-сервиса RESTFul или SOAP). Таким образом, клиент работает с "интерфейсом", который не раскрывает знания на Hibernate/JPA (например, WSDL в случае, если SOAP определяет контракт между клиентом службы и самой службой).
Разбирая архитектуру на сервис-ориентированную, вы получаете всевозможные преимущества, такие как свободное соединение, простоту замены фрагментов кода, и вы можете сосредоточить всю логику DAL в одной службе, которая обслуживает остальные ваши сервисы, и позже самостоятельно заменить DAL, если потребуется другой службой.
C. Вы можете использовать инфраструктуру отображения "объект для объекта", например dozer, чтобы сопоставить объекты классов с аннотациями Hibernate/JPA к тому, что я называть "истинные" POJO - т.е. - java beans без каких-либо аннотаций на них.
D. Наконец, в отношении аннотаций - зачем вообще использовать аннотации? Hibernate использует hbm xml файлы как альтернативу для "магии ORM" - таким образом ваши классы могут оставаться без аннотаций.
E. Один последний момент - я хотел бы предложить вам посмотреть на материал, который мы сделали в Ovirt - вы можете загрузить код git клонировать наше репо. Вы найдете там под движком /backend/manager/modules/bll - проект maven, содержащий нашу логику bll, и под движком /backend/manager/moduled/dal - наш слой DAL (хотя в настоящее время он реализован с помощью Spring -JDBC, и некоторые эксперименты с гибернацией, вы получите несколько хороших идей о том, как использовать их в своем коде. Я хотел бы добавить, что если вы пойдете на аналогичное решение, я предлагаю вам вводить DAO в свой код и не удерживать их в Singletone, как мы сделали с методами getXXXDao (это устаревший код, который мы должны стремиться удалить и перейти к инъекциям).
Я бы порекомендовал вам позволить базе данных обрабатывать экспорт в CSV-операцию, а не строить ее самостоятельно на Java, но это не так эффективно. ORM не следует использовать для этих крупномасштабных пакетных операций, поскольку ORM следует использовать только для обработки транзакционных данных.
Крупномасштабные пакетные операции Java действительно должны выполняться JDBC напрямую с отключенной поддержкой транзакций.
Однако, если вы делаете это регулярно, я рекомендую настроить базу данных отчетов, которая представляет собой задержанную копию базы данных, которая не используется приложением, и использует инструменты репликации, специфичные для базы данных, которые могут поставляться с вашей базой данных.
Ваш архитектор решений должен иметь возможность работать с другими группами, чтобы помочь вам в этом.
Если вы действительно должны делать это в уровне приложения, то использование более надежных вызовов JDBC может быть лучшим вариантом. С помощью raw JDBC вы можете выполнить запрос для сбора данных, которые вам нужны на стороне базы данных, и получить данные по одной строке за один раз, а затем записать в выходной поток.
Чтобы ответить на вопрос о ваших слоях. Хотя мне не нравится использование слоёв, потому что обычно это означает одно. Я предпочел бы использовать слово "компоненты", и у меня есть следующие группы компонентов.
приложения
база данных
наследие
Мне кажется, нам нужно еще раз взглянуть на слои. (Надеюсь, кто-то меня исправит, если я ошибаюсь.)
Итак, для случая создания отчета слои сломаются так.
MyReportGenerator.GenerateReportData()
или подобное будет называтьсяDAOLocator.GetDAO(Entity.class)
; или аналогичные методы типа factory будут использоваться для получения DAO. возвращенные DAO расширят Общий интерфейс DAOНу, чтобы получить четкое разделение беспокойства или вы можете сказать, что разделение чистого слоя вы можете добавить уровень сервиса в ваше приложение, которое находится между вами FrontEnd и DaoLayer.
Вы можете поместить свою бизнес-логику в уровень сервиса и связанные с базой данных на уровне Дао с помощью Hibernate.
Итак, если вам нужно что-то изменить в своей бизнес-логике, вы можете отредактировать свой уровень обслуживания, не изменяя DAO, и если вы хотите изменить уровень Dao, вы можете обойтись без изменения фактической бизнес-логики, то есть уровня обслуживания.