My application has a monolithic application that is provided to spring Ehcache.It is worked correctly.It is important to know that the cache config is at service method and the lazy loading is true for all of object that is queried from DB.like this:
#Transactional
#Override
#Caching(evict = { #CacheEvict(value = "schoolCache", key = "#school.id")})
public Integer save(School school) {
// Code here
}
It is clear what is saved in the Ehcache is lazy instance of school.There is ModelMapper at controller layer to exchange data between Model and ViewModel like this:
#RequestMapping(value = "/load/{Id}", method = RequestMethod.GET)
#ResponseBody
public SchoolViewModel load(#PathVariable Integer Id) {
SchoolViewModel schoolViewModel = ModelMapper.map(schoolService.loadByEntityId(Id), SchoolViewModel.class);
return schoolViewModel;
}
If SchoolViewModel has an attribute of object that is into school is fetched from DB at controller by ModelMapper.
So i cluster the application and config Ehcache to Redis.
What is the problem? At the beginning when a request query from SchoolService,this service query from DB and because the session of hibernate has been opening,ModelMapper maps Model to ViewModel successfully.at the second time the service method that is called with id as same as id at first time get school object from Redis and there is not session of Hiberante,ModelMapper that want to map an attribute of an object that is into school gets exception.The exception is like this:
Caused by: org.hibernate.LazyInitializationException: could not
initialize proxy - no Session at
org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:165)
at
org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:286)
at
org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:185)
So how do i do? What is the best way that helps me?
Thanks a lot
You need to initialize all associations that you further need in the service method:
schoolService.loadByEntityId(Id), SchoolViewModel.class)
So, you have multiple options:
You can use JOIN FETCH with a JPQL query.
You can use Hibernate.initialize(proxy) or Hibernate.unproxy(proxy).
You can simply navigate the LAZY associations.
You can use a DTO projection and you will never bump into any LazyInitializationException.
Related
In our Angular + Spring boot application application, we have 2 Controllers (2 Services are internally referenced). In first controller, We are sending a File from UI and reading the content of the file , query an external application and retrieve a set of data and return only a sub-set of Data, for entering as recommendation for UI fields. why we are returning only sub-set of data received from the external application? Because, we need only those sub-set data for showing recommendations in UI.
Once the rest of the fields are filled, then, we call another controller to generate a report. But, for generation of files, the second service requires the rest of the data from external application, which is received by the first service. I understand that Autowiring the first service in the second service, will create new instance of the first service and I will not get the first service instance, which is used to query the external application. I also like to avoid calling the external application again to retrieve the same data again in the second service. My question is how to fetch the data received by the first service in the second service?
For example:
First controller (ExternalApplicationController), which delegates loading of loading/importing of data from files
public class Department{
private Metadata metadata; // contains data such as name, id, location, etc.,
private Collection<Employee> employees; // the list of employees working in the department.
}
#RestController
#RequestMapping("/externalApp")
public class ExternalApplicationController{
#Autowired
private ExternalApplicationImportService importService;
#PostMapping("/importDepartmentDataFromFiles")
public Metadata importDepartmentDataFromFiles(#RequestParam("files") final MultipartFile[] files) {
return this.importService.loadDepartmentDetails(FileUtils.getInstance().convertToFiles(files)).getMetadata();
}
}
The first service (ExternalApplicationImportService), which delegates the request to the external application for loading of department data.
#Service
public class ExternalApplicationImportService{
private final ExternalApp app;
public Department loadDepartmentDetails(File file){
return app.loadDepartmentDetails(file);
}
}
The Metadata from the ExternalApplicationController is used to populated UI fields and after doing some operations (filling up some data), user requests to generate a report(which contains details from the employees of that department)
#RestController
#RequestMapping("/reportGenerator")
public class ReportController{
#Autowired
private ReportGenerationService generationService;
#PostMapping("/generateAnnualReports")
public void generateAnnualReports(){
generationService.generateAnnualReports();
}
}
#Service
public class ReportGenerationService{
public void generateAnnualReports(){
//here I need access to the data loaded in the ExternalApplicationImportService.
}
}
So, I would like to access the data loaded in the ExternalApplicationImportService in the ReportGenerationService.
I also see that there would be more services created in the future and might need to access the data loaded in the ExternalApplicationImportService.
How can this be designed and achieved?
I feel that I'm missing something how to have a linking between these services, for a given user session.
Thanks,
Paul
You speak about user session. Maybe you could inject the session of your user directly in your controllers and "play" with it?
Just adding HttpSession as parameter of your controllers' methods and spring will inject it for you. Then you just have to put your data in the session during the first WS call. And recover it from the session at the second WS call.
#RestController
#RequestMapping("/reportGenerator")
public class ReportController{
#PostMapping("/generateAnnualReports")
public void generateAnnualReports(HttpSession session){
generationService.generateAnnualReports();
}
}
Alternatively for the second call you could use:
#RestController
#RequestMapping("/reportGenerator")
public class ReportController{
#PostMapping("/generateAnnualReports")
public void generateAnnualReports(#SessionAttribute("<name of your session attribute>") Object yourdata){
generationService.generateAnnualReports();
}
}
You are starting from a wrong assumption:
I understand that Autowiring the first service in the second service, will create new instance of the first service and I will not get the first service instance, which is used to query the external application.
That is not correct: by default, Spring will create your bean as singleton, a single bean definition to a single object instance for each Spring IoC container.
As a consequence, every bean in which you inject ExternalApplicationImportService will receive the same instance.
To solve your problem, you only need a place in where temporarily store the results of your external app calls.
You have several options for that:
As you are receiving the same bean, you can preserve same state in instance fields of ExternalApplicationImportService.
#Service
public class ExternalApplicationImportService{
private final ExternalApp app;
// Maintain state in instance fields
private Department deparment;
public Department loadDepartmentDetails(File file){
if (department == null) {
department = app.loadDepartmentDetails(file);
}
return department;
}
}
Better, you can use some cache mechanism, the Spring builtin is excellent, and return the cached result. You can choose the information that will be used as the key of the cached data, probably some attribute related to your user in this case.
#Service
public class ExternalApplicationImportService{
private final ExternalApp app;
#Cacheable("department")
public Department loadDepartmentDetails(File file){
// will only be invoked if the file argument changes
return app.loadDepartmentDetails(file);
}
}
You can store the information returned from the external app in an intermediate information system like Redis, if available, or even in the application underlying database.
As suggested by Mohicane, in the Web tier, you can use the http sessions to store the attributes you need to, directly as a result of the operations performed by your controllers, or even try using Spring session scoped beans. For example:
#RestController
#RequestMapping("/externalApp")
public class ExternalApplicationController{
#Autowired
private ExternalApplicationImportService importService;
#PostMapping("/importDepartmentDataFromFiles")
public Metadata importDepartmentDataFromFiles(#RequestParam("files") final MultipartFile[] files, HttpSession session) {
Deparment department = this.importService.loadDepartmentDetails(FileUtils.getInstance().convertToFiles(files));
session.setAttribute("department", department);
return deparment.getMetadata();
}
}
And:
#RestController
#RequestMapping("/reportGenerator")
public class ReportController{
#Autowired
private ReportGenerationService generationService;
#PostMapping("/generateAnnualReports")
public void generateAnnualReports(HttpSession session){
Department department = (Department)session.setAttribute("department");
// Probably you need pass that information to you service
// TODO Handle the case in which the information is not present in the session
generationService.generateAnnualReports(department);
}
}
In my opinion, the second of the proposed approaches is the best one but all are valid mechanisms to share your data between the two operations.
my recommendation for you will be to revisit your design of classes and build a proper relationship between them. I feel you need to introduce the extra logic to manage your temporal data for report generation.
#Mohicane suggested to use HTTP Session in above answer. It might be a possible solution, but it has an issue if your service needs to be distributed in the future (e.g. more than one runnable instance will serve your WEB app).
I strongly advise:
creating a separate service to manage Metadata loading process, where you will have load(key) method
you need to determine by yourself what is going to be a key
both of your other services will utilize it
this service with method load(key) can be marked by #Cacheable annotation
configure your cache implementation. As a simple one you can use In-Memory, if a question becomes to scale your back-end app, you can easily switch it to Redis/DynamoDB or other data storages.
Referances:
Spring Caching
Spring Caching Guide
Latest Spring Boot with JPA and Hibernate: I'm struggling to understand the relationship between transactions, the persistence context and the hibernate session and I can't easily avoid the dreaded no session lazy initialization problem.
I update a set of objects in one transaction and then I want to loop through those objects processing them each in a separate transaction - seems straightforward.
public void control() {
List<> entities = getEntitiesToProcess();
for (Entity entity : entities) {
processEntity(entity.getId());
}
}
#Transactional(value=TxType.REQUIRES_NEW)
public List<Entity> getEntitiesToProcess() {
List<Entity> entities = entityRepository.findAll();
for (Entity entity : entities) {
// Update a few properties
}
return entities;
}
#Transactional(value=TxType.REQUIRES_NEW)
public void processEntity(String id) {
Entity entity = entityRepository.getOne(id);
entity.getLazyInitialisedListOfObjects(); // throws LazyInitializationException: could not initialize proxy - no Session
}
However, I get a problem because (I think) the same hibernate session is being used for both transactions. When I call entityRepository.getOne(id) in the 2nd transaction, I can see in the debugger that I am returned exactly the same object that was returned by findAll() in the 1st transaction without a DB access. If I understand this correctly, it's the hibernate cache doing this? If I then call a method on my object that requires a lazy evaluation, I get a "no session" error. I thought the cache and the session were linked so that's my first confusion.
If I drop all the #Transactional annotations or if I put a #Transactional on the control method it all runs fine, but the database commit isn't done until the control method completes which is obviously not what I want.
So, I have a few questions:
How can I make the hibernate session align with my transaction scope?
What is a good pattern for doing the separation transactions in a loop with JPA and declarative transaction management?
I want to retain the declarative style (i.e. no xml), and don't want to do anything Hibernate specific.
Any help appreciated!
Thanks
Marcus
Spring creates a proxy around your service class, which means #Transactional annotations are only applied when annotated methods are called through the proxy (where you have injected this service).
You are calling getEntitiesToProcess() and processEntity() from within control(), which means those calls are not going through proxy but instead have the transactional scope of the control() method (if you aren't also calling control() from another method in the same class).
In order for #Transactional to apply, you need to do something like this
#Autowired
private ApplicationContext applicationContext;
public void control() {
MyService myService = applicationContext.getBean(MyService.class);
List<> entities = myService.getEntitiesToProcess();
for (Entity entity : entities) {
myService.processEntity(entity.getId());
}
}
I am getting the LazyInitializationException when i try to retrieve information inside a POJO.
User.java
public class User implements java.io.Serializable {
private Set groups = new HashSet(0);
public Set getGroups() {
return this.groups;
}
}
UserController.java
#RequestMapping(value = "/home", method = RequestMethod.GET)
public ModelAndView getHome(HttpServletRequest request) throws Exception {
ModelAndView mv;
User user = SessionUtil.getSessionUser(request);
if (user == null) {
mv = new ModelAndView("redirect:/user/login");
} else {
mv = new ModelAndView("home");
user = this.userService.getUserById(user.getId());
// Exception here
Set<Group> groups = user.getGroups();
mv.addObject("groups", groups);
// This work fine
List<Group> invitation_groups = this.userService.getInvitationGroups(user);
mv.addObject("invitation_groups", invitation_groups);
// This work fine
List<Group> subscription_groups = this.userService.getSubscriptionGroups(user);
mv.addObject("subscription_groups", subscription_groups);
}
return mv;
}
Database
=====
-User-
id
login
=====
-Goup-
id
user (Foreign key to user)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:285)
at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:185)
at model.pojo.User_$$_jvst464_2.getGroups(User_$$_jvst464_2.java)
at controller.UserController.getHome(UserController.java:151)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
I think I understand why I get this exception : I always close the HibernateSession after all my transaction in my DAO so the session can't be open for the POJO request.
In an other hand user.getLogin() for exemple works. I think i dont understand well where the problem is. Is that because it uses a foreign key ?
I think i found a workaround here but I dont know how to implement it and if it's really efficient.
I know that if I remove session.close() from my DAO it will works but it's not the solution.
I hope someone can help me. Thanks.
Solution
Remove all the hand made transactions
Add transactionnal annotation
User OpenSessionInView filter.
Thanks guys.
Why are you handling your session manually? Do you need that?
If not, you should use OpenSessionInView pattern. It will keep your session open until the request ends, but, be careful, you can run in trouble with lots of queries made to the database because the lazy load of collections. So whenever you can, try to fetch your data eagerly if you know that they will be used.
Your user.getLogin() returns a string right? Even if it was the one side of a relationship mapping, it would be fetched eagerly by default.
I'm not used with spring but I think spring has an OpenSessionInView filter to manage your session.
Its normal to handle transaction in API layer and using DTO,
So you have: API -> Service -> DAO.
But since you only have transactional in DAO its probably okai, but then you have to take care of lazyload object in DAO., before transaction is closed.
// after this the transaction is open and closed, user object is hibernate jpa entity you usually get this.
user = this.userService.getUserById(user.getId());
The simplest solution is to loop through and do getId() in DAO, before returning user.
Set<Group> groups = user.getGroups();
for (Group group in groups){
group.getId();
}
I get a LazyInitializationException by combining the conversionservice with a #Transactional behavior in a MVC controller.
Following fails:
#RequestMapping(value = "/{userId}", method = RequestMethod.GET)
#ResponseBody
#Transactional
public JsonUser getUser(#PathVariable("userId") User user) {
// convertToJsonUser triggers lazy loading of User.adresses
return mUserPresenter.convertToJsonUser(user);
}
... with following exception:
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: User.adresses, could not initialize proxy - no Session
But the same code without the conversionservice succeeds:
#RequestMapping(value = "/{userId}", method = RequestMethod.GET)
#ResponseBody
#Transactional
public JsonUser getUser(#PathVariable("userId") Long userId) {
User user = mUserRepository.findOne(userId);
// convertToJsonUser triggers lazy loading of User.adresses
return mUserPresenter.convertToJsonUser(user);
}
and the same code without the transactional behavior succeeds:
#RequestMapping(value = "/{userId}", method = RequestMethod.GET)
#ResponseBody
public JsonUser getUser(#PathVariable("userId") User user) {
// changedConvertToJsonUser DOESN'T trigger lazy loading of User.adresses
return mUserPresenter.changedConvertToJsonUser(user);
}
The conversion seems to occur in its own transaction before the main transaction to be opened by the #Transactional annotation. As a result, the User loaded by the conversionmanager is not bound to the main transaction and lazy loading fails for that reason.
Is that behavior known?
How can I get rid of it?
Did I forget something in the configuration?
Thank you in advance!!!
Controller is not a perfect place to put #Transactional annotations. It would be better to add them to service methods. This may cause problems because you may have a proxy object of controller which is mixed up with proxy from transaction manager and you can not predict what the order of execution will be. So my advice is: try to add a service layer.. even a very simple put there #Transactional annotations and test the code again.
... the conversionservice is per se the way of loading entities ...
No, it's not. It's like the name says: conversion. If you load entities, then that's your choice, not Spring's.
if it is not recommanded to open a transaction in the controller-layer, then why did Spring developers have developed the conversionservice feature?
The service in ConversionService implies that we are not in the controller layer.
The conversion seems to occur in its own transaction before the main transaction to be opened by the #Transactional annotation
There is no transaction opened by #Transactional in this case, because it doesn't work.
Transactions usually work on the service layer. You could have something like:
MyUserService implements UserService {
#Override
#Transactional(readonly = true)
public User getUserById(int id) {
But that's not your problem. Your problem is that the User object is not fully initialized. You would need a transaction on convertToJsonUser.
As a side note: The people who develop Spring prefer using the ids directly and load entities in the controller, not before.
I have an #ModelAttribute("myModel") that is injected into a spring form and also stored as part of the session in a #SessionAttribute("myModel").
At the controller, the model is picked up and changes merged into the #SessionAttribute.
#RequestMapping(value = "/save", method = RequestMethod.POST)
public String save(#ModelAttribute("myModel") MyModel myModel) {
myModel.getSomething();
...
}
The problem is that all my hibernate domain objects are LAZY. Because the Hibernate Session that created myModel no longer exists, when I try to access someting that was not included as part of a fetch statement in the HQL, I get a LazyInitializationException.
Hibernate has a merge() method, how can I use this to merge an object created in a previous hibernate session into the current one so that all the LAZY properties can be accessed? Or is there another way of doing this?
Call merge(), and use its returned value: it's the attached entity containing the values found in the detached entity passed as argument:
Foo modifiedAttachedFoo = session.merge(modifiedDetachedFoo);
modifiedAttachedFoo.getLazyCollection().size(); // no problem: the entity is attached