我正在开发一个Web应用程序,在其中我尝试集成Vaadin和DB4O以及在TomEE容器中运行的CDI。为了拥有数据库事务,我创建了ServletFilter,它在请求结束时拦截所有请求和提交或回滚。
@WebFilter("/*")
public class DBTransactionHandler implements Filter {
@Inject
SessionImpl sessionImpl;
@Override
public void destroy() {
// TODO Auto-generated method stub
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
ServletException {
boolean hadException = false;
try {
chain.doFilter(request, response);
} catch (RuntimeException ex) {
hadException = true;
throw ex;
} finally {
if (sessionImpl != null) {
if (hadException || sessionImpl.isRollbackOnly()) {
sessionImpl.rollback();
} else {
sessionImpl.commit();
}
sessionImpl.close();
}
}
}
@Override
public void init(FilterConfig arg0) throws ServletException {
// TODO Auto-generated method stub
}
}
我有一个SessionImpl,它是@RequestScoped
。有了这个,我试图在处理HTTP请求时实现所有需要使用数据库的服务,然后它们应该获得相同的实例,因此它将在同一个数据库事务中执行。
/**
* http://community.versant.com/documentation/reference/db4o-8.0/java/reference/Content/platform_specific_issues/web/servlets.htm
*/
@RequestScoped
public class SessionImpl implements Session {
@Inject
DBConnectionFactory connectionFactory;
private boolean rollbackOnly;
private ObjectContainer delegate;
@PostConstruct
public void init() {
delegate = connectionFactory.getConnection().ext().openSession();
}
//.... many other database related methods
}
我的所有服务都来自AbstractService
,因此他们立即开始工作。
public class AbstractService {
@Inject
protected BeanManager beanManager;
@Inject
protected Session db;
}
这就是我现在所拥有的,这就是我的问题:
在我的Web应用程序中,我需要创建一个 Scheduler 组件。预定的工作将使用我已有的相同服务。由于SessionImpl
是@RequestScoped
并且我在预定作业中没有HTTP请求,因此无法注入SessionImpl。
我尝试做的是创建自定义范围@SchedulerScoped
。这将在调度程序开始执行作业之前激活。这种方法的问题是当我向SessionImpl
添加第二个范围时,我的应用程序不再部署了:
SEVERE: CDI Beans module deployment failed
org.apache.webbeans.exception.WebBeansConfigurationException: Managed Bean implementation class : org.reluxa.db.SessionImplstereotypes must declare the same @Scope annotations.
at org.apache.webbeans.config.DefinitionUtil.defineScopeType(DefinitionUtil.java:390)
at org.apache.webbeans.component.creation.AbstractBeanCreator.defineScopeType(AbstractBeanCreator.java:145)
at org.apache.webbeans.util.WebBeansUtil.defineManagedBean(WebBeansUtil.java:2548)
at org.apache.openejb.cdi.BeansDeployer.defineManagedBean(BeansDeployer.java:552)
at org.apache.openejb.cdi.OpenEJBLifecycle.deployManagedBeans(OpenEJBLifecycle.java:407)
答案 0 :(得分:1)
这是一个有趣的问题。我建议以下作为解决方案的概要我还没有尝试,因此需要进行一些调整:
SessionImpl
非CDI:移除@RequestScoped
,@Inject
。使用生成Session
s的生成器方法创建一个CDI bean,如下所示:
// I believe it should be @ApplicationScoped
public class SessionProducer {
private ThreadLocal<Session> currentSession;
@Produces Session makeSession() {
return currentSession.get();
}
public ThreadLocal<Session> getCurrentSessionThreadLocal() {
return currentSession;
}
}
所以会话驻留在ThreadLocal
中,生产者只是从那里得到它。谁把它放在那里?
修改您的过滤器,将{em>并删除 Session
中的ThreadLocal
:
@WebFilter("/*")
public class DBTransactionHandler implements Filter {
@Inject
DBConnectionFactory connectionFactory;
@Inject
SessionProducer sessionProducer;
...
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
boolean hadException = false;
try {
SessionImpl sessionImpl = new SessionImpl();
sessionImpl.setConnectionFactory(connectionFactory);
sessionProducer.getCurrentSessionThreadLocal().set(sessionImpl);
// here you probably want to call sessionImpl.init();
chain.doFilter(request, response);
} catch (RuntimeException ex) {
hadException = true;
throw ex;
} finally {
if (sessionImpl != null) {
if (hadException || sessionImpl.isRollbackOnly()) {
sessionImpl.rollback();
} else {
sessionImpl.commit();
}
sessionImpl.close();
sessionProducer.getCurrentSessionThreadLocal().set(null);
}
}
}
...
}
到此为止,您的网络配置应该像以前一样工作。根据需要进行测试和调整。 (测试每个线程只生成一个会话。)
SessionImpl
创建和销毁仪式。这样,服务仍然可以看到有效的Session
,而不需要请求范围。这个“仪式”可能被考虑在内(例如,执行这些操作的调度程序的CDI或EJB拦截器是可重用的,并且不会像这样使用系统逻辑来污染组件的业务逻辑。)