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);
}
}
OpenSessionInViewFiltercommonly 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 theBarobjects can be applied.First You get all the
Fooobject ids instead to get fully objects.Second Use
Fooids collection to iterate thru relatedBarobjects.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:
Example below shows how to solve your task without some performance overheads. But you should understand that if
FooandBartables linked with foreign key constraint, then related record inFootable may be blocked byRDBMSeach time you update row inBartable.