0

While I have Java Batch jobs that read data, process it and store it in other places in the database, now I need a step to actually remove data from the database. All I need to run is a delete query via JPA.

The chunk based Reader/Processor/Writer pattern does not make sense here. But the Batchlet alternative is giving me a headache either. What did I do?

I created a Batchlet that gets invoked via CDI. At that moment it is easy to inject my JPA EntityManager. What is not easy is to run the update query. Code looks like this:

package ...;

import javax.batch.api.BatchProperty;
import javax.inject.Inject;
import javax.inject.Named;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

@Named("CleanerBatchlet")
public class CleanerBatchlet extends AbstractBatchlet {
    public static final Logger log = LogManager.getLogger(CleanerBatchlet.class);

    @PersistenceContext(unitName = "...")
    private EntityManager entityManager;  

    @Inject
    @BatchProperty(name = "technologyIds")
    private String technologyIds;
    
    private void clearQueue(long technologyId) {
        //EntityManager entityManager = ...getEntityManager();
        
        //entityManager.getTransaction().begin();
        Query q = entityManager.createQuery("delete from Record r where r.technologyId=:technologyId");
        q.setParameter("technologyId", technologyId);
        int count = q.executeUpdate();
        //entityManager.getTransaction().commit();
        log.debug("Deleted {} entries from queue {}", count, technologyId);
        
        //entityManager.close();
    }
    
    @Override
    public String doProcess() throws Exception {
        log.debug("doProcess()");
        out.println("technologyIds=" + technologyIds);
        log.info("technologyIds=" + technologyIds);
        try {
            String[] parts = technologyIds.split(",");
            for (String part: parts) {
                long technologyId = Long.parseLong(part);
                clearQueue(technologyId);
            }
        } catch (NullPointerException | NumberFormatException e) {
            throw new IllegalStateException("technologyIds must be set to a string of comma-separated numbers.", e);
        }
        return "COMPLETED";
    }

}

As you can see some lines are commented out - these are the ones I am experimenting with. So if I run the code as-is, I get an exception telling me that the update query requires a transaction. This is regardless of which of the two persistence units in my project I use (one is configured for JTA, the other is not).

javax.persistence.TransactionRequiredException: Executing an update/delete query

It also does not matter whether I uncomment the transaction handling code begin/commit. I still get the same error that a transaction is required to run the update query.

Even when I try to circumvent CDI and JTA completely by creating my own EntityManager via the Persistence API (and close it afterwards, respectively) I do get the very same exception.

So how can I run this delete query or other update queryies from within the batch job?

Queeg
  • 7,748
  • 1
  • 16
  • 42

2 Answers2

1

I'd suggest using plain jdbc to run this delete query, with either auto commit or manual transaction commit.

During the batchlet processing, the incoming transaction is suspended. So the entity manager does not have a transaction context.

cheng
  • 1,076
  • 6
  • 6
  • I thought of running without JPA but was afraid CMT would spoil my datasource. But I just found another way going for JPA with Bean Managed Transaction. – Queeg Feb 06 '23 at 21:55
  • BTW, https://download.oracle.com/otn-pub/jcp/batch-1_0_revA-mrel-spec/JSR_352-v1.0_Rev_a-Maintenance_Release.pdf?AuthParam=1675714365_77d88e32217ae56fdb24d056c9e1efae only details in chapter 9.7 Transactionality only Chunk based processing. There is no mention of Batchlets unfortunately. – Queeg Feb 06 '23 at 22:19
  • Many tasks done by batchlet don't involve transaction, and the external resources these tasks work with may not be transactional. For example, it could be copy a large file from one location to the other. So it's really up to the batchlet class to choose to use transaction or not. – cheng Feb 07 '23 at 20:22
  • You are 100% right, it makes sense for batchlets to control their transactions themselves. I think it is worth to be mentioned in an updated version of the spec. – Queeg Feb 08 '23 at 08:05
  • Thanks for the suggestion. Will keep that in mind when working on the next revision. – cheng Feb 09 '23 at 21:38
1

Ultimately I made it work by following this tutorial: https://dzone.com/articles/resource-local-vs-jta-transaction-types-and-payara and going for the Classic RESOURCE_LOCAL Application pattern.

It involves injecting the nonJTA EntityManagerFactory, using that to create the entitymanager and closing it after use. Of course the transaction has to be managed manually but after all now it works.

The essential excerpt of my code looke like this:

@PersistenceUnit(unitName = "...")
private EntityManagerFactory emf;  

@Inject
@BatchProperty(name = "technologyIds")
private String technologyIds;

private void clearQueue(long technologyId) {
    EntityManager entityManager = emf.createEntityManager(); 
    entityManager.getTransaction().begin();
    Query q = entityManager.createQuery("delete from Record r where r.technologyId=:technologyId");
    q.setParameter("technologyId", technologyId);
    q.executeUpdate();
    entityManager.getTransaction().commit();
    entityManager.close();
}
Queeg
  • 7,748
  • 1
  • 16
  • 42