0

I have a service in the business tier of a JEE CDI application that needs to perform an intensive work task that needs to be spawned off on a separate thread.

public MyService {
    public void doTask( MyDataKey myDataKey ) {
       MyThread myThread = new MyThread( myDataKey );
       myThread.run();
    }
}

public MyThread : extends Thread {
    MyDataKey myDataKey;
    ...
    public MyThread( MyDataKey inMyDataKey ) {
       this.myDataKey = inMyDataKey;
    }
    public void run() {
        ...
        myWorkerService.doWork( this.myDataKey );
    }
}

public MyWorkerService {
    @PersistenceContext( unitName = "default" ) EntityManager em;
    ...
    @Transactional
    public void doWork( MyDataKey myDataKey ) {
        MyData myData = em.find( myDataKey.getId() );
        myData.setStatus( "Changed" );
        ... (Set values for other properties)
        MyDataChild myDataChild = new MyDataChild( "Some Value" );
        em.persist( myDataChild );
        myData.addChild( myDataChild );
        // According to documentation, flush should update myData
        // and em.merge( myData ) is not needed.
    }
    // When doWork() is exited, the transaction should commit the changes
    // to MyData upon EM flush.
}

I need to be able to use EntityManager to persist new elements related to MyData and to update MyData attributes. Oddly enough, the em.persist( myDataChild ) works and the new data element is stored, but the reference to it and the changes to the myData attributes are not retained.

The problem that I am encountering is how to use CDI to inject the WorkerService to obtain access to the EntityManager within the scope of the run() operation in the MyThread class.

If I create MyWorkerService in MyService and pass it into the thread, the EM flush apparently does not work such that the changes to properties of MyData are not kept after the @Transactional method is exited and the MyThread.run() is completed.

For example:

public MyService {
    @Inject MyWorkerService myWorkerService;
    ...
    public void doTask( MyDataKey myDataKey ) {
       MyThread myThread = new MyThread( myWorkerService, myDataKey );
       myThread.run();
    }
}


public MyThread : extends Thread {
    MyWorkerService myWorkerService;
    MyDataKey myDataKey;
    ...
    public MyThread( MyWorkerService inMyWorkerService, MyDataKey inMyDataKey ) {
       this.myWorkerService = inMyWorkerService;
       this.myDataKey = inMyDataKey;
    }
    public void run() {
        ...
        myWorkerService.doWork( this.myDataKey );
    }
}

The above code is a simplified rendition of the actual code

The problem that appears to happen with this is that because the MyWorkerService is injected outside of the thread run() context, and the EntityManager is similarly created in the parent service context, there is some problem when the EntityManager is used within the @Transactional worker operation that executes within the run() context and the changes to MyData are not propagated to the DB and are not retained by the MyData instance.

The following are questions that I am seeking answers:

1) Is the passing of the MyWorkerService to the MyThread (as shown above) a viable way to set up the worker service and EntityManager for use within the thread.run() ? i.e. that assures CDI injection of each.

2) Why are the changes to the MyData instance within the @Transactional doWork() operation not retained or stored?

3) Is there a better way to acquire the EntityManager within the context of the thread.run() operation that assures better thread safety usage?

4) What code needs to be changed to make this work in a manner that is thread safe and employs correct usage of the EntityManager and @Transactional within the thread.run() invocation?

Chris
  • 311
  • 1
  • 2
  • 10

1 Answers1

0

Java EE discourages use of creating threads, for a lot of good reasons! Java EE is about managed objects, so one should not attempt to create and manage objects like threads!

Fortunately, you're not the first person to need to perform such a task. You have several options for doing work and be in a managed context where your injections still work. Here are a few more common ones:

Good luck! Post back what you end up choosing and why

Jonathan S. Fisher
  • 8,189
  • 6
  • 46
  • 84
  • The JMS and Asynchronous solutions are not really appropriate for my situation. I tried to implement the third option with ManagedExecutorService. I was able to get the injection working for the WorkerService, get the Task class to run within the ManagedExecutorService.submit() and invoke the worker method on the WorkerService within ManagedExecutorService.submit(). However, the @Transactional annotation appears to no longer work at all within the task.run() context. The transaction is declared within the delegate WorkerService and not directly within run(). – Chris Nov 07 '17 at 02:02
  • The article you referenced applies UserTransaction within the task. Do you know if the @Transactional and CMT are not supported within the context of the ManagedExecutorService.submit() ? – Chris Nov 07 '17 at 02:02