After #CachePut findAll() doesn't give result from cache - java

I am learning spring boot caching to apply this concept in our organization's project and I made a sample project called employe cache. I have four methods in my controller and service component insert, update, get, and getAll.For insert and get #Cacheable is working perfectly. Now I am calling getAllEmployee() first time then it is fetching data from the database. After that I am trying to update with #CachePut it updates the value in the database and again I am calling getAllEmployee() then it didn't return updated value from the cache. I also refer to the documentation for #CachePut. I also refer to some other documents like this and this but I didn't solve my problem. Also, When I am calling, no error is raised.
What I Tried is
These are my two APIs from EmplyeeController.java
#PostMapping(value = "/updateSalary")
private Boolean updateSalary(#RequestParam int salary, #RequestParam Integer id) {
return empService.updateSalary(salary, id);
}
#GetMapping(value = "/getAllEmployee")
private Object getAllEmployee() {
List<EmployeeMapping> empList = empService.getAllEmployee();
return !empList.isEmpty() ? empList : "Something went wrong";
}
These are my two methods from EmployeeService.java. I applied different keys to update the method but didn't work. My getAll() method has no parameter so I tried all the keys techniques for no parameter methods from here then also I didn't get any results.
#CachePut(key = "#root.method.name")
public Boolean updateSalary(int salary, int id) {
System.err.println("updateSalary method is calling in service");
if (empRepo.salary(salary, id) != 0) {
return true;
}
return false;
}
#Cacheable(key = "#root.method.name")
public List<EmployeeMapping> getAllEmployee() {
return empRepo.findAllEmployee();
}
These are my two methods from EmployeeRepository.java. I used #SqlResultSetMappings and #NamedNativeQueriesin EmployeeMetaModel.java with EmployeeMapping.java but there is no error in native query in EmployeeMetaModel.java because it's giving result from database.
#Transactional
#Modifying
#Query("update employee_cache e set e.salary = ?1 where e.id = ?2")
int salary(int salary, int id);
#Query(name = "EmployeeListQuery", nativeQuery = true)
List<EmployeeMapping> findAllEmployee();
Kindly help me to get rid of this I just need an updated value from the cache using getAllEmployee() after updateSalary() called.

There is an issue with how you've defined caching via annotations. Your #CachePut and #Cacheable don't use the same cache key. What you should actually have is something like this:
#CachePut(value = "employees", key = "T(org.springframework.cache.interceptor.SimpleKey).EMPTY")
public List<EmployeeMapping> updateSalary(int salary, int id) {
// update salary and return the list of employees
}
#Cacheable(value = "employees")
public List<EmployeeMapping> getAllEmployee() {
// return the list of employees
}
Here #CachePutand #Cacheable have the same cache key.d Now, when you call the updateSalary() method, #CachePut will replace the existing cached value for key "employees", with the result of the method i.e. list of employees with updated salary.

Related

Spring Cache - partial cache elements in list inside of DTO

So from my understanding in bellow code, when string ID is already in cache as key, DTO value gets returned from cache, instead of calling externalService method:
#Cachable("elements")
public ElementsDTOResponse getById(String id) {
return externalService.getById(id);
}
But can i use cache, if externalService takes list of IDs instead? My external service takes DTO in form:
ElementsDTO { List < ElementDTO > elementDTOList; }
Where ElementDTO { String id }
So can i do it, so externalService.getByIds(elementsDTO) will only be called with list of IDs that are not already in cache? Also if all IDs in DTO are already in cache (as keys) then do not call externalService at all, instead get values from cache. Something like that
#Cachable("elements")
#CachePut(key = "#elementsDTO.elementDTOList.id")
public ElementsDTOResponse getById(ElementsDTO elementsDTO) {
return externalService.getAllByIds(id);
}
Is it possible to do that simply with Spring Cache?

Why does not delete method of spring data (JPA) have any return values?

