Write to database asynchronously using Panache

1,579 views
Skip to first unread message

Brand Bintley

unread,
Feb 6, 2021, 7:05:44 PM2/6/21
to Quarkus Development mailing list
I am facing the same situation described here:


Essentially, I have a service into which ManagedExecutor is injected. The executor's task then reads from the database using panache, then alters the entity and writes it back to the database.

Upon writing, the following exception is thrown:

javax.persistence.TransactionRequiredException: Transaction is not active, consider adding @Transactional to your method to automatically activate one.

The weird part is that the run() method of the task is indeed annotated with @Transactional.

Any idea how to address this?

Brand Bintley

unread,
Feb 7, 2021, 10:07:38 AM2/7/21
to Quarkus Development mailing list
Please help. Below is my reproducer code. Obviously, if I replace executor.execute(task); with  task.run(); things work well because the execution becomes synchronous. I am trying to see how to persist entities asynchronously while using Panache. 


@Path("/hello")

public class GreetingResource {

@Inject ManagedExecutor executor;

@GET

@Produces(MediaType.TEXT_PLAIN)

@Transactional

public String hello() {

PersonTask task= new PersonTask();

    executor.execute(task);

        return "hello";

}

}




@Entity

public class Person extends PanacheEntity {

       public String name=UUID.randomUUID().toString();

}




public class PersonTask implements Runnable{

@Transactional

public void run() {

Person p = new Person();

 p.persistAndFlush();

}

}



The error is:


ARJUNA012094: Commit of action id 0:ffffc0a8010e:e950:60200089:0 invoked while multiple threads active within it.

ARJUNA012107: CheckedAction::check - atomic action 0:ffffc0a8010e:e950:60200089:0 commiting with 2 threads active!

Thread Thread[executor-thread-2,5,executor] threw an uncaught exception: javax.persistence.TransactionRequiredException: Transaction is not active, consider adding @Transactional to your method to automatically activate one.


--
You received this message because you are subscribed to the Google Groups "Quarkus Development mailing list" group.
To unsubscribe from this group and stop receiving emails from it, send an email to quarkus-dev...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/quarkus-dev/aa0713ca-89a7-4f96-830a-22b49d6e91a4n%40googlegroups.com.

Paul Carter-Brown

unread,
Feb 7, 2021, 10:18:33 AM2/7/21
to brand....@gmail.com, Quarkus Development mailing list
Try annotating the run() with @Transactional(REQUIRES_NEW)

Brand Bintley

unread,
Feb 7, 2021, 12:59:46 PM2/7/21
to Paul Carter-Brown, Quarkus Development mailing list
Thanks. REQUIRES_NEW as well as MANDATORY didn't help. This is what works for me.


@Path("/hello")

public class GreetingResource {

@Inject ManagedExecutor executor;

@Inject TransactionManager tm; 


@GET

@Produces(MediaType.TEXT_PLAIN)

public String hello() {

PersonTask task= new PersonTask();

task.setTm(tm);

    executor.execute(task);

        return "hello";

}

}



public class PersonTask implements Runnable{

       protected TransactionManager tm; 


public void setTm(TransactionManager tm) {

this.tm = tm;

}


@Override

public void run() {

try {

   tm.begin();

   Person p = new Person();

   p.persistAndFlush();

   tm.commit();

}catch(Exception e) {

e.printStackTrace();

}

}

}




Stephane Epardaud

unread,
Feb 8, 2021, 3:54:32 AM2/8/21
to brand....@gmail.com, Quarkus Development mailing list
Hi,

So what happens here is that you're opening a transaction, then starting an async task with the same transaction, then before it's done you return a response to the client and we close the transaction because your method is done.

What you should do if you want to make it async, but not reply to the user until the transaction is finished is this:

@Path("/hello")

public class GreetingResource {

 @Inject ManagedExecutor executor;

 @GET

 @Produces(MediaType.TEXT_PLAIN)

 @Transactional

 public CompletionStage<String> hello() {

PersonTask tasknew PersonTask();

     return executor.runAsync(task).thenApply(v -> "hello");

 }

}




@Entity

public class Person extends PanacheEntity {

       public String name=UUID.randomUUID().toString();

}




public class PersonTask implements Runnable{

public void run() {

 Person p = new Person();

 p.persistAndFlush();

}

}


At least this way, you're sure the task is done before you return your response to the user, and we won't close the transaction before you're done.





--
Stéphane Épardaud

Brand Bintley

unread,
Feb 8, 2021, 5:10:11 AM2/8/21
to Stephane Epardaud, Quarkus Development mailing list
Hi Stephane,

Thanks for your input. What I really want is to respond to the user and then continue the processing asynchronously. That's why I think the transaction is needed for the execution of the run() method. What seems to happen is that the @Transactional annotation is ignored when the run method is executed by the executors.

I hope that makes sense.

Brand

Stephane Epardaud

unread,
Feb 8, 2021, 5:24:33 AM2/8/21
to Brand Bintley, Quarkus Development mailing list
OK, yes the @Transactional annotation is only used on beans, and your Runnable function is not a bean.

You will need something like this:



@Path("/hello")

public class GreetingResource {

         @Inject Service service;

 @Inject ManagedExecutor executor;

 @GET

 @Produces(MediaType.TEXT_PLAIN)

 public String hello() {

PersonTask tasknew PersonTask();

     executor.execute(() -> service.run());

                return "hello";

 }

}




@Entity

public class Person extends PanacheEntity {

       public String name=UUID.randomUUID().toString();

}



@ApplicationScoped

public class Service {

        @Transactional

--
Stéphane Épardaud
Reply all
Reply to author
Forward
0 new messages