My expectation is that a lazy loaded collection should be fetched when the collection is accessed within a transactional scope. For example, if I want to fetch a collection I can call foo.getBars.size()
. The absence of an active transaction should result in an exception with an error message like
failed to lazily initialize a collection of bars: .... could not initialize proxy - no Session
However, I noticed that the behavior is different in my latest application. I'm using Spring Boot 1.5.1 with the "data-jpa" starter. I have used Spring Boot in the past, but the data-jpa starter is new for me.
Consider the following case. I have a lazy loaded ManyToMany collection.
@SuppressWarnings("serial")
@Entity
@Table(name = "foo")
public class Foo implements java.io.Serializable {
....
private Set<Bar> bars = new HashSet<Bar>(0);
....
@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinTable(name = "foo_bar_map",
joinColumns = {@JoinColumn(name = "foo_id", nullable = false, updatable = false)},
inverseJoinColumns = {@JoinColumn(name = "bar_id", nullable = false, updatable = false)})
public Set<Bar> getBars() {
return this.bars;
}
public void setBar(Set<Bar> bars) {
this.bars = bars;
}
My service method is NOT marked as Transactional but I am accessing a lazy loaded collection
@Service
public class FooServiceImpl implements FooService {
@Autowired
private FooRepository fooRepo;
@Override
public FooDTO findById(int fooId) {
Foo foo = fooRepo.findOne(fooId);
// The FooDTO constructor will access foo.getBars()
return new FooDTO(foo);
}
And for context on the FooDTO constructor
public FooDTO(Foo foo) {
...
for (Bar bar : foo.getBars()) {
this.bars.add(bar);
}
}
Contrary to my expectation and past experience, this code executes successfully and fetches the collection. Further, if I throw a breakpoint in my service method, I can step through the code and see the SQL statements in my logs that fetch the bars after my call to the fooRepo. After my call to fooRepo, I expect the transaction to be closed.
What's happening here?
If you're using Spring without Spring Boot, you need to activate the transaction management by annotating your application class with @EnableTransactionManagement. Here you can see a simple example of a service with a transactional method.
"@Transactional" as itself on any isolation level doesn't enabling any locking. To achieve locking behaviour you should use "@Lock" annotation or use " for update" in your query.
By default, JPA uses the lazy fetch strategy in associations of type @ElementCollection. Thus, any access to the collection in a closed Persistence Context will result in an exception. This test throws an exception when we try to access the phone list because the Persistence Context is closed.
The usage of the @Repository annotation or @Transactional . @Repository is not needed at all as the interface you declare will be backed by a proxy the Spring Data infrastructure creates and activates exception translation for anyway.
Spring Boot uses an OpenEntityManagerInView interceptor by default. You can turn it off by setting the property spring.jpa.open-in-view
to false.
See the documentation for the reference about this (and other) JPA properties.
You could turn on logging to check if a Transaction is being opened.
org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction
or
org.hibernate.engine.transaction.internal.jta.JtaTransaction
Also, you could set a breakpoint and use this static method to check if a transaction is open.
org.springframework.transaction.support.TransactionSynchronizationManager.isActualTransactionActive()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With