Find Object in List Cacheable using Ehcache in spring boot - spring-boot

I want to use cache in my application using ehcache with spring boot.
So i want to cache a list of users and when admin want to find user by email for example not use JpaRepository but i want to find in list of users cachable.
To cache list of users i write below code
#Override
#Cacheable(cacheNames = "users")
public List<User> getList() {
return userRepository.findAll();
}
To find a user by email i use for instant code like below :
List<User> users = getList();
User userByEmail(String email){
for(User user: users){
if(user.getEmail().equals(email)){
return user;
}
}
return null;
}
I know this is not a good why, but i don't find a good solution.
Anyone help me to use cache correctly and find user using Cacheable list of users.

You should have a method which takes email as an input and returns user with that email from database.
Add #cacheable on that method so that it will only execute an expensive query to the database first time and add the result to the cache .For any subsequent call to the method it will return the data from cache without actually executing the body of the method.
#Cacheable("users")
public User getUserByEmail(String email) {
return userRepository.findUserByEmail(email);
}

You can create a new method as below
#Cacheable(value = "user", key = "#email")
public User findUserByEmail(String email) {
//Logic to retrieve the data
}
The first time you call this method it will fetch the data from the source and popular the Cache. Next time you will get it from the Cache.

Related

Spring Security - How to get authenticated object in non-secure and secure pages?

