Cache the results of two methods in a single cache in Spring - java

I have two methods, the first returning a list of elements and the second returning a single element:
List<User> getUsersFromExternalSystem(List<Integer> userIds);
User getUserFromExternalSystem(Integer userId);
I would like Spring to cache the results of these two methods, so that when the list of elements method (getUsersFromExternalSystem()) is called it caches the results for the provided ids (userIds) and when the single element method (getUserFromExternalSystem()) is called with the id previously provided to the list of elements method it uses the cache.
I can simply apply #Cacheable to these methods, then (if I understand correctly) when I call:
getUsersFromExternalSystem(Arrays.asList(1, 2))
the results will be cached but when I call
getUserFromExternalSystem(1);
the cache will not be used. How this be done in Spring?

You can use following approach. Only first method getUser(Integer id) is cacheable, and second method just combines the results of getUser invocations.
#Service
#Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
class UserService {
private final UserService self;
#Autowired
public UserService(UserService userService) {
self = userService;
}
#Cacheable(cacheNames = "users", key = "id") // assuming that you've already initialized Cache named "users"
public User getUser(Integer id) {
return new User(); // ... or call to some DataSource
}
public List<User> getUsers(List<Integer> ids) {
List<User> list = new ArrayList<>();
for (Integer id : ids) {
list.add(self.getUser(id));
}
return list;
}
}
The trick with injecting a bean into himself and calling
self.getUser(id) instead of this.getUser(id)
is required because #Cacheable will be actually invoked only when used on a Spring proxied bean, and this is not a proxy. More details here Transactions, Caching and AOP: understanding proxy usage in Spring

Related

Spring Data findById returns cached value instead of database one

