2015-04-08 3 views
9

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

Одна из вещей, которые я реализовал, является «оберткой» вокруг моих реализаций DAO для целей проверки. Обертка принимает экземпляр моего DAO в качестве параметра конструктора и реализует аналогичный интерфейс, такой как DAO, за исключением типов исключенных типов.

Например:

бизнес-логики Интерфейс

public interface UserBLInt { 

    private void assignRightToUser(int userId, int rightId) throws SomeAppException; 

} 

DAO интерфейс

public interface UserDAOInt { 

    private void assignRightToUser(int userId, int rightId) throws SomeJPAExcption; 

} 

Бизнес Логика Реализация

public class UserBLImpl implements UserBLInt { 

    private UserDAOInt userDAO; 

    public UserBLImpl(UserDAOInt userDAO){ 
     this.userDAO = userDAO; 
    } 

    @Override 
    private void assignRightToUser(int userId, int rightId) throws SomeAppException{ 
     if(!userExists(userId){ 
     //throw an exception 
     } 
     try{ 
     userDAO.assignRightToUser(userId, rightId); 
     } catch(SomeJpAException e){ 
     throw new SomeAppException(some message); 
     } 
    } 

} 

DAO Реализация

public class UserDAOImpl implements UserDAOInt { 
     //...... 
     @Override 
     public void assignRightToUser(int userId, int rightId){ 
     em.getTransaction().begin(); 
     User userToAssignRightTo = em.find(User.class, userId); 
     userToAssignRightTo.getRights().add(em.find(Right.class, rightId)); 
     em.getTransaction().commit(); 
     } 
} 

Это лишь небольшой пример, но мой вопрос, кажется, «лишними» в рамках реализации DAO, чтобы сделать другую проверку, чтобы убедиться, что User не равно нулю, перед добавлением Right, но, как программист, я вижу возможность для нулевого указателя.

Очевидно, я мог бы добавить нулевую проверку после вызова find в диспетчере сущностей и выбросить исключение, если возвращается null, но это целая цель иметь DAO, завернутую в реализацию бизнес-логики, выполнить всю работу по проверке заранее , так что код DAO является чистым и не должен делать много на пути нулевых проверок или много логики вообще. Поскольку у меня есть обертка вокруг DAO, все-таки хорошая идея делать нулевую проверку в DAO? Я знаю, что теоретически объект может быть удален между вызовом бизнес-логики и вызовом dao, это маловероятно, и проверка на нуль кажется дублированной работой. Какова наилучшая практика для такой ситуации?

EDIT:

ли это выглядеть подходящей модификации DAO?

public EntityManager beginTransaction(){ 
    EntityManager entityManager = entityManagerFactory.createEntityManager(); 
    EntityTransaction entityTransaction = entityManager.getTransaction(); 
    entityTransaction.begin(); 
    return entityManager; 
} 

public void rollback(EntityManager entityManager){ 
    entityManager.getTransaction().rollback(); 
    entityManager.close(); 
} 

public void commit(EntityManager entityManager){ 
    entityManager.getTransaction().commit(); 
    entityManager.close(); 
} 

ответ

3

У DAO слишком много логики. Роль DAO заключается не в том, чтобы назначать права пользователю. Это бизнес-логика. Роль DAO заключается в поиске пользователей или поиске прав. Код, который находится в DAO должен быть на службе:

interface UserDAO { 
    User findById(int userId); 
    //... 
} 

interface RightDAO { 
    Right findById(int rightId); 
    // ... 
} 

public class UserBLImpl implements UserBLInt { 

    private UserDAO userDAO; 
    private RightDAO rightDAO; 

    // ... 

