我正在为网站编写功能区/成就系统,我必须为系统中的每个功能区编写一些逻辑。例如,如果您是第一批注册到该网站的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种不同的色带,您如何理想地构建代码?感谢
答案 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;}
}