Автоматическое подключение DAO к объекту домена
Я кодирую систему ленты / достижений для веб-сайта, и мне нужно написать логику для каждой ленты в моей системе. Например, вы можете заработать ленту, если будете в числе первых 2000 человек, зарегистрировавшихся на сайте, или после 1000 постов на форуме. Идея очень похожа на значки stackru, правда.
Итак, каждая лента, очевидно, находится в базе данных, но им также нужно немного логики, чтобы определить, когда пользователь заработал ленту.
По тому, как я это кодировал, Ribbon
простой абстрактный класс:
@Entity
@Table(name = "ribbon")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "ribbon_type")
public abstract class Ribbon
{
@Id
@Column(name = "id", nullable = false, length = 8)
private int id;
@Column(name = "title", nullable = false, length = 64)
private String title;
public Ribbon()
{
}
public abstract boolean isEarned(User user);
// ... getters/setters...
}
Вы можете видеть, что я определяю стратегию наследования как SINGLE_TABLE
(так как я должен кодировать как 50 лент, и мне не нужны дополнительные столбцы для любой из них).
Теперь конкретная лента будет реализована следующим образом:
@Entity
public class First2000UsersRibbon extends Ribbon
{
@Autowired
@Transient
private UserHasRibbonDao userHasRibbonDao;
public First2000UsersRibbon()
{
super.setId(1);
super.setTitle("Between the first 2,000 users who registered to the website");
}
@Override
public boolean isEarned(User user)
{
if(!userHasRibbonDao.userHasRibbon(user, this))
{
// TODO
// All the logic to determine whether the user earned the ribbon
// i.e. check whether the user is between the first 2000 users who registered to the website
// Other autowired DAOs are needed
}
else
{
return true;
}
return false;
}
}
Проблема в том, что userHasRibbonDao
является нулевым внутри isEarned()
метод, так что NullPointerException
брошен
Я думал, что автоматическое подключение DAO к объектам домена было неправильным, но в этой теме мне сказали, что это правильный подход (доменно-управляемый дизайн).
Я поделился нерабочим очень простым примером на GitHub: https://github.com/MintTwist/TestApp (не забудьте изменить детали соединения в /WEB-INF/properties/jdbc.properties и импортировать скрипт test_app.sql)
Любая помощь очень ценится.
Спасибо!
Обновление - читая первые ответы, кажется, что мой подход совершенно неверен. Как бы вы идеально структурировали код, учитывая, что может быть 50-70 разных лент? Спасибо
8 ответов
Я не говорю, что согласен с внедрением DAO в доменные экземпляры.... но
Вы можете подключить свой DAO к своему объекту домена. Но вы должны будете объявить свой доменный объект в контексте приложения Spring и получить новые экземпляры из Spring, НЕ используя new
, Убедитесь, что вы используете прототип области! Вы не хотите получать один и тот же экземпляр синглтона каждый раз!
Действительно, эта логика, которую вы хотите реализовать, принадлежит сервису, который внедряется с DAO, которые ему требуются.
Возможно, вы могли бы иметь такой сервис, как:
@Service
public class RibbonServiceImpl implements RibbonService
@Autowired
private RibbonDAO ribbonDAO;
public boolean isEarned(Ribbon ribbon, User user) {
if(!userHasRibbonDao.userHasRibbon(user, this))
{
// TODO
// All the logic to determine whether the user earned the ribbon
// i.e. check whether the user is between the first 2000 users who registered to the website
// Other autowired DAOs are needed
}
else
{
return true;
}
return false;
}
Отметить как @Configurable
- @Configurable annotion гарантирует, что даже если бины создаются вне Spring, зависимости вводятся
Вы также должны добавить <context:spring-configured/>
в вашем контексте.
Один ответ отсутствует, и это не красиво, но это работает. Вместо того, чтобы связать Dao, вы можете найти его в WebApplicationContext:
RibbonDao dao = ContextLoader.getCurrentWebApplicationContext.getBean(RibbonDao.class);
Это противоположность всего, что означает внедрение зависимостей (мне нравится называть этот шаблон "Инверсия инверсии управления":-)), но затем: внедрение сервисов в доменные объекты.
Вы также можете попробовать использовать @Component
аннотация, объявленная вместе с аннотацией @Entity для вашего класса First2000UsersRibbon. И убедитесь, что пакет с этим классом находится в <context:component-scan base-package="" />
, И наряду с этим вы должны убедиться, что объект этого класса не создан с помощью new
оператор.
Надеюсь, это поможет вам. Приветствия.
Зачем использовать DAO в DomainObject? Я предлагаю разделить DAO и DomainObject, потому что (IMHO) метод isEarned(User user) не относится к First2000UsersRibbon.
class UserHasRibbonDao {
public boolean isEarned(User user){
if(!userHasRibbonDao.userHasRibbon(user, this)) {
// TODO
// All the logic to determine whether the user earned the ribbon
// i.e. check whether the user is between the first 2000 users who registered to the website
// Other autowired DAOs are needed
} else {
return true;
}
return false;}
}
Как уже упоминал Алекс, не рекомендуется использовать сущности вашего приложения в качестве компонентов в вашем контексте. Есть много громоздких вещей, которые могут случиться, и это не похоже на хороший дизайн.
Код будет выглядеть примерно так:
public abstract class Ribbon{
public abstract boolean checkUser(User user);
}
public class NewUserRibbon extends Ribbon{
@Override
public boolean checkUser(User user){
// your logic here
}
}
В вашем Сервисе вы можете иметь кеш-коллекцию всех лент в системе (если они не являются динамическими), я бы даже предложил классифицировать ленты по триггерам событий (новые пользователи, ответы, голоса и т. Д.), Чтобы вы могли проверьте в своих сервисах только подходящие ленты (вместо всех), перебирая соответствующий список лент с текущим пользователем.
Я думаю, что вам нужно настроить свой дизайн. Первый вопрос, который у меня возник, был: "Почему у вашего класса ленты есть возможность проверить, у какого пользователя он есть?" Это как сказать, что на кухонном столе должен быть метод boolean doesThisKitchenHaveMe(Kitchen k)
,
Мне кажется более логичным, что вам нужен третий локатор, который отображает ленту для пользователя
Из того, что я вижу, дизайн ваших спящих классов и постоянство заработанных лент - это хорошо. Я думаю, что проблема в том, когда и как вы решите, заработал ли пользователь новую ленту.
Допустим, поступил новый запрос от зарегистрированного пользователя. Объект User создается и заполняется Hibernate, и теперь мы знаем все ленты, уже заработанные этим пользователем, в userHasRibbonSet. Вероятно, нам нужен такой метод в User:
public boolean hasEarnedRibbon(Ribbon ribbon) {
for (UserHasRibbon userHasRibbon : userHasRibbonSet) {
if (userHasRibbon.getRibbon().equals(ribbon) {
return true;
}
}
return false;
}
(возможно, это можно оптимизировать, кэшируя сами ленты в наборе и выполняя поиск в постоянном времени, но это не является ключевым моментом здесь)
Запрос обрабатывается, объект User обновляется, чтобы отразить произошедшее. Затем, на выходе, вы проверяете, какие ленты заработал пользователь, примерно так:
public class RibbonAwardingInterceptor extends HandlerInterceptorAdapter {
@Resource
private SessionFactory sessionFactory;
@Resource // assuming it's a request-scoped bean; you can inject it one way or another
private User user;
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView) throws Exception {
List<Ribbon> allRibbons = sessionFactory.getCurrentSession().createQuery("from Ribbon").list();
for (Ribbon ribbon : allRibbons() {
if (!user.hasEarnedRibbon(ribbon)) {
// The user has not previously earned this ribbon - lets see if they have now
if (ribbon.isEarned(user)) {
user.getUserHasRibbonSet().add(new UserHasRibbon(user, ribbon));
}
}
}
}
}
Если вы хотите использовать этот точный шаблон, убедитесь, что этот перехватчик идет после любых перехватчиков, которые обновляют пользователя способом, относящимся к лентам, но до перехватчика, который закрывает транзакцию (при условии, что вы используете модель транзакции на запрос). Затем очистка Hibernate Session автоматически обновит таблицу UserHasRibbon, поэтому нет особой необходимости в выделенном DAO.
Это упрощенный подход, который, очевидно, можно уточнить. Явным улучшением будет более избирательный подход к проверяемым лентам. Возможно, каждый метод Контроллера мог бы завершиться проверкой того, применимы ли какие-либо соответствующие Ленты - Контроллер должен знать, какие Ленты могут быть присуждены после его действия.
Надеюсь, это поможет, пожалуйста, дайте мне знать, если я полностью упустил суть, и я попробую еще раз.