将DAO自动装配到域对象中

时间:2012-06-15 19:02:09

标签: java spring spring-mvc dao autowired

我正在为网站编写功能区/成就系统,我必须为系统中的每个功能区编写一些逻辑。例如,如果您是第一批注册到该网站的2,000人或者在论坛中发布了1,000个帖子后,您就可以获得一个功能区。这个想法非常类似于stackoverflow的徽章,真的。

因此,每个功能区显然都在数据库中,但它们还需要一些逻辑来确定用户何时获得功能区。

在我编写它的方式中,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自动装入域对象是错误的,但在this topic中他们告诉我这是正确的方法(域驱动设计)。

我在GitHub上分享了一个非常简单的非常简单的例子:https://github.com/MintTwist/TestApp(记得改变/WEB-INF/properties/jdbc.properties中的连接细节并导入test_app.sql脚本)

非常感谢任何帮助。

谢谢!

更新 - 阅读第一个答案,似乎我的方法完全错误。考虑到可能有50-70种不同的色带,您如何理想地构建代码?感谢

8 个答案:

答案 0 :(得分:6)

我并不是说我同意将DAO注入域实例......但是

您可以将DAO连接到您的域对象。但是您必须在spring应用程序上下文中声明域对象,并使用new从Spring NOT 获取新实例。确保使用原型范围!您不希望每次都获得相同的单例实例!

你想要实现的这个逻辑真的属于一个服务,它注入了它需要的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;
  }  

答案 1 :(得分:4)

将其标记为@Configurable - @Configurable注释将确保即使在Spring之外创建bean,也会注入依赖项

您还需要在上下文中添加<context:spring-configured/>

答案 2 :(得分:1)

缺少一个答案,它并不漂亮,但它有效。您可以从WebApplicationContext中查找Dao,而不是连接Dao:

RibbonDao dao = ContextLoader.getCurrentWebApplicationContext.getBean(RibbonDao.class);

这与依赖注入所代表的一切相反(我喜欢称这种模式为“反转控制反转”:-)),但随后:将服务注入到域对象中。

答案 3 :(得分:0)

您还可以尝试在 First2000UsersRibbon 类上使用@Component注释声明以及@Entity注释。并确保<context:component-scan base-package="" />中包含此类的包。与此同时,您需要确保不使用new运算符创建此类的对象。

希望这会对你有所帮助。欢呼声。

答案 4 :(得分:0)

正如Alex所提到的那样,将您的应用程序实体作为bean在您的上下文中使用并不是一个好习惯。有很多繁琐的事情可能发生,看起来不是一个好的设计。

代码看起来像这样:

public abstract class Ribbon{

    public abstract boolean checkUser(User user);
}

public class NewUserRibbon extends Ribbon{

    @Override
    public boolean checkUser(User user){
        // your logic here
    }
}

在您的服务中,您可以拥有系统中所有色带的缓存集合(除非它们是动态的),我建议甚至按事件触发器(新用户,答案,投票等等)对色带进行分类,因此,您可以通过与当前用户一起迭代适用的功能区列表,仅为相应的色带(而不是所有色带)检入您的服务。

答案 5 :(得分:0)

从我可以看到你的hibernate类的设计,以及所获得的色带的持久性,很好。我认为问题在于您何时以及如何决定用户是否获得了新的功能区。

让我们说来自登录用户的新请求。用户对象由Hibernate创建并填充,现在我们在userHasRibbonSet中了解该用户已经获得的所有Ribbons。我们可能需要在User中使用这样的方法:

public boolean hasEarnedRibbon(Ribbon ribbon) {
    for (UserHasRibbon userHasRibbon : userHasRibbonSet) {
        if (userHasRibbon.getRibbon().equals(ribbon) {
            return true;
        }
    }
    return false;
}

(这可能可以通过在一个Set中缓存色带本身并进行恒定时间查找来优化,但这不是关键)

处理请求,更新User对象以反映发生的情况。然后,在出路的时候,你会检查用户现在获得的Ribbons,如下所示:

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));
                }
            }
        }
    }
}

如果你想使用这个确切的模式,请确保这个拦截器追踪任何以与band相关的方式更新User的拦截器,但是在关闭事务的拦截器之前(假设你使用的是每个请求的事务)模型)。然后刷新Hibernate Session将自动更新UserHasRibbon表,因此不需要专用的DAO。

这是一种简单的方法,显然可以改进。一个明显的改进是对您正在检查的Ribbons更具选择性。也许每个Controller方法都可以通过检查现在是否适用任何相关的Ribbons来完成 - Controller应该知道在行动之后可以授予哪些Ribbons。

希望有所帮助,如果我完全错过了这一点,请告诉我,我会再试一次。

答案 6 :(得分:0)

我认为你需要调整你的设计。我遇到的第一个问题是“你的Ribbon课程怎么能检查哪个用户有它?”这就像说厨房桌应该有一个名为boolean doesThisKitchenHaveMe(Kitchen k)的方法。

对我而言,您需要一个将功能区映射到用户的第三个定位器服务

似乎更合乎逻辑

答案 7 :(得分:0)

为什么在DomainObject中使用DAO?我建议将DAO和DomainObject分离,因为(恕我直言)方法isEarned(用户用户)与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;}
}