I have used a delete method of Spring Data JPA in the service layer, but I wonder why neither the deleteById method nor delete method has any return values.
If we inspect the implementation of the delete method carefully, there is an if statement that when the entity to be deleted doesn't exist returns nothing.
public void delete(T entity) {
Assert.notNull(entity, "Entity must not be null!");
if (entityInformation.isNew(entity)) {
return;
}
Class<?> type = ProxyUtils.getUserClass(entity);
T existing = (T) em.find(type, entityInformation.getId(entity));
// if the entity to be deleted doesn't exist, delete is a NOOP
if (existing == null) {
return;
}
em.remove(em.contains(entity) ? entity : em.merge(entity));
}
Personally, I think returning a Boolean value could be an adequate approach in this case because the controller layer will know about the deletion status, and the view layer can be provided with the far more reliable alert message.
Spring Data JPA design some build-in methods that way they think and give us the option to use the other way also.
You can easily get deleted records and their count using derived delete query supported By Spring Data JPA (Reference)
#Repository
public interface FruitRepository extends JpaRepository<Fruit, Long> {
Fruit deleteById(Long id); // To get deleted record
}
#Repository
public interface FruitRepository extends JpaRepository<Fruit, Long> {
Long deleteById(Long id); // To get deleted record count
}
use #Modifying and #Query ant it will return number of deleted rows.
#Repository
public interface FruitRepository extends JpaRepository<Fruit, Long> {
#Modifying
#Query(value = "DELETE FROM Fruit f where f.id = ?1")
int costumDeleteById(Long id);
}
Another option would be to follow the suggestion from this answer and check if the number of affected entities is 1 (in case of a deleteById method).

How to check is there a row with getOne function

Assume that "Project" is POJO. In service layer of my project, I wrote a function that is get a row from table.
#Override
public ProjectDto getAProject(Long id) {
try {
Project project = projectRepository.getOne(id);
if (project==null) {
throw new IllegalArgumentException("Project not found");
} else {
return modelMapper.map(project, ProjectDto.class);
}
} catch (EntityNotFoundException ex) {
throw new IllegalArgumentException("Project not found");
}
}
The function is working fine with already exist id values. But if I give non-exist value, an exception occur like following. Looks like "getOne()" function don't throw "EntityNotFoundException".
ModelMapper mapping errors: Error mapping com.issuemanagement.entity.Project to com.issuemanagement.dto.ProjectDto
that means the exception come from model mapper logic. Because "project" object filled with null values so couldn't map to DTO class. I modified the function as following to fix this.
#Override
public ProjectDto getAProject(Long id) {
boolean isExist = projectRepository.existsById(id);
if (isExist) {
Project project = projectRepository.getOne(id);
return modelMapper.map(project, ProjectDto.class);
} else {
throw new IllegalArgumentException("Project not found");
}
}
but in this way the program goes to DB for two times. I don't like it. How can I do this operation with just one transaction?
BTW, if I try to run "toString()" function of "project", it throw "EntityNotFoundException" but it's looks like not official way. or it is? I hope there should be a boolean variable in somewhere.
getOne() on JpaRepository will call getReference() on EntityManager under the hood which will return an instance whose state is lazily fetch .The EntityNotFoundException will not throw immediately but will only be thrown when its state is accessed at the first time .
It is normally used in the case that when you need to configure a #ManyToOne relationship for an entity (Let say configure a Department for an Employee) but you just have the ID of the related entity.(e.g. DepartmentId) . Using getOne() allows you to get a Department proxy instance such that you do not really need to query the database to get the actual Department instance just for setting up its relationship for an Employee.
In your case , you should use findById() which will return an empty Optional if the instance does not exist:
#Override
public ProjectDto getAProject(Long id) {
Project project = projectRepository.findById(id)
.orElseThrow(()->new IllegalArgumentException("Project not found"));
return modelMapper.map(project, ProjectDto.class);
}

Spring JPA - Best way to update multiple fields

I'm new to using JPA and trying to transition my code from JdbcTemplate to JPA. Originally I updated a subset of my columns by taking in a map of the columns with their values and created the SQL Update string myself and executed it using a DAO. I was wondering what would be the best way to do something similar using JPA?
EDIT:
How would I transform this code from my DAO to something equivalent in JPA?
public void updateFields(String userId, Map<String, String> fields) {
StringBuilder sb = new StringBuilder();
for (Entry<String, String> entry : fields.entrySet()) {
sb.append(entry.getKey());
sb.append("='");
sb.append(StringEscapeUtils.escapeEcmaScript(entry.getValue()));
sb.append("', ");
}
String str = sb.toString();
if (str.length() > 2) {
str = str.substring(0, str.length() - 2); // remove ", "
String sql = "UPDATE users_table SET " + str + " WHERE user_id=?";
jdbcTemplate.update(sql, new Object[] { userId },
new int[] { Types.VARCHAR });
}
}
You have to read more about JPA for sure :)
Once entity is in Persistence Context it is tracked by JPA provider till the end of persistence context life or until EntityManager#detach() method is called. When transaction finishes (commit) - the state of managed entities in persistence context is synchronized with database and all changes are made.
If your entity is new, you can simply put it in the persistece context by invoking EntityManager#persist() method.
In your case (update of existing entity), you have to get a row from database and somehow change it to entity. It can be done in many ways, but the simpliest is to call EntityManager#find() method which will return managed entity. Returned object will be also put to current persistence context, so if there is an active transaction, you can change whatever property you like (not the primary key) and just finish transaction by invoking commit (or if this is container managed transaction just finish method).
update
After your comment I can see your point. I think you should redesign your app to fit JPA standards and capabilities. Anyway - if you already have a map of pairs <Attribute_name, Attrbute_value>, you can make use of something called Metamodel. Simple usage is shown below. This is naive implementation and works good only with basic attributes, you should take care of relationships etc. (access to more informations about attributes can be done via methods attr.getJavaType() or attr.getPersistentAttributeType())
Metamodel meta = entityManager.getMetamodel();
EntityType<User> user_ = meta.entity(User.class);
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaUpdate<User> update = cb.createCriteriaUpdate(User.class);
Root e = update.from(User.class);
for( Attribute<? super User, ?> attr : user_.getAttributes() ) {
if (map.containsKey(attr.getName())) {
update.set(attr, map.get(attr));
}
}
update.where(cb.equal(e.get("id"), idOfUser));
entityManager.createQuery(update).executeUpdate();
Please note that Update Criteria Queries are available in JPA since 2.1 version.
Here you can find more informations about metamodel generation.
Alternatively to metamodel you can just use java reflection mechanisms.
JPA handles the update. Retrieve a dataset as entity using the entitymanager, change the value and call persist. This will store the changed data in your db.
In case you are using Hibernate(as JPA provider), here's an example
Entity
#Entity
#Table(name="PERSON")
public class Person {
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
#Column(name="NAME", nullable=false)
private String name;
other fields....
}
DAO
public interface PersonDao {
Person findById(int id);
void persist(Person person);
...
}
DaoImpl
#Repository("personDao")
public class PersonDaoImpl extends AnAbstractClassWithSessionFactory implements PersonDao {
public Person findById(int id) {
return (Person) getSession().get(Person.class, id);
}
public void persist(Person person){
getSession().persist(person);
}
}
Service
#Service("personService")
#Transactional
public class PersonServiceImpl implements PersonService {
#Autowired
PersonDao personDao;
#Override
public void createAndPersist(SomeSourceObject object) {
//create Person object and populates with the source object
Person person = new Person();
person.name = object.name;
...
personDao.persist(person);
}
#Override
public Person findById(int id) {
return personDao.findById(id);
}
public void doSomethingWithPerson(Person person) {
person.setName(person.getName()+" HELLO ");
//here since we are in transaction, no need to explicitly call update/merge
//it will be updated in db as soon as the methods completed successfully
//OR
//changes will be undone if transaction failed/rolledback
}
}
JPA documentation are indeed good resource for details.
From design point of view, if you have web interfacing, i tends to say include one more service delegate layer(PersonDelegateService e.g.) which maps the actual data received from UI to person entity (and viceversa, for display, to populate the view object from person entity) and delegate to service for actual person entity processing.

Using Spring HibernateTemplate. How to delete by Id?

For work with database, my class extends HibernateDaoSupport class and inside the methods I'm using Spring HibernateTemplate.
So, for delete a row in database I use this method:
public void delete(MyObject obj) {
getHibernateTemplate().delete(obj);
}
all ok!
But, at this moment I'm trying to implement a method that can delete a row based on id:
public void delete(final long id) {
// some code here
}
And I can't find some HibernateTemplate method like this:
getHibernateTemplate().remove(id)
What is a good solution for me in this case?
delete using particular id,
public void delete(long id)
{
Session session ;
MyObject myObject ;
session = sessionFactory.getCurrentSession();
myObject = (MyObject)session.load(MyObject.class,id);
session.delete(myObject);
//This makes the pending delete to be done
session.flush() ;
}
Also consider encapuslate this methods in try/catch/finally and log the error as needed
Another alternative is:
public void deleteById(Class clazz,Integer id) {
String hql = "delete " + clazz.getName() + " where id = :id";
Query q = session.createQuery(hql).setParameter("id", id);
q.executeUpdate();
}
As you mentioned, there s not such method in HibernateTemplate. You can do the following,
hibernateTemplate.delete(hibernateTemplate.get(Class,Id));
You can also use below method:
public void deleteById(Class clazz,Integer id) {
hibernateTemplate.bulkUpdate("delete from "+clazz.getName()+" where id="+id);
}
There is a simple solution by creating an object and setting only the ID:
Product product = new Product();
product.setId(37);
session.delete(product);
The drawback of this simple solution is that it doesn’t remove the associated instances.
If you have some attribute (another entity related) of the product to be deleted, you will need to load the product before.
Serializable id = new Long(17);
Object persistentInstance = session.load(Product.class, id);
if (persistentInstance != null)
{
session.delete(persistentInstance);
}
This will issue (if you have an attribute table in cascade) a delete on the children attributes.

Categories