2
votes

In a spring/hibernate application I have some cronjobs running every night. One of them should do its work in multiple transactions like this:

@Scheduled(cron = "...")
public void cron ( ) 
{
    batchJob();
}

@Transactional(propagation = Propagation.NEVER)
public void batchJob ( )
{
    List<Customer> customers = getCustomers();
    for (Customer customer : customers
    {
        doSomething(customer);
    }
}

@Transactional(readOnly = true)
protected List<Customer> getCustomers ( )
{
    return customerRepository.getCustomers();
}

@Transactional
protected void doSomething (Customer customer )
{
         // LazyInitializationException 
         customer.getAddress();
         // ...
}

parts of my spring config:

<!-- Transaction -->
<tx:annotation-driven mode="aspectj" />

<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory" />
</bean>

The key point here is that I don't want to have one long running transaction. So first I get all customers and call a transactional method for each customer. ( I hope the code posted is enough to understand the problem)

Of course I get a LazyInitializationException because Spring is closing the session when the Transaction around 'getCustomer' is committed.

possible solutions i can think of:

  • I can use an OpenSessionInViewInterceptor but this is a web component

  • I can reattach the detached object with session.merge(customer)

  • Is this a case for Propagation.Nestedtransaction? What are the semantics of nested transaction? Most databases don't have nested transaction (postgresql does but I have never used it, it is called two phase commit

  • I can rewrite the method to consume customerId and load the customer again inside the second transaction

Now I have two questions concerning my problem:

  1. How can you write a test to reproduce the bug above?

  2. How can I easily span an open session around this or what is the best way to do a bunch of work in multiple transactions?

1
I guess you meant protected void doSomething (Customer customer ) instead of protected void do (Customer customer )?Oleksandr Bondarenko
Are you sure that aspectj mode is enabled? Since if it would you didn't get LazyInitializationException.Oleksandr Bondarenko
@OleksandrBondarenko: Why? batchJob() has Propagation.NEVER, therefore methods called from it would have their own sessions.axtavt
@axtavt: Actually I meant the following idea (quote from Spring docs): ... self-invocation, in effect, a method within the target object calling another method of the target object, will not lead to an actual transaction at runtime even if the invoked method is marked with @Transactional.Oleksandr Bondarenko
It is aspectj activated, definitely. This is not the problem. I just marked one method with Propagation.NEVER to make clear that there is no class annotation or something else. I have no problem configuring spring. This is more an architectural question. I would like to know how other people doing things like this.Janning

1 Answers

0
votes

I don't quite understand your first question about a test to reproduce it, as far as I understand you just need to call cron() in a non-transactional test.

Regarding possible solutions, I think the best one is to load ids in initial query (as long as it doesn't matter that you get a fresh instance of each Customer for processing).

Note that solution with merge() has no advantages over solution with ids, it just increases memory usage and network bandwidth consumption. Also as far as I remember nested transactions are not supported by JpaTransactionManager.

Theoretically speaking in this case you can use extended persistence context instead of transactional one, but I don't think it's possible to configure Spring Data JPA this way.

EDIT: If you don't like how it looks you can encapsulate low-level details to decouple your business logic from them, something like this:

interface CustomerProcessor {
    public void process(Customer c);
}

public void processCustomersInBatchJob(CustomerProcessor processor) {
    List<Long> ids = ...;
    for (Long id: ids) {
        processCustomer(id, processor);
    }
}

@Transactional
protected void processCustomer(Long id, CustomerProcessor processor) {
    Customer c = getCustomer(id);
    processor.process(c);
}