在CDI-bean中安排JPA查询和访问结果?

时间:2012-10-17 19:25:02

标签: jpa-2.0 cdi

每隔x分钟我想查询新实例并缓存结果。我目前只需要一个简单的缓存解决方案,因此我想更新Set @ApplicationScoped

中的CacheBean

我试过了:

ScheduledExecutorService scheduler = Executors
        .newScheduledThreadPool(1);
    ScheduledFuture<?> sf = scheduler.scheduleAtFixedRate(new Runnable() {
        public void run() {
//.................

但是创建的线程无法访问任何上下文实例(InvocationException)。

那么如何以CDI / JPA的方式做到这一点?

使用Tomcat 7,Weld,JPA2 - Hibernate。

3 个答案:

答案 0 :(得分:2)

我的建议是尝试已集成CDI和JPA的Tomcat版本(TomEE)。它附带OpenJPA,但您可以使用Hibernate。然后用这样的类进行缓存:

@Singleton
@Startup
public class CachingBean {

    @Resource 
    private BeanManager beanManager;

    @Schedule(minute = "*/10", hour = "*")
    private void run() {
        // cache things
    }
}

该组件会在应用启动时自动启动,并且每十分钟运行一次上述方法。有关详细信息,请参阅Schedule文档。

更新

为你破解了一个例子。使用一个不错的CDI / EJB组合来安排CDI事件。

实际上,这是围绕BeanManager.fireEvent(Object,Annotations...)方法的简单包装,可将ScheduleExpression添加到组合中。

@Singleton
@Lock(LockType.READ)
public class Scheduler {

    @Resource
    private TimerService timerService;

    @Resource
    private BeanManager beanManager;

    public void scheduleEvent(ScheduleExpression schedule, Object event, Annotation... qualifiers) {

        timerService.createCalendarTimer(schedule, new TimerConfig(new EventConfig(event, qualifiers), false));
    }

    @Timeout
    private void timeout(Timer timer) {
        final EventConfig config = (EventConfig) timer.getInfo();

        beanManager.fireEvent(config.getEvent(), config.getQualifiers());
    }

    // Doesn't actually need to be serializable, just has to implement it
    private final class EventConfig implements Serializable {

        private final Object event;
        private final Annotation[] qualifiers;

        private EventConfig(Object event, Annotation[] qualifiers) {
            this.event = event;
            this.qualifiers = qualifiers;
        }

        public Object getEvent() {
            return event;
        }

        public Annotation[] getQualifiers() {
            return qualifiers;
        }
    }
}

然后使用它,将Scheduler作为EJB注入并安排离开。

public class SomeBean {

    @EJB
    private Scheduler scheduler;

    public void doit() throws Exception {

        // every five minutes
        final ScheduleExpression schedule = new ScheduleExpression()
                .hour("*")
                .minute("*")
                .second("*/5");

        scheduler.scheduleEvent(schedule, new TestEvent("five"));
    }

    /**
     * Event will fire every five minutes
     */ 
    public void observe(@Observes TestEvent event) {
        // process the event
    }

}

完整源代码和工作示例here

你必须知道

  • CDI事件不是多线程的

如果有10个观察者,每个观察者需要7分钟才能执行,那么一个事件的总执行时间为70分钟。如果安排该事件的发生频率超过70分钟,那绝对没有用。

如果你做了会怎么样?取决于@Singleton @Lock政策

  • @Lock(WRITE)是默认设置。在此模式下,timeout方法基本上将被锁定,直到上一次调用完成。即使每70分钟只能处理一次,每5分钟启动它最终会导致所有池化的计时器线程等待你的Singleton。
  • @Lock(READ)允许并行执行timeout方法。事件会并发一段时间。然而,由于它们实际上每个需要70分钟,所以在一个小时左右的时间内,我们将在计时器池中耗尽线程,就像上面一样。

优雅的解决方案是使用@Lock(WRITE)然后在@AccessTimeout(value = 1, unit = TimeUnit.MINUTES)方法上指定一些短暂的超时,例如timeout。当下一个5分钟的调用被触发时,它会等到1分钟才能在放弃之前访问Singleton。这将使您的计时器池不会填满备份作业 - “溢出”就会被丢弃。

答案 1 :(得分:0)

而不是将新的Runnable(){....}传递给scheduler.scheduleAtFixedRate,而是创建一个实现Runnable和@Inject该bean的CDI bean,然后将其传递给scheduler.scheduleAtFixedRate

答案 2 :(得分:0)

与David Blevins聊了一会儿后,我可以承认他的答案是我投票的好人。非常感谢所有这一切。尽管大卫,你忘了宣布你参与了TomEE,我知道他总是打扰别人。

无论如何,我去的解决方案是由Mark Struberg在#Deltaspike(freenode)中提出的。

作为一名deltaspike用户,我很高兴与deltaspike一起做。此博客文章中概述了解决方案:

http://struberg.wordpress.com/2012/03/17/controlling-cdi-containers-in-se-and-ee/

我必须切换到OWB,请参阅https://issues.apache.org/jira/browse/DELTASPIKE-284

干杯