Spring Boot EHcache cacheResolver - spring-boot

Use Case:
I have used #Cacheable product repository
In ProductRepository.java
#Cacheable(value = "ProductId")
public ProductEntity findByProductId(long productId);
ProductController
Get API ( Product Id)
Patch API (update the required data)
Product Service
for GET Method
#CacheEvict(cacheNames = { "ProductId" }, allEntries = true)
public ProductView getProductByProductId(long productId)
{
productEntity = productRepository.findByProductId(productId);
}
My question is after updating my single product details (using #Patch) how to update cache for single record only while calling Get API for single Product

You could declare the save method in your repository:
#Override
#CacheEvict(value = "ProductId")
ProductEntity save(ProductEntity product);
And call that method on PATCH.

Related

Spring #Cacheable for method no parameter

I want to cache some db data. for example Cache Customer and use customer.id as the key.
How could I set the key if I want to load all customers (allCustomer() in the code) ?
#Cacheable(value = "customer", key = "#customerID")
public Customer getCustomer(Long customerID) {
return getCustomerData(customerID);
}
// How to setup this key?
#Cacheable(value = "customer", key = "?")
public List<Customer> allCustomer(){
return db.values().stream().collect(Collectors.toList());
}
#CachePut(value = "customer", key = "#customer.id")
public void updateCustomer(Customer customer){
db.put(customer.getId(), customer);
}
#CacheEvict(value = "customer", key = "#customerID")
public void deleteCustomer(Long customerID){
db.remove(customerID);
}
I would recommend using #CachePut instead of #Cacheable. In the case that a new entry is added to the DB from outside of this application instance, the cache would not contain that new value.
You can use #result.id to tell Spring which value to use as a key and I've included a conditional so that you don't get strange errors in case of a null value.
#CachePut(value = "customer", key = "#result.id", condition = "#result != null")
It's impossible to do it for collections with the Spring's annotations - with #Cacheable you'd have just one element in a cache with a computed key and a value with the whole list inside.
If performance is not that important in your app, use getCustomer(...) in a loop.
Otherwise, you'll need to update your cache manually. Unfortunately, Cache interface doesn't provide a method to retrieve all keys/values/key-value pairs from a cache, so a bit of casting is required.
The example for the default in-memory cache (spring.cache.type=simple):
#Autowired
private org.springframework.CacheManager cacheManager;
public List<Customer> allCustomers() {
ConcurrentMap<Long, Customer> customerCache = (ConcurrentMap<Long, Customer>)
cacheManager.getCache("customer").getNativeCache();
if (!customerCache.isEmpty()) {
return new ArrayList<>(customerCache.values());
}
List<Customer> customers = db.values().stream().collect(Collectors.toList());
customers.forEach(customer -> customerCache.put(customer.getId(), customer));
return customers;
}
Or for spring.cache.type=jcache with backed EhCache 3:
#Autowired
private org.springframework.CacheManager cacheManager;
public List<Customer> allCustomers() {
javax.cache.Cache<Long, Customer> customerCache = (javax.cache.Cache<Long, Customer>)
cacheManager.getCache("customer").getNativeCache();
Iterator<Cache.Entry<Long, Customer>> iterator = customerCache.iterator();
List<Customer> cachedCustomers = new ArrayList<>();
while (iterator.hasNext()) {
Cache.Entry<Long, Customer> entry = iterator.next();
cachedCustomers.add(entry.getValue());
}
if (!cachedCustomers.isEmpty()) {
return cachedCustomers;
}
List<Customer> customers = db.values().stream().collect(Collectors.toList());
customers.forEach(customer -> customerCache.put(customer.getId(), customer));
return customers;
}
The same can be done similarly for any other cache type (redis, hazelcast, caffeine etc.).
The corresponding eviction method can be written much easier:
#CacheEvict(value = "customer", allEntries = true)
public void deleteAllCustomers(){
db.removeAll(); //pseudocode
}

Spring Cache property extraction

I have two methods for which i have use #Cacheable annotation.
#Cacheable(value = "product", key = "#id")
public Product getProduct(String id) {
Product product = productRepository.getProductById(id)
return product;
}
and the second method is,
#Cacheable(value = "product", key="#id")
public Integer getProductType(String id) {
return getProduct(id).getProductType();
}
The second method does not work. How can i extract only a property from the "product" cache? In this case, already product for that specific id is cached. i just need only the property from the cache. I am using Hazelcast as Cache provider.
If you want to share the cache between two methods, I think you should do the following:
#Resource
private Service self;
#Cacheable(value = "product", key = "#id")
public Product getProduct(String id) {
Product product = productRepository.getProductById(id)
return product;
}
public Integer getProductType(String id) {
return self.getProduct(id).getProductType();
}
Here is the complete working example that works.

Updating entity with One to One relationship using Spring Data

I have an issue when updating entity with OneToOne relationship, it creates record instead of updating the existing one. Below are the sample entities.
#Entity
#Table(schema = "crm", name = "persons")
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Long getId() {
return id;
}
#OneToOne(mappedBy = "person", fetch = FetchType.LAZY)
public Employee getEmployee() {
return employee;
}
}
#Entity
#Table(schema = "crm", name = "employees")
public class Employee {
#Id
public Long getId() {
return id;
}
#OneToOne
#MapsId //Use the person PK id value as Employee PK id
#JoinColumn(name = "id")
public Person getPerson() {
return person;
}
}
I am using the PagingAndSortingRepository of Spring Data. Below is the service layer to update the entity.
#Override
#Transactional
public EmployeeResponse updateEmployee(Employee aEmployee) {
EmployeeResponse response = new EmployeeResponse();
try {
Optional<Employee> probableEmployee = employeeRepository.findById(aEmployee.getId());
if (!probableEmployee.isPresent()) {
throw new RecordNotFoundException(String.format(MessageConstants.EMPLOYEE_ID_NOT_FOUND, aEmployee.getId()));
}
Employee existingEmployeeToUpdate = probableEmployee.get();
EmployeeEntityHelper.updateExistingEntity(aEmployee, existingEmployeeToUpdate);
existingEmployeeToUpdate = employeeRepository.save(existingEmployeeToUpdate);
response.setSuccessfulResponse(existingEmployeeToUpdate);
} catch (Exception ex) {
LOGGER.error(ex.getLocalizedMessage(), ex);
response.setErrorAttributes(false, ReturnCode.FAILED.getCode(), ex.getLocalizedMessage());
}
return response;
}
The EmployeeEntityHelper.updateExistingEntity(source, target) will simply copy all properties of entities from source to target.
The save() method will generate an insert for Person even if I am explicitly passing the id existing in DB. But for employee it will generate an update which is expected.
Below is the updateExistingEntity() method:
public static void updateExistingEntity(Employee source, Employee target) {
copyProperties(source, target, Arrays,asList("person", "employeeNumber", "hiredDate", "birthDate"));
}
private static void copyProperties(Object aSource, Object aTarget, Iterable<String> aProperties) {
BeanWrapper sourceWrapper = PropertyAccessorFactory.forBeanPropertyAccess(aSource);
BeanWrapper targetWrapper = PropertyAccessorFactory.forBeanPropertyAccess(aTarget);
aProperties.forEach(p ->
targetWrapper.setPropertyValue(p, sourceWrapper.getPropertyValue(p))
);
}
Generally, a new INSERT instead of UPDATEs could be due to:
Either entity passed to save has no ID set, thus persist is called under the hood
Or entity with given ID is not present in the database thus merge fails (and maybe it is persisted as a fallback, dunno)
Check if entities have set ID field.
In your model , override equal/hash-code and just chek the id parameter
maybe it will help you :)
The issue is already fixed, somehow the copyproperties logic is not right in the sense that I am updating/setting the person also.