I am preparing notification system for API which I've build before.
Basically I have an aspect which listens on projectRepository.save method. What I want to achieve is check project status in an entity which is a parameter for save method with original status from database record. What I have notice is that when I search for the DB record by id it returns cached value so it is always the same as the object which is in save method even if database still have old value. Can I force Spring Data Jpa to return database record instead of cached entity?
#Aspect
#Component
#RequiredArgsConstructor
public class NotificationAspect {
private final UserService userService;
private final ProjectRepository projectRepository;
private final NotificationService notificationService;
#Pointcut("execution(* *com.stars.domain.project.ProjectRepository.save(..))")
public void projectSavePointcut() {}
#Before("projectSavePointcut()")
public void sendNotificationOnStatusChange(JoinPoint joinPoint) {
if(joinPoint.getArgs().length > 0 && joinPoint.getArgs()[0] instanceof Project) {
Project projectToUpdate = (Project) joinPoint.getArgs()[0];
Optional<Project> oldProject = projectRepository.findById(projectToUpdate.getProjectId());
if(oldProject.isPresent() && !oldProject.get().getStatus().equals(projectToUpdate.getStatus())) {
notificationService.saveNotification(
MessageFormat.format("Project: {} status has been changed from: {} to: {}",
projectToUpdate.getName(),
oldProject.get().getStatus(),
projectToUpdate.getStatus()),
List.of(userService.getUser(projectToUpdate.getCreatedBy())));
}
}
}
}
This line always returns true even if database record has different value.
oldProject.get().getStatus().equals(projectToUpdate.getStatus())
I can think of two ways.
First, if you're interested only in status field, you can create a custom native query in a repository, which will bypass EntityManager, for example like this:
#Query("SELECT p.status FROM projects p WHERE p.id = :id", nativeQuery = true)
String getProjectStatusById(#Param("id") String projectId);
Second looks like a bad idea, but it should work - you can make the entity manager's cache detach all managed entities, so it will be forced to make a DB call again.
For this inject EntityManager in your aspect bean and call its .clear() method right before calling projectRepository.findById method.

Reactor : How to convert a Flux of entities into a Flux of DTO objects

I have a User entity and a Role entity. The fields are not important other than the fact that the User entity has a role_id field that corresponds to the id of its respective role. Since Spring Data R2DBC doesn't do any form of relations between entities, I am turning to the DTO approach. I am very new to R2DBC and reactive programming as a whole and I cannot for the life of me figure out how to convert the Flux<User> my repository's findAll() method is returning me to a Flux<UserDto>. My UserDto class is extremely simple :
#Data
#RequiredArgsConstructor
public class UserDto
{
private final User user;
private final Role role;
}
Here is the UserMapper class I'm trying to make :
#Service
#RequiredArgsConstructor
public class UserMapper
{
private final RoleRepository roleRepo;
public Flux<UserDto> map(Flux<User> users)
{
//???
}
}
How can I get this mapper to convert a Flux<User> into a Flux<UserDto> containing the user's respective role?
Thanks!
Assuming your RoleRepository has a findById() method or similar to find a Role given its ID, and your user object has a getRoleId(), you can just do it via a standard map call:
return users.map(u -> new UserDto(u, roleRepo.findById(u.getRoleId())));
Or in the case where findById() returns a Mono:
return users.flatMap(u -> roleRepo.findById(u.getRoleId()).map(r -> new UserDto(u, r)));
You may of course want to add additional checks if it's possible that getRoleId() could return null.
Converting the data from business object to database object :
private static UserDAO covertUserBOToBUserDAO(UserBO userBO){
return new UserDAO(userBO.getUserId(), userBO.getName(), userBO.getMobileNumber(),
userBO.getEmailId(), userBO.getPassword());
}
Converting the data from database object to business object :
private static Mono<UserBO> covertUserDAOToBUserBO(UserDAO userDAO){
return Mono.just(new UserBO(userDAO.getUserId(), userDAO.getName(),
userDAO.getMobileNumber(), userDAO.getEmailId(), userDAO.getPassword()));
}
Now in service (getAllUsers) asynchronously:
public Flux<UserBO> getAllUsers(){
return userRepository.findAll().flatMap(UserService::covertUserDAOToBUserBO);
}
Since flatMap is asynchronous so we get the benefit from asynchronous operation for even converting the object from DAO to BO.
Similarly if saving data then I tried below :
public Mono<UserBO> saveUser(UserBO userBO)
{
return
userRepository.save(covertUserBOToBUserDAO(userBO)).flatMap(UserService::covertUserDAOToBUserBO);
}

Handling Java 8 Optional with Spring cache

Considering a service class that can insert and retrieve objects and use Spring cache abstraction, how can I annotate methods in a way that an Optional is returned?
class MyServiceImpl implements MyService {
private static final String CACHE_NAME = "itemCache";
#Override
#Cacheable(CACHE_NAME)
public Optional<Item> findById(Long id) {
// access the repository to retrieve the item
}
#Override
#CachePut(cacheNames = CACHE_NAME, key = "#item.id")
public Item insertItem(Item item) {
...
}
}
In the above example, a ClassCastException is thrown because insertItem puts an Item instance in the cache, and findById expects an Optional that may contain an Item instance.
Just a follow-up on the comment to give a definitive answer to this one. We do as of Spring Framework 4.3 RC2

Multiple SessionFactories' sessions together

I am trying to implement a sharded Hibernate logic. All Databases have same table called MyTable which is mapped to MyClass through Hibernate POJO.
public class SessionFactoryList {
List<SessionFactory> factories;
int minShard;
int maxShard;
// getters and setters here.
}
In my Dao implementation, I have a method getAll which is following -
public class MyClassDao {
#Autowired // through Spring
private SessionFactoryList list;
List<MyClass> getAll() {
List<MyClass> outputList = new ArrayList<>();
for(SessionFactory s : list.getFactories()) {
Criteria c = s.getCurrentSession.createCriteria(MyClass.class);
outputList.addAll(c.list());
}
return outputList;
}
Here is my test for the corresponding getAll implementation -
public class MyClassTest {
#Autowired
SessionFactoryList list;
#Autowired
MyClassDao myClassDao;
#Test
void getAllTest() {
Session session1 = list.getFactories.get(0).getCurrentSession();
session1.beginTransaction();
session1.save(new MyClass(// some parameters here));
Session session2 = list.getFactories.get(1).getCurrentSession();
session2.beginTransaction();
session2.save(new MyClass(// some parameters here));
//Set up done.
assert myClassDao.getAll().size() == 2
}
}
I am using HSQL in-memory database for the test cases.
I have verified that DB connections are correctly setup, but the Assert statement is failing.
'getAll' method of MyClassDao is returning 3 rows. MyClass object inserted in SessionFactory1's session is getting duplicated.
Is there anything I am missing out here?
I found it. The 2 sessionFactory configurations which I used for the test had same Database URL. Hence the same database was queried twice which caused the duplicates.

Lazy initialisation and Spring Data DomainClassConverter

I'm trying to use the DomainClassConverter from Spring Data to load entities in a controller and then use these entities in a service.
The problem is that I get a LazyInitializationException when I access lazy loaded collection from my Service.
Adding Transactional annotation to the controller does not help, I guess the conversion occurs before the start of the controller transaction.
Is there a way to use this converter in this kind of use case ? can I reattach the entity to the current session someway ?
My controller:
#RestController
#RequestMapping("/api/quotation-requests/{id}/quotation")
public class QuotationResource {
#RequestMapping(value = "/lines", method = RequestMethod.POST, params="freeEntry")
#Timed
public ResponseEntity<PricingLineDTO> addFreeEntryLine(#PathVariable("id") Quotation quotation, #RequestBody PricingLineDTO pricingLineTo)
{
PricingLine pricingLine = conversionService.convert(pricingLineTo, PricingLine.class);
pricingLine = quotationService.addFreeLineToQuotation(quotation, pricingLine);
return new ResponseEntity<>(conversionService.convert(pricingLine, PricingLineDTO.class), HttpStatus.OK);
}
}
The service:
#Service
#Transactional
public class QuotationService {
public PricingLine addFreeLineToQuotation(Quotation quotation, PricingLine pricingLine) {
quotation.getPricingLines().add(pricingLine); // org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: x.y.z.Quotation.pricingLines, could not initialize proxy
}
}
And the entity
#Entity
public class Quotation {
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<PricingLine> pricingLines = new ArrayList<>();
}
If it is not possible, what is my best alternative :
make the controller transactionnal, inject data repositories in it and still offer a Service API that takes Entities parameters instead of IDs.
Cons: controller become transactional, which seem to be commonly considered as a bad practice; it introduces boilerplate code.
make the Service API takes IDs as parameters, letting the controller out of the transaction.
Cons: The API become harder to read and can be error prone as every entities as referred as "Long" object, especially when a Service method needs severals entities as input. For example:
void addUserToGroup(Long userId, Long groupId)
One could easily switch parameters.

Categories