【问题标题】:Autowiring a DAO into a domain object [duplicate]将DAO自动装配到域对象中[重复]
【发布时间】:2012-06-18 21:05:42
【问题描述】:

我正在为网站编写功能区/成就系统,我必须为系统中的每个功能区编写一些逻辑。例如,如果您是注册网站的前 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;
    }
}

问题是userHasRibbonDaoisEarned() 方法中为空,所以会抛出NullPointerException

我认为将 DAO 自动装配到域对象中是错误的,但在 this topic 他们告诉我这是正确的方法(域驱动设计)。

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

非常感谢任何帮助。

谢谢!

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

【问题讨论】:

  • >_
  • 这里应该有一些github URL吗?
  • 谢谢@NathanHughes,我刚刚发布了他们在另一个问题上告诉我的内容。
  • 抱歉@madth3,我忘记添加了。它现在在那里:)

标签: java spring spring-mvc dao autowired


【解决方案1】:

我并不是说我同意将 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;
  }  

【讨论】:

  • 谢谢你,@Alex。因此,显然他们在另一个 SO 问题中告诉我的内容是错误的……但是鉴于我总共可以拥有 50-70 个色带,您将如何构建服务和代码?谢谢!
  • 感谢您的编辑,@Alex。问题是每个功能区都有不同的逻辑,所以我可以看到你的代码工作的唯一方法是通过添加一些 instanceof 来检测我应该使用哪个功能区逻辑来修改它。我知道必须使用instanceof(在equals() 等方法之外)是由糟糕的软件设计引起的,那么什么是更好的解决方案?谢谢。
  • 嗯好的,所以也许你可以让你的 RibbonService 抽象并创建一个抽象的 isEarned 方法。然后,您可以使用 static.springsource.org/spring/docs/3.1.0.RC1/… 根据 RibbonType 查找正确的服务实例
  • 抱歉,@Alex,但我真的看不出这种方法对我的情况有何帮助......有帮助吗?非常感谢
【解决方案2】:

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

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

【讨论】:

  • +1 但注意:这需要 AspectJ 编译或加载时间编织!
  • +1 @satoshi:看看 Spring Roo 看看它在实践中是如何工作的
  • 看看这个项目作为替代方法 - github.com/sabomichal/spring-injector。免责声明。我是作者
【解决方案3】:

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

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

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

【讨论】:

    【解决方案4】:

    您也可以尝试在 First2000UsersRibbon 类上使用 @Component 注释和 @Entity 注释声明。并确保具有此类的包在<context:component-scan base-package="" /> 中。除此之外,您还需要确保该类的对象不是使用new 运算符创建的。

    希望这对您有所帮助。干杯。

    【讨论】:

      【解决方案5】:

      正如 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
          }
      }
      

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

      【讨论】:

        【解决方案6】:

        从我可以看到你的休眠类的设计,以及获得的丝带的持久性,都很好。我认为问题在于您何时以及如何确定用户是否获得了新丝带。

        假设一个新的请求来自一个登录的用户。 User 对象由 Hibernate 创建和填充,我们现在知道该用户已经在 userHasRibbonSet 中获得的所有 Ribbon。我们可能在 User 中需要这样的方法:

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

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

        请求被处理,用户对象被更新以反映发生了什么。然后,在出去的路上,你检查用户现在获得了什么丝带,如下所示:

        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。

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

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

        【讨论】:

          【解决方案7】:

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

          对我来说,您需要将功能区映射到用户的第三个定位器服务似乎更合乎逻辑

          【讨论】:

            【解决方案8】:

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

            【讨论】:

              猜你喜欢
              • 2014-11-08
              • 2017-08-06
              • 2018-01-27
              • 1970-01-01
              • 1970-01-01
              • 2014-08-17
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多