I have a Spring-boot/Thymeleaf application with two ends point:
1: /int/: requires sso/authorization;
2. /ext/: public pages, everyone can access;
Using a PreAuthenticationFilter, I was able to secure /int/* pages. When an user tries to access the /ext/* pages, I'd like to be able to tell in the controller if the user has previously been authenticated (by accessing a secured page). Currently I save the authenticated Principal object in the HTTP session in UserDetailsService's loadUserDetails(). Just curious if this is the right way (or a better way) to do it.
You can get your authenticated object via #AuthenticationPrincipal annotation instead of getting the object from httpsession and casting it back to your object for every controller method.
Let me give you an example, given login page is a public page and User object as below:
User Class:
public class User implement UserDetails {
String contact;
Integer age;
}
Controller:
#GetMapping(value = "/login")
ModelAndView login(#AuthenticationPrincipal User user) {
if (user == null) {
return new ModelAndView("/login");
} else {
return new ModelAndView(new RedirectView("/home"));
}
}

Incompatible types List and Page

I am developing a webapp using JHipster that manages payments and wanted to filter the payments that a user can see so he can only see his payments. To do this I was following the blog tutorial from Matt Raible on Youtube. He uses findByUserIsCurrentUser() which is generated by JHipster.
List<Blog> blogs = blogRepository.findByUserIsCurrentUser();
When I went to make the same change in my project I found the type that I must return is a Page and I get an incompatible type error, here is my method:
public Page<Payment> findAll(Pageable pageable) {
log.debug("Request to get all Payments");
Page<Payment> result = paymentRepository.findByUserIsCurrentUser();
return result;
}
If I change the findAll(pageable) for findByUserIsCurrentUser() which is declared in the PaymentRepository as follows
public interface PaymentRepository extends JpaRepository<Payment,Long> {
#Query("select payment from Payment payment where payment.user.login = ?#{principal.username}")
List<Payment> findByUserIsCurrentUser();
}
I get the following error:
How can I solve this?
You can fix this in two ways. This is assuming you are using a service, if not it's still pretty similar:
Method 1: If you want your service to return ALL payments for the current user, then you need to modify your service and probably your resource to handle a list instead of a page:
public List<Payment> findAll() {
log.debug("Request to get all Payments");
List<Payment> result = paymentRepository.findByUserIsCurrentUser();
return result;
}
And in your resource:
public List<Payment> getAllPayments() {
log.debug("REST request to get all Payments");
List<Payment> payments = paymentService.findAll();
return payments;
}
Alternatively, you can change "pagination": "pagination" to "pagination": "no" in .jhipster/Payment.json and rerun the generator. This will regenerate the service, resource and repository without pagination.
Method 2: If you want your service to be paginated, you need to change your repository method to accept the Pageable object, and return a page:
public interface PaymentRepository extends JpaRepository<Payment,Long> {
#Query("select payment from Payment payment where payment.user.login = ?#{principal.username}")
Page<Payment> findByUserIsCurrentUser(Pageable pageable);
}
Then you need to pass the pageable object in from your service:
public Page<Payment> findAll(Pageable pageable) {
log.debug("Request to get all Payments");
Page<Payment> result = paymentRepository.findByUserIsCurrentUser(pageable);
return result;
}
This will return a subset of results, based on whatever the pageable object contains. This includes size, page number, offset, and sorting. For example, a size of 20 and a page number of 0 will return the first 20 results. Changing the page number to 1 will return the next 20 results. These parameters are generally passed in from the front end to your API - you will notice that getAllPayments() in PaymentResource has a Pageable parameter.

#cacheput is not updating the existing cache

I am working with Spring 4 and Hazelcast 3.2. I am trying to add a new record to existing cache with below code. somehow cache is not getting updated and at the same time I don't see any errors also. below is the code snippet for reference.
Note:- Cacheable is working fine, only cacheput is not working. Please throw light on this
#SuppressWarnings("unchecked")`enter code here`
#Transactional(readOnly = true, propagation = Propagation.REQUIRED)
#Cacheable(value="user-role-data")
public List<User> getUsersList() {
// Business Logic
List<User> users= criteriaQuery.list();
}
#SuppressWarnings("unchecked")
#Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
#CachePut(value = "user-role-data")
public User addUser(User user) {
return user;
}
I had the same issue and managed to solved it. The issue seemed to be tied to the transaction management.
Bascially updating the cache in the same method where you are creating or updating the new record does not work because the transaction was not committed. Here's how I solved it.
Service layer calls repo to insert user
Then go back to service layer
After the insert /update db call
In the service layer I called a refresh cache method
That returned the user data and this method has the cacheput annotation
After that it worked.
An alternative approach is you could use #CacheEvict(allEntries = true) on the method used to Save or Update or Delete the records. It will flush the existing cache.
Example:
#CacheEvict(allEntries = true)
public void saveOrUpdate(Person person)
{
personRepository.save(person);
}
A new cache will be formed with updated result the next time you call a #Cacheable method
Example:
#Cacheable // caches the result of getAllPersons() method
public List<Person> getAllPersons()
{
return personRepository.findAll();
}

Spring + Hibernate : LazyInitializationException

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();
}

Lazy Initialization Hibernate in Struts Spring Hibernate

I save current user in session, and when i use current user (example:user.getRole.getRoleName()), i got LIE. How can i solve this problem, my code is like this
Controller:
public String home(){
Users users = userService.getCurrentUser();
if(users.getRole().getRoleName().equals("admin")){ //causing LIE
....
}
UserService :
#Override
public Users getCurrentUser(){
session = ActionContext.getContext().getSession();
return (Users) session.get("user");
}
But, when i change userService.getCurrentUser() to be like this, error is resolved but i think this is not a right manner, because it need connection to database every time i use current user.
#Override
public Users getCurrentUser(){
session = ActionContext.getContext().getSession();
return daoManager.getDetailUser(((Users) session.get("user")).getUsername());
}
DaoManager.getDetailUser is like this
#Override
public Users getDetailUser(String username) {
try {
Users user = (Users)sessionFactory.getCurrentSession().createQuery("SELECT U FROM Users U WHERE U.username=:USERNAME")
.setParameter("USERNAME", username)
.uniqueResult();
return user;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
is there any other better way to solve this problem? Thank you.
The easiest way to resolve this is just to access the lazy fetched field before the session is closed:
#Override
public Users getCurrentUser(){
session = ActionContext.getContext().getSession();
Users user = (Users) session.get("user");
user.getRole(); //Accessed to resolve lazy field
return user;
}
I do not recommend FetchType.EAGER. With that you cannot control access, it is always fetched whether you need it or not.. Add a few EAGER fields to your data model and suddenly your fetching the entire database for the simplest requests..
For queries you can also use JOIN FETCH
The most likely explanation will be that Spring closes the current session when you are exiting the service layer (UserService), however, after this happens, because Hibernate attempts to lazily load the children objects to avoid unnecessarily loading data (see also What is lazy loading in Hibernate?).
To avoid this, you could either ensure that hibernate does not do lazy loading by specifying fetch=FetchType.EAGER on the role, or use the OpenSessionInView pattern (however, that's sometimes considered an antipattern, see Why is Hibernate Open Session in View considered a bad practice?).

Resources