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();
}
Related
#Cacheable(cacheNames = "BooksCache", key = "#id")
public Book findById(long id) {
LOGGER.info("Fetching Book From DB For BookId: {}", id);
return bookRepository.findById(id).orElse(null);
}
Cacheable is working fine but when we add a new book or update the existing book cache is not updating
Here is the code for saveOrUpdate() and I have used #CachePut to update the cache but that was not working, Database is getting updated but cache is not updating
#Transactional
#CachePut(cacheNames = "BooksCache", key = "#book.id")
public Book saveOrUpdateBook(Book book) {
return bookRepository.save(book);
}
I have tried #EnableTransactionManagement along with #Transactional Annotation.
I have also tried spring boot starter cache instead of redis cache,
But that was not working
You must call #Cachable- methods from another class. Otherwise the cache proxy will not work and the cache doesn't change/trigger.
While using Spring's #Cacheable, how to make sure the cache does not last longer than the actual session timeout?
Suppose your caches are defined as below,
#Cacheable("cacheName1")
public Map<String, List<String>> getMethod1(){
}
#Cacheable("cacheName2")
public Map<String, List<String>> getMethod2(){
}
then call below method while the user clicks logout / session expires.
#CacheEvict(value = { "cacheName1", "cacheName2"}, allEntries = true)
public void evictAllCache(){
logger.info("All Cache Evict");
}
Extend CacheManager to handle bucket's name, e.g. #session_#name
Extend HttpSessionListener to make a clean up when session is destroyed
please find my draft sample of explicit cache per session below:
https://gist.github.com/pyanoveugen/b360622dc76136064b0215136f402837
I use a hibernate as JPA provider
#RestController
public class RestController {
private final TestService testService;
#PostMapping(value = "/file/{entityId}", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public void test(#PathVariable #NotNull UUID entityId) {
testService.delete(entityId);
}
}
class TestService {
#AutoWired
EntityRepository repo; // <- Crud repository from Spring Data
public void delete(UUID id2){
//if row not exists with id == id2
throw NoFoundException
// else
//remove from database using repo.
}
}
And how to resolve the following case:
"if row not exists with id == id2 " evaluated to false, because object exists in fact.
Other thread deleted that row.
"remove from database using repo" <- error, there is no such row because it was removed by other thread in the step 2.
You can use #Transactional on your Service methods to ensure your database operations run in a transaction. By default, you can roll back the transaction if you throw a unchecked exception inside the annotated method. You can also specify on which exceptions to rollback using #Transactional's rollbackFor Parameter
Not sure why you've got a delete method that is basically doing exactly the same as the SimpleJpaRepository delete method, so for starters I'd change your code to
repo.delete(entityId)
and get rid of test service.
If you are worried about getting a EmptyResultDataAccessException when there is no data to delete, either catch the exception and ignore it or use pessimistic locking on whatever else is doing deletes, as explained
here
You can use the annotation #Transaction for your service method delete(UUID id2).Default propagation of #Transaction is Propagation.REQUIRED which means that if you get an existing transaction it continues that and if you do not have existing transaction it will create one for you.
Based on an article, Better application events in Spring Framework 4.2, I set up all related classes. The most of my code works as desired with an exception in a method of a listener.
The controller:
#PostMapping("/Foos")
public ResponseEntity<Foo> handle(#RequestBody Foo foo){
Optional<Foo> f = fooService.save(foo);
return f.isPresent() ? new ResponseEntity<>(f, HttpStatus.OK) :
new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
The service:
#Transactional
public Optional<Foo> save(Foo foo){
foo = fooRepository.save(foo);
publisher.publishEvent(new FooEvent(foo));
return Optional.of(foo);
}
Without #Transcational in the above method, the following method won't be triggered.
The Listener
#Async
#TransactionalEventListener(condition ="#event.ok", phase = TransactionPhase.AFTER_COMMIT)
public void handle(FooEvent event){
Product product = new Product(event.getData());
productService.save(product);
}
The ProductService
#Transactional(propagation = Propagation.REQUIRES_NEW)
public Optional<Product> save(Product product){
product = productRepository.save(product);
return Optional.of(product);
}
The Product data isn't saved at all although the listener method is invoked. The code is run in a Spring Boot app BTW. I haven't found any related information online yet. How to solve this problem?
The solution might be different depending on what you want to achieve:
If you want to save product within the scope of existing transaction (where you published an event) then just change the phase to TransactionPhase.BEFORE_COMMIT and you should be good.
If you want to save product within the new independent transaction just after the previous one then add a #Transactional(propagation = Propagation.REQUIRES_NEW) to your handle method and left everything else as is.
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();
}