开发者

JPA and Threads in play framework

I'm trying to create a multithreading server. The problem is that I get the following error: play.exceptions.JPAException: The JPA context is not initialized. JPA Entity Manager automatically start when one or more classes annotated with the @javax.persistence.Entity annotation开发者_开发百科 are found in the application.

What I'm trying to do is access the db from the new thread here's the code

package controllers;
import java.util.Iterator;
import java.util.List;
import models.Ball;


public class MainLoop extends Thread {

@Override
public void run() {
    List<Ball> balls;
    new Ball(5,5,2,2,10,15);
    while (true){
        balls = Ball.all().fetch(); //Here throws an exception 

        for (Iterator iterator = balls.iterator(); iterator.hasNext();) {
            Ball ball = (Ball) iterator.next();
            ball.applyForces();
        }
    }
}
}

Any ideas?


Don't use plain thread, use jobs instead :

@OnApplicationStart 
public class MainLoop extends Job { 
       public void doJob() { 
               new BallJob().now(); 
       }
} 

And BallJob :

public class BallJob extends Job {
public void doJob() {
    List<Ball> balls;
    new Ball(5,5,2,2,10,15);
    while (true){
        balls = Ball.all().fetch(); 
        for (Iterator iterator = balls.iterator(); iterator.hasNext();) {
            Ball ball = (Ball) iterator.next();
            ball.applyForces();
        }
    }
}


Update

This is neater than what's below:

JPAPlugin.startTx(false);
// Do your stuff
JPAPlugin.endTx(false);

Had similar problem today.

You have to create new EntityManager and transaction for each thread and set it in JPA class.

Play uses ThreadLocal to keep it's EntityManager in JPA, so it's null for your created thread. Unfortunately you cannot use helper methods in JPA to do it (they are package private) and you have to use ThreadLocal directly. Here's how you can do this:

class Runner extends Runnable {
     @Override
     public void run() {
         if (JPA.local.get() == null) {
             EntityManager em = JPA.newEntityManager();
             final JPA jpa = new JPA();
             jpa.entityManager = em;
             JPA.local.set(jpa);
         }

         JPA.em().getTransaction().begin();
         ... DO YOUR STUFF HERE ...
         JPA.em().getTransaction().commit();
     }
}

I use it with single thread executor from java.util.concurrent without any problems.


I am guessing your thread is kicked off before Play has a chance to start the JPA Entity Manager.

If your Model class is annotated with @Entity, then the entity manager would have been created and your error would not appear.

So, you have a couple of options. Either,

  1. You can create a PlayPlugin, with a lower priority than the Play standard onApplicationStart processes.
  2. You can kickstart your thread from a bootstrap job. This will ensure that Play has had a chance to start up correctly before you start interacting with the server. To see more about bootstrap jobs, see http://www.playframework.org/documentation/1/jobs#concepts

Personally, I would go with option 2. It is better documented, and play plugins are meant more for extending the framework rather than changing the order of processing.


class Runner extends Runnable {
     @Override
     public void run() {
        EntityManager em = JPA.newEntityManager();
        em.setFlushMode(FlushModeType.COMMIT);
        JPA jpa = new JPA();
        jpa.bindForCurrentThread(JPA.DEFAULT, em, false);

         em.getTransaction().begin();
        // ... DO YOUR STUFF HERE ...
         em.getTransaction().commit();
     }
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