    @Override 
    public void assignRightToUser(int userId, int rightId) { 
     User u = userDAO.findById(userId); 
     Right right = rightDAO.findById(rightId); 
     // do some null checks and throw exception 
     user.addRight(right); 
    } 
} 

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

И поэтому структуры, такие как EJB или Spring, разрешают демаркацию транзакций декларативно: вам не нужно явно запускать и совершать транзакции, обрабатывать исключения и исключения отката. Все, что вам нужно сделать, это отметить метод обслуживания как транзакционный, используя аннотацию.

+0

спасибо за ваш ответ, я ценю это – user1154644

+0

я видел EJB использовали и методы их помечено как требующее новую транзакцию. Если это стандартное java-приложение, а не приложение j2ee, было бы нормально вручную обрабатывать откаты? – user1154644

+0

Я бы использовал Spring в этом случае. –

7

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

Кажется, что ваш DAO на самом деле намного больше, чем просто абстрагирование слоя данных. В:

public class UserDAOImpl implements UserDAOInt { 
    ...... 
    @Override 
    public void assignRightToUser(int userId, int rightId){ 
    em.getTransaction().begin(); 
    User userToAssignRightTo = em.find(User.class, userId); 
    userToAssignRightTo.getRights().add(em.find(Right.class, rightId)); 
    em.getTransaction().commit(); 
    } 
} 

Ваш DAO знает бизнес-логику. Он знает, что , назначая право на пользователя, добавляет право на список прав. (Может показаться очевидным, что присвоение права просто добавляет его в список, но представьте, что в будущем это может стать намного сложнее, с побочными эффектами для других пользователей и прав и т. Д.)

So это присвоение не принадлежит DAO. Он должен быть в бизнес-слое. У вашего DAO должно быть только что-то вроде userDAO.save(user), которое бизнес-уровень вызывал бы после того, как это было сделано, установив права и прочее.


Еще одна проблема: Ваша транзакция слишком локализованы. Это почти не транзакция.

Помните, что транзакция - это бизнес-единица, что-то, что вы делаете в атомной («пакетной») деловой работе, а не только что-то, что вы открываете, потому что вы делаете EntityManager.

Я имею в виду, что с точки зрения кода бизнес-уровень должен иметь инициативу открытия транзакции, а не DAO (на самом деле DAO должен иметь «открывать транзакцию» в качестве сервис-метода - который будет называться).

Рассмотрим, то, открывая сделку в бизнес-уровне:

public class UserBLImpl implements UserBLInt { 
    ... 
    @Override 
    private void assignRightToUser(int userId, int rightId) throws SomeAppException{ 
     userDAO.beginTransaction(); // or .beginUnitOfWork(), if you wanna be fancy 

     if(!userExists(userId){ 
     //throw an exception 
     // DON'T FORGET TO ROLLBACK!!! before throwing the exception 
     } 
     try{ 
     userDAO.assignRightToUser(userId, rightId); 
     } catch(SomeJpAException e){ 
     throw new SomeAppException(some message); 
     } 

     userDAO.commit(); 
    } 
} 

Теперь на ваш вопрос: что риск изменения БД между userExists()if и userDAO сохраняющиеся он все еще существует ... но у вас есть варианты:

(1) Заблокировать пользователя до завершения транзакции; или (2) Пусть это так.

1: Если риск того, что пользователь перепутался между этими двумя командами, высок (например, ваша система имеет много одновременных пользователей), и если бы эта проблема возникла, это было бы большим делом, тогда подумайте о блокировке user для вся транзакция; то есть, если я начну работать с ним, никакая другая транзакция не сможет его изменить.

  • Другой (лучше) решение, что если ваша система имеет тонн Os одновременно работающих пользователей является «дизайн от решения проблемы», то есть, переделывают свой дизайн так, что изменяется в бизнес-операции, имеет более строгий (меньше) - объем вашего примера достаточно мал, поэтому этот совет может не иметь особого смысла, но просто подумайте, что ваша деловая транзакция сделала намного больше (а затем сделать его намного меньше, каждый в свою очередь, может быть решением). Это целая тема сама по себе, поэтому я не буду вдаваться в подробности здесь, просто держите свой ум открытым.

2: Еще одна возможность, и вы будете понять это наиболее распространенный подход, если вы имеете дело с БД SQL, который имеет ограничений проверки, таких как UNIQUE, просто чтобы исключение DAO сдуть.Я имею в виду, это будет такая редкая и почти невозможная вещь, которая может произойти, что вы можете справиться с ней, просто принимая ее, и ваша система просто отобразит хорошее сообщение типа «Что-то пошло не так, попробуйте еще раз» - - это просто базовые затраты против взвешивания преимуществ.


Update:

Programatic обработки транзакций может быть сложно (не удивительно, декларативные альтернативы, такие как Spring и EJB/CDI, не так привыкли). Тем не менее, у нас не всегда есть возможность использовать их (возможно, вы адаптируете устаревшую систему, кто знает). Итак, вот предложение: https://gist.github.com/acdcjunior/94363ea5cdbf4cae41c7

+0

спасибо за ваш ответ, я ценю это – user1154644

+0

Я вижу вашу точку зрения о операции saveUser, но с точки зрения диспетчера сущностей какая операция будет вызываться для «обновления» пользователя в базе данных? Будет ли уровень бизнес-логики добавлять роль пользователю, а слой dao просто вызовет слияние? – user1154644

+0

Да; фактически 'saveUser()' будет обновлять или вставлять, DAO должен иметь возможность выяснить, что нужно пользователю. Пример этого [можно найти в приложении Petclinic] (https://github.com/spring-projects/spring-petclinic/blob/master/src/main/java/org/springframework/samples/petclinic/repository/ JPA/JpaPetRepositoryImpl.java # L55). – acdcjunior

Смежные вопросы