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. Это лучше документировано, а плагины для загрузки больше предназначены для расширения рамки, а не для изменения порядка обработки.