JPA и Threads в игре

Я пытаюсь создать многопоточный сервер. Проблема в том, что я получаю следующую ошибку: play.exceptions.JPAException: Контекст JPA не инициализируется. JPA Entity Manager автоматически запускается, когда один или несколько классов, аннотированных с помощью аннотации @javax.persistence.Entity, находятся в приложении.

То, что я пытаюсь сделать, это получить доступ к db из нового потока здесь код

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();
        }
    }
}
}

Любые идеи?

Ответ 1

Не используйте обычный поток, вместо этого используйте задания:

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

И 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();
        }
    }
}

Ответ 2

Обновление

Это более аккуратно, чем ниже:

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

У нас была аналогичная проблема сегодня.

Вам нужно создать новый EntityManager и транзакцию для каждого потока и установить его в классе JPA.

Play использует ThreadLocal, чтобы сохранить его EntityManager в JPA, поэтому он имеет значение null для созданного потока. К сожалению, вы не можете использовать вспомогательные методы в JPA, чтобы сделать это (они закрыты для пакета), и вам нужно использовать ThreadLocal напрямую. Вот как вы можете это сделать:

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();
     }
}

Я использую его с одним исполнителем потока из java.util.concurrent без проблем.

Ответ 3

Я предполагаю, что ваш поток стартует, прежде чем у Play появится возможность запустить диспетчер Entity JPA.

Если ваш класс модели аннотируется с @Entity, тогда менеджер объектов был бы создан, и ваша ошибка не появится.

Итак, у вас есть несколько вариантов. Либо,

  • Вы можете создать PlayPlugin с меньшим приоритетом, чем в стандарте Play onApplicationStart.
  • Вы можете запускать свой поток из начальной загрузки. Это обеспечит правильную загрузку Play, прежде чем вы начнете взаимодействовать с сервером. Подробнее о загрузочных работах см. http://www.playframework.org/documentation/1/jobs#concepts

Лично я бы пошел с опцией 2. Это лучше документировано, а плагины для загрузки больше предназначены для расширения рамки, а не для изменения порядка обработки.