Автоматическое подключение 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.

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

Надеюсь, это поможет, пожалуйста, дайте мне знать, если я полностью упустил суть, и я попробую еще раз.

Другие вопросы по тегам