Reputation: 994
I want to switch from EAGER to LAZY fetching on my JPA entity, but then I get an error. I have tried a lot of solutions and been searching for a while now for a solution that works - can't find anything that works on Google... And I don't want the entity to join fetch either.
Error: org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: package.User.wallets, could not initialize proxy - no Session
Webcontroller file:
@Controller
public class AppController {
@GetMapping("app/")
public ModelAndView app() {
ModelAndView mv = new ModelAndView();
mv.setViewName("app/index");
User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
List<Wallet> wallets = user.getWallets(); // error on this line
mv.addObject("wallets", wallets);
return mv;
}
}
User files:
@Entity
public class User {
@Id
@GeneratedValue
@Type(type="uuid-char")
private UUID id;
@Column(nullable = false)
private String email;
@Column(nullable = false)
private String firstName;
@Column(nullable = false)
private String lastName;
@Column(nullable = false)
private String password;
// FetchType.EAGER solves the error, but I can't have EAGER due to performance issues.
@OneToMany(mappedBy="user", fetch = FetchType.LAZY)
@Fetch(value = FetchMode.SUBSELECT)
private List<Wallet> wallets;
// ** getters and setters **
}
@Repository
public interface UserRepo extends JpaRepository<User, UUID> {
Optional<User> findByEmail(String email);
}
Wallet files:
@Entity
public class Wallet {
@Id
@GeneratedValue
@Type(type="uuid-char")
private UUID id;
@ManyToOne
@JoinColumn(name="user_id", nullable=false)
private User user;
@OneToOne
@JoinColumn(name="currency_id", nullable=false)
private Currency currency;
@Column(nullable = false)
private double balance;
// ** getters and setters **
}
@Repository
public interface WalletRepo extends JpaRepository<Wallet, UUID> {
Optional<Wallet> findByUserAndCurrency(User user, Currency currency);
}
Thanks for reading all this, I would really appreciate any replies.
Upvotes: 0
Views: 2550
Reputation: 994
This was caused by that the User object in SecurityContextHolder.getContext().getAuthentication().getPrincipal() had already closed the connection even though @Transaction was annotated in Controller class.
I had to change the Controller class like this:
@GetMapping("app/")
public ModelAndView app() {
ModelAndView mv = new ModelAndView();
mv.setViewName("app/index");
UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
User user = userRepo.findByUsername(userDetails.getUsername()).orElse(null);
List<Wallet> wallets = user.getWallets(); // no error anymore
mv.addObject("wallets", wallets);
return mv;
}
Hope this can help anyone else, because I couldn't find this mistake anywhere.
Upvotes: 1
Reputation: 332
You have to add org.springframework.transaction.annotation.Transactional
above your AppController
class and then lazy loading will work.
Example:
import org.springframework.transaction.annotation.Transactional;
@Controller
@Transactional
public class AppController {
@GetMapping("app/")
public ModelAndView app() {
ModelAndView mv = new ModelAndView();
mv.setViewName("app/index");
User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
List<Wallet> wallets = user.getWallets(); // error on this line
mv.addObject("wallets", wallets);
return mv;
}
}
If it didn't work, you probably don't have enabled transaction management (this is normally configured by Spring Boot auto configuration). Try to add @EnableTransactionManagement
e.g. above the main application class, where @SpringBootApplication
annotation is used.
You could also try that line below:
Hibernate.initialize(user.getWallets());
Upvotes: 1
Reputation: 1332
The origin of your problem:
By default hibernate lazily loads the collections (relationships) which means whenver you use the collection in your code the hibernate gets that from database, now the problem is that you are getting the collection in your controller (where the JPA session is closed).This is the line of code that causes the exception (where you are loading the comments collection) :
List<Wallet> wallets = user.getWallets();
Solution
you have this on your User entity
@OneToMany(mappedBy="user", fetch = FetchType.LAZY)
@Fetch(value = FetchMode.SUBSELECT)
private List<Wallet> wallets;
use this
@OneToMany(fetch = FetchType.EAGER, mappedBy = "user", cascade = CascadeType.ALL)
private List<Wallet> wallets;
Collections are lazy-loaded by default
UPDATED Solution
This particular case will be solved by adding toString() in User
to re-define over Lombok toString()
keep your fetchType.LAZY
instead fetchType.EAGER
and
add toString method to your User Entity
@Override
public String toString() {
return "User [id=" + id + ", email=" + email+ ", firstName=" + firstName+ ", lastName=" + lastName + ", password=" + password+ "]";
}
and add to you Wallet Entity :
@ManyToOne(fetch=FetchType.EAGER)
@JoinColumn(name="user_id", nullable=false)
private User user;
try to add @Transactional
to your controller too
Upvotes: 1