playframework / play1

Play framework
https://www.playframework.com/documentation/1.4.x/home
Other
1.58k stars 682 forks source link

[play1.5.2] transaction control in thread #1296

Open hooTJ opened 5 years ago

hooTJ commented 5 years ago

Execute the following code in play 1.5.2

@Test
public void test01() {
    new Thread(() -> {
        EntityManager em = JPA.createEntityManager();
        em.setFlushMode(FlushModeType.COMMIT);
        JPA.bindForCurrentThread(JPA.DEFAULT, em, false);
        JPA.em().getTransaction().begin();
        t_user user = t_user.findById(1L);
        user.name = "123456";
        System.out.println(user.name);
            JPA.em().getTransaction().commit();
    }).start();
}

The following error occurred

Exception in thread "Thread-4" java.lang.IllegalStateException: Session/EntityManager is closed at org.hibernate.internal.AbstractSharedSessionContract.checkOpen(AbstractSharedSessionContract.java:337) at org.hibernate.engine.spi.SharedSessionContractImplementor.checkOpen(SharedSessionContractImplementor.java:135) at org.hibernate.internal.AbstractSharedSessionContract.checkOpenOrWaitingForAutoClose(AbstractSharedSessionContract.java:343) at org.hibernate.internal.SessionImpl.afterTransactionBegin(SessionImpl.java:3190) at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.afterTransactionBegin(JdbcCoordinatorImpl.java:462) at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.afterBeginCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:136) at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$000(JdbcResourceLocalTransactionCoordinatorImpl.java:38) at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.begin(JdbcResourceLocalTransactionCoordinatorImpl.java:204) at org.hibernate.engine.transaction.internal.TransactionImpl.begin(TransactionImpl.java:56) at BasicTest.lambda$0(BasicTest.java:42) at java.lang.Thread.run(Thread.java:748)

tomparle commented 5 years ago

Hi @jgwmjz , this is because the transaction is no more accessible if you quit the main thread, here by using your own thread. I also think your code is too low-level, because in Play you don't need to use the JPA manager directly in most cases. Try using the Promise class provided by Play like this :

new Job<>() {
    public void doJob() throws Exception {
        // an example of your logic here :
        t_user user = t_user.findById(1L);
    user.name = "123456";
        user.save();
    }
}.now().get();

Note that just calling user.save() will make Play automatically persist the change for you. Finally, the Job also allows you to return an object. In this case, override the method doJobWithResult() instead !

tazmaniax commented 5 years ago

Yes the Play Job picks up the filters of the configured plugins including the JPA plugin so it gets a new database session/transaction. If you call job.now() the job is executed in another thread but if you are already in another thread, such as from a web service call back, then I call job.call() so it executes in the current thread but gets all of the filter goodies including JPA session. Not sure if that is best practice and would be good to hear from others there better approaches but it seems to work

ghost commented 5 years ago

@jgwmjz u can us TS like follow: new Thread(()->{ JPA.startTx(JPA.DEFAULT, Boolean.FALSE); // todo JPA.closeTx(JPA.DEFAULT); });