2

I have a batchEdit(List<E> entity) that calls an edit(E entity) function in a loop, while each edit() has it's own transaction so that failed edits don't rollback the good edits. I currently have it implemented like so:

Option 1

@Stateless
@TransactionManagement( value = TransactionManagementType.CONTAINER )
public class Service<E> {

    @Resource
    private SessionContext context;

    @Override
    @TransactionAttribute( value = TransactionAttributeType.REQUIRES_NEW )
    public E edit( E entity ) {
       //edit code
    }

    @Override
    public List<E> bulkEdit( List<E> entities ) {
       for(E entity : entities){
          //case 1: Regular edit, Does not create a new transaction!
          //edit(entity);

          //case 2: Hacky edit, creates a new transaction
          context.getBusinessObject( Service.class ).editPersistNulls( entity );
       }
    }
}

According to this stackoverflow discussion, The @TransactionAttribute is ignored in my case 1 because it doesn't cross any EJB boundaries, so batchEdit() calls edit() as if it wasn't annotated. Using the context.getBusinessObject() function in case 2 to grab a reference of the bean causes the TransactionManagement annotation to work, but it seems really weird to go through all of that.

Option 2

The other option I have is to change to bean managed transactions:

@TransactionManagement( value = TransactionManagementType.BEAN )

But then I would lose the "JPA Magic" and have to manage the transactions everywhere. I don't think other people on my team would want to go through that, so if there's a better or standard way to do this, any insight is appreciated.

We are using OpenJPA and EJBs, but we are trying to stay close to the JPA standard.

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
GuitarStrum
  • 713
  • 8
  • 24

2 Answers2

1

You can just inject EJB in self.

@Stateless
public class Service<E> {

    @EJB
    private Service<E> self;

    @TransactionAttribute(REQUIRES_NEW)
    public void edit(E entity) {
        // ...
    }

    @TransactionAttribute(NOT_SUPPORTED)
    public void bulkEdit(List<E> entities) {
        for (E entity : entities) {
           self.edit(entity);
        }
    }
}

Better would be to make it @Asynchronous. It's faster.

@Stateless
public class Service<E> {

    @EJB
    private Service<E> self;

    public void edit(E entity) {
        // ...
    }

    @Asynchronous
    @TransactionAttribute(REQUIRES_NEW)
    public void asyncEdit(E entity) {
        // ...
    }

    @TransactionAttribute(NOT_SUPPORTED)
    public void bulkEdit(List<E> entities) {
        for (E entity : entities) {
           self.asyncEdit(entity);
        }
    }
}

It also keeps the original edit() method free from potentially unwanted REQUIRES_NEW transaction attribute as it might be called from other services which of course should stay in same transaction. For @Asynchronous it makes more sense to require a new transaction on every call.

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • What will happen, when i call the asynchronous method twice (or even more often) in short terms. Are the invokations stacked until an overflow occures? – kaiser Sep 18 '17 at 15:39
  • One last question. You use REQUIRED_NEW. Since the calling function has NOT_SUPPORTED wouldn't the REQUIRED attribute be the same? – kaiser Sep 18 '17 at 22:22
  • @kaiser Nope. It's not called on the same instance. I guess you need to take a step back and learn how EJBs actually work: https://stackoverflow.com/q/25514361 – BalusC Sep 19 '17 at 06:01
0

I guess that "hacky" is in the eye of the beholder. context.getBusinessObject exists precisely so that you can do this kind of thing.

The alternative is to use a second class:

@Stateless
public class BulkService<E> {

    @EJB
    private Service<E> service;

    public List<E> bulkEdit( List<E> entities ) {
       for(E entity : entities) {
           service.editPersistNulls( entity );
       }
    }

}

Beware of large entity lists though, as your encompassing transaction can time out.

If you're using a Java EE 7 compliant server implementation then consider using the JSR-352 Batch Applications support.

Steve C
  • 18,876
  • 5
  • 34
  • 37
  • Interesting. We ended up going with the getBusinessObject solution; the only annoying part about that is that it needs the interface of the bean, not the bean's class itself. Passing the bean's class itself outputs a "Component has no such interface: ", whereas passing in the interface it implements works. – GuitarStrum May 10 '17 at 12:14
  • If you eliminate the interfaces altogether then you can use the class name. Only use an interface on an EJB if you're planning a bunch of different implementations, which is not very often. – Steve C May 10 '17 at 13:27
  • I will run that by my team. We had interfaces for potential testing that we haven't done yet, but passing in the interface is going to be a big refactor. – GuitarStrum May 11 '17 at 13:30