spring 将DAO自动装入域对象

eivgtgni  于 2023-01-08  发布在  Spring
关注(0)|答案(8)|浏览(150)

我正在为一个网站编写一个丝带/成就系统,我必须为系统中的每个丝带编写一些逻辑。例如,如果你是网站注册的前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个Ribbon,并且不需要为其中任何一个编写额外的列)。
现在,将像这样实现特定Ribbon,例如:

@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中,他们告诉我这是正确的方法(域驱动设计)。
I shared a non-working very simple example on GitHub: https://github.com/MintTwist/TestApp (remember to change the connection details in /WEB-INF/properties/jdbc.properties and to import the test_app.sql script)
任何帮助都非常感谢。
谢谢大家!

    • 更新**-阅读第一个答案,似乎我的方法是完全错误的。考虑到可能有50 - 70个不同的功能区,您理想的代码结构是什么?谢谢
sg3maiej

sg3maiej1#

我并不是说我同意将DAO注入到域示例中......但是
你可以把DAO连接到域对象中。但是你必须在你的spring应用上下文中声明你的域对象,并且使用new从SpringNOT中获取新的示例。确保你使用了prototype作用域!你不想每次都得到相同的单例示例!
实际上,您要实现的逻辑属于注入了所需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;
  }
06odsfpq

06odsfpq2#

将其标记为@Configurable- @Configurable注解将确保即使bean是在Spring之外创建的,依赖项也会被注入
您还需要在上下文中添加<context:spring-configured/>

izkcnapc

izkcnapc3#

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

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

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

2exbekwf

2exbekwf4#

您也可以尝试在First 2000 UsersRibbon类上使用@Component注解声明沿着@Entity注解。并确保<context:component-scan base-package="" />中存在包含此类的包。此外,您还需要确保此类的对象不是使用new操作符创建的。
希望这对你有帮助。干杯。

mefy6pfw

mefy6pfw5#

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

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

qvsjd97n

qvsjd97n6#

从我所看到的你的休眠类的设计,以及获得的丝带的持久性来看,都很好。我认为问题在于你何时以及如何决定一个用户是否获得了一个新丝带。
假设一个登录用户发出了一个新的请求。User对象是由Hibernate创建和填充的,现在我们知道了该用户在userHasRibbonSet中已经获得的所有Ribbons。我们可能需要在User中使用这样的方法:

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

(this可以通过在Set中缓存Ribbon本身并执行恒定时间查找来优化,但这不是这里的关键)
请求被处理,用户对象被更新以反映发生的事情。然后,在退出时,你检查用户现在获得了什么样的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));
                }
            }
        }
    }
}

如果您想使用这种模式,请确保此拦截器在任何以与Ribbon相关的方式更新User的拦截器之后,但在关闭事务的拦截器之前(假设您使用的是按请求处理事务的模型),然后刷新Hibernate会话将自动更新UserHasRibbon表,因此真实的上不需要专用DAO。
这是一个过于简单的方法,显然可以改进。一个明显的改进是对你正在检查的功能区进行更多的选择。也许每个控制器方法可以通过检查是否有任何相关的功能区现在适用来完成-控制器应该知道在它的操作之后可以授予哪些功能区。
希望有帮助,请让我知道,如果我完全错过了点,我会再试一次。

0g0grzrc

0g0grzrc7#

我认为你需要调整你的设计。我的第一个问题是“为什么你的Ribbon类有能力检查哪个用户拥有它?”这就像说厨房的table应该有一个名为boolean doesThisKitchenHaveMe(Kitchen k)的方法。
在我看来,更符合逻辑的做法是需要第三个定位器服务来将功能区Map到用户

bvjveswy

bvjveswy8#

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

相关问题