Caching with Spring and ehcache doesnt work as expected

I have a Product model object like this -
class ProductDTO {
int id;
String code;
String description;
//getters and setters go here
}
I am writing a service (code below) that looks up products by id or code and returns their description. I am using Spring 4 and ehcache to cache the results.
I have 2 methods - one for lookup by id and one for lookup by code - they are getProductByCode and getProductById. Both return the description as a string. They do so by calling getAllProducts() which returns a list of all products. The callers then search the list for a product matching the id or code and return the description.
getAllProducts() also calls 2 methods with #CachePut for each product - to save the description Strings in cache - by key code and id.
Caching works properly if the same arguments are passed for code or id to to the getProductByCode and getProductById methods. But if I pass a different argument, getAllProducts() is called again.
How do I achieve the desired behavior - where every time a call is made to getAllProducts(), all descriptions get cached and a subsequent call looks up the cache rather than going to the repository?
public class ProductServiceImpl implements ProductService {
#Autowired
ProductsRepository ProductRepo;
#Override
public List<ProductDTO> getAllProducts() {
List<ProductDTO> products = ProductRepo.getAllProducts();
for(ProductDTO prodDTO : products) {
String desc = prodDTO.getDescription();
String code = prodDTO.getCode();
int id = prodDTO.getId();
putDescriptionInCache(desc, code);
putDescriptionInCache(desc, id);
}
return products;
}
#CachePut(value = "products", key = "#id")
public String putDescriptionInCache(String description, int id){
return description;
}
#CachePut(value = "products", key = "#code")
public String putDescriptionInCache(String description, String code){
return description;
}
#Override
#Cacheable(value="products", key="#id")
public String getProductById(Integer id) throws NullPointerException {
String dtoDesc = null;
List<ProductDTO> products = getAllProducts();
for(ProductDTO currDTO : products) {
int currId = currDTO.getId();
if(id.equals(new Integer(currId))) {
dtoDesc = currDTO.getDescription();
}
}
return dtoDesc;
}
#Override
#Cacheable(value="products", key="#code")
public String getProductByCode(String code) throws NullPointerException {
String dtoDesc = null;
List<ProductDTO> products = getAllProducts();
for(ProductDTO currDTO : products) {
String currCode = currDTO.getCode();
if(currCode.equals(code)) {
dtoDesc = currDTO.getDescription();
}
}
return dtoDesc;
}
}
As it was commented by M. Deinum, the problem comes from the annotations, like CachePut or Cacheable, being transformed into an aspect at runtime. And the main limitation with that approach is that calls from the same class are not properly captured.
As you replied yourself in the comments section, moving the annotated methods to another type that is injected in the current one solves the problem.

Spring Hibernate - Dao Return By Id

I use Spring & Hibernate and i would like to get a product with his id in my DAO.
#Repository
#Transactional
public class ProductDaoImpl implements ProductDao {
protected final Log logger = LogFactory.getLog(getClass());
#Autowired
private SessionFactory sessionFactory;
public List<Product> getProductList() {
return sessionFactory.getCurrentSession().createQuery("from Product p order by p.productName asc").list();
}
public Product getProductById(int productId) {
String hql = "from Product p where p.productId = :id";
Query query = sessionFactory.getCurrentSession().createQuery(hql);
query.setInteger("id", productId);
return null;
}
}
For example when i would like to get all my products i return list of them (calling function getProductList() ), but now i want to call getProductById but i don't know how i could return something with the "Product" type.
Thanks.
In your getProductById(int productId) method:
return (Product) query.uniqueResult();

Resources