Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Play 2.1. WebSocket No EntityManager bound to this thread

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.

like image 475
ziky90 Avatar asked Feb 16 '23 14:02

ziky90


1 Answers

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.

like image 144
mguillermin Avatar answered Mar 03 '23 12:03

mguillermin