I'm trying to implement WebSocket in Play 2.1. framework together with JPA using Hibernate and I'm getting following exception when I'm accessing the DB from WebSocket:
java.lang.RuntimeException: No EntityManager bound to this thread. Try to annotate your action method with @play.db.jpa.Transactional
at play.db.jpa.JPA.em(JPA.java:42)
at model.operations.DistrictOperations.isOwner(DistrictOperations.java:15)
at controllers.security.Secured.isOwnerOf(Secured.java:28)
at controllers.DistrictCommunication.initWebSocket(DistrictCommunication.java:157)
at Routes$$anonfun$routes$1$$anonfun$applyOrElse$20$$anonfun$apply$20.apply(routes_routing.scala:277)
at Routes$$anonfun$routes$1$$anonfun$applyOrElse$20$$anonfun$apply$20.apply(routes_routing.scala:277)
at play.core.j.JavaWebSocket$$anonfun$webSocketWrapper$1$$anonfun$apply$1.apply(JavaWebSocket.scala:20)
at play.core.j.JavaWebSocket$$anonfun$webSocketWrapper$1$$anonfun$apply$1.apply(JavaWebSocket.scala:14)
at play.core.server.netty.PlayDefaultUpstreamHandler.liftedTree1$1(PlayDefaultUpstreamHandler.scala:324)
at play.core.server.netty.PlayDefaultUpstreamHandler.messageReceived(PlayDefaultUpstreamHandler.scala:322)
at org.jboss.netty.channel.SimpleChannelUpstreamHandler.handleUpstream(SimpleChannelUpstreamHandler.java:75)
at org.jboss.netty.channel.DefaultChannelPipeline.sendUpstream(DefaultChannelPipeline.java:565)
at org.jboss.netty.channel.DefaultChannelPipeline$DefaultChannelHandlerContext.sendUpstream(DefaultChannelPipeline.java:793)
at org.jboss.netty.handler.codec.http.HttpContentDecoder.messageReceived(HttpContentDecoder.java:104)
at org.jboss.netty.channel.SimpleChannelUpstreamHandler.handleUpstream(SimpleChannelUpstreamHandler.java:75)
at org.jboss.netty.channel.DefaultChannelPipeline.sendUpstream(DefaultChannelPipeline.java:565)
at org.jboss.netty.channel.DefaultChannelPipeline$DefaultChannelHandlerContext.sendUpstream(DefaultChannelPipeline.java:793)
at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:296)
at org.jboss.netty.handler.codec.frame.FrameDecoder.unfoldAndFireMessageReceived(FrameDecoder.java:455)
at org.jboss.netty.handler.codec.replay.ReplayingDecoder.callDecode(ReplayingDecoder.java:538)
at org.jboss.netty.handler.codec.replay.ReplayingDecoder.messageReceived(ReplayingDecoder.java:437)
at org.jboss.netty.channel.SimpleChannelUpstreamHandler.handleUpstream(SimpleChannelUpstreamHandler.java:75)
at org.jboss.netty.channel.DefaultChannelPipeline.sendUpstream(DefaultChannelPipeline.java:565)
at org.jboss.netty.channel.DefaultChannelPipeline.sendUpstream(DefaultChannelPipeline.java:560)
at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:268)
at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:255)
at org.jboss.netty.channel.socket.nio.NioWorker.read(NioWorker.java:84)
at org.jboss.netty.channel.socket.nio.AbstractNioWorker.processSelectedKeys(AbstractNioWorker.java:472)
at org.jboss.netty.channel.socket.nio.AbstractNioWorker.run(AbstractNioWorker.java:333)
at org.jboss.netty.channel.socket.nio.NioWorker.run(NioWorker.java:35)
at org.jboss.netty.util.ThreadRenamingRunnable.run(ThreadRenamingRunnable.java:102)
at org.jboss.netty.util.internal.DeadLockProofWorker$1.run(DeadLockProofWorker.java:42)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:895)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:918)
at java.lang.Thread.run(Thread.java:680)
Here is my code:
@Transactional(readOnly = true)
public static WebSocket<JsonNode> initWebSocket(final long id) {
if (Secured.isOwnerOf(id)) {
return new WebSocket<JsonNode>() {
// Called when the Websocket Handshake is done.
public void onReady(WebSocket.In<JsonNode> in,
WebSocket.Out<JsonNode> out) {
// For each event received on the socket,
in.onMessage(new Callback<JsonNode>() {
public void invoke(JsonNode event) {
System.out.println(event);
}
});
// When the socket is closed.
in.onClose(new Callback0() {
public void invoke() {
System.out.println("Disconnected");
}
});
// Send a single 'Hello!' message
ObjectNode on = Json.newObject();
on.put("greeting", "hello");
out.write(on);
}
};
}else{
return null;
}
secured isOwnerOf() method:
public static boolean isOwnerOf(Long district) {
return DistrictOperations.isOwner(district, Context.current().request().username());
}
and DistrictOperations.isOwner() method:
@Transactional(readOnly = true)
public static boolean isOwner(long district, String login){
Query query = JPA.em().createQuery("SELECT d FROM District d WHERE d.id = :districtId");
District d = (District) query.setParameter("districtId", district).getSingleResult();
return d.getName().equals(login);
}
It's advising me to add @Transactional annotation which I have in my code, so my question is: Is it somehow possible to access the database via EntityManager from the WebSocket? Because it seems to me like everything else except EntityManager works fine here.
EDIT I've even tried to use EntityManager inside the WebSocket, but I have still the same problem. So maybe small reformulation is how am I supposed to work with database using from WebSocket and JPA? Is it somehow possible to use both together?
Thank you in advance.
In Play, a WebSocket
is not an Action
. The @Transactional
annotation is not taken into account because the play.db.jpa.TransactionalAction
only intercepts Action
calls.
In your case, you have to manually place your code in a transaction with something like :
boolean isOwnerOf = JPA.withTransaction(new play.libs.F.Function0<Boolean>{
public Boolean apply() throws Throwable {
return Secured.isOwnerOf(id);
}
})
You will have to do the same if you need to access DB in your other websocket's callbacks. These callbacks will be executed in other threads and the JPA.withTransaction()
will take care of binding an EntityManager to these threads.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With