springhibernatejpatransactionsopen-session-in-view

OpenSessionInView vs. Transactional? (Spring/Hibernate/JPA)


I have a JPA entity with Lazy loaded collection on it. I do not need the collection every time.

@Entity(name = "Foo")
@Access(AccessType.FIELD)
@Table(name = "TEST", schema = "TEST")
public class Foo implements Serializable {
    private static final long serialVersionUID = 1L;

    @OneToMany(mappedBy="foo", targetEntity=Bar.class, fetch=FetchType.LAZY, cascade=CascadeType.ALL)
    private List<Bar> bars;
}

@Entity(name = "Bar")
@Access(AccessType.FIELD)
@Table(name = "TEST", schema = "TEST")
public class Bar implements Serializable {
    private static final long serialVersionUID = 1L;

    @ManyToOne(targetEntity = Foo.class)
    @JoinColumn(name = "FOO_ID", referencedColumnName = "ID")
    private Foo foo;
}

I have a few methods on a service class that perform a lot of database interactions and at the end save a Foo entity to the database. I need this to happen for about a 100 items in a collection.

@Service
public class FooService {

    @Autowired
    private FooRepository fooRepository;

    public void processAllFoos() {
        fooRepository.findAll().forEach(foo -> {
            processFoo(foo);
        });
    }

    private void processFoo(Foo foo) {
        foo.getBars().forEach(bar -> {
            // Do a lot of time consuming stuff here that involves
            // entities of other types and modify each bar object
        });
        fooRepository.save(foo);
    }
}

processAllFoos gets called from a @RESTController whenever it gets a request.

However, I do not want processAllFoos to be wrapped in a single database transaction, because that locks up the entire Foo table till the business logic is executed for all Foos.

If I make the processFoo method @Transactional I get the LazyInitializationException which complains that the Hibernate session is non-existent. To make this work I need to make all methods in the call stack @Transactional so that the nested methods can join onto the calling method's transaction. But this locks the entire Foo table as mentioned above.

Adding a OpenSessionInViewFilter for the dispatcher servlet solves my problem but I've read that there are issues with performance and entity detaching/reattaching (which I do in other parts of the application) with this approach.

Is there a way I can do what I want to without using the OpenSessionInView approach? What other vulnerabilities am I adding by using this approach?

Spring/Hibernate 4.x


Based on the answer below, I was able to do the following:

@Service
public class FooService {

    @Autowired
    private FooRepository fooRepository;

    @Autowired
    private TransactionTemplate transactionTemplate;

    public void processAllFoos() {
        fooRepository.findAll().forEach(foo -> {
            transactionTemplate.execute(new TransactionCallback<Object>() {
                public Object doInTransaction(TransactionStatus status) {
                    try {
                        processFoo(foo);
                        status.flush();
                    } catch(Exception e) {
                        status.setRollbackOnly();
                    }
                    return null;
                }
            });
        });
    }

    private void processBar(Foo foo) {
        foo.getBars().foreEach(bar -> {
            // Do a lot of time consuming stuff here that involves
            // entities of other types and modify each bar object 
        });
        fooRepository.save(foo);
    }
}

Solution

  • OpenSessionInViewFilter commonly used to solve LazyInitialization problem in View layer (UI components or page templates), because View layer can't and must not manage transactions directly. In your case another way to get all the Bar objects can be applied.

    First You get all the Foo object ids instead to get fully objects.

    Second Use Foo ids collection to iterate thru related Bar objects.

    Third If you don't want one BIG transaction then you can use Spring Transaction template to manage transactions explicitly.

    Your code example may look like this:

    @Service
    public class FooService {
    
        @Autowired
        private FooRepository fooRepository;
    
        @Autowired
        private BarRepository barRepository;
    
        @Autowired
        private TransactionTemplate transactionTemplate;
    
        public void processAllFoos() {
            final List < Long > fooIdList = transactionTemplate.execute(new TransactionCallback() {
                public Object doInTransaction(TransactionStatus status) {
    
                    return fooRepository.findIdList();
                }
            });
    
            transactionTemplate.execute(new TransactionCallback() {
                public Object doInTransaction(TransactionStatus status) {
                    barRepository.findByFooIdList(fooIdList).forEach(bar - > {
                        processBar(bar);
                    });
                    return null;
                }
            });
    
        }
    
        private void processBar(Bar bar) {
            // Do a lot of time consuming stuff here that involves
            // entities of other types and modify each bar object
            barRepository.save(bar);
        }
    }
    

    Example below shows how to solve your task without some performance overheads. But you should understand that if Foo and Bar tables linked with foreign key constraint, then related record in Foo table may be blocked by RDBMS each time you update row in Bar table.