I have a data repository DAO class that gets data from DB and it has following methods that my controller/service class calls:
#Override
public Account getOne(final String id) {
this.namedParameterJdbcTemplate.queryForObject(this.BY_ID,
namedParameters,
new Mapper());
}
#Cacheable(value = "accounts")
#Override
public List<Account> getAll() {
return this.namedParameterJdbcTemplate.query(this.ALL, new Mapper());
}
#CacheEvict(value = "accounts", allEntries = true)
public void evictAll() {
}
I am caching the result of an expensive getAll() call.
This is refreshed also by a scheduler calling evictAll().
My question is how can I cache getOne() as it takes a id param.
Should I create a new cache or can use existing one "accounts" with reference to the id. Any ideas with some samples or pointers to examples will be much appreciated.
Related
I’ve been working on a website and want to ask why the newly created records that I save to a database via Spring JPA don't appear in other queries within the server. The records themselves are saved just fine and do appear in the database itself but not when the server looks for them. Older records that were not made by the server do appear just fine.
For example, a new book is added with the ID of 601 and it simply will not appear in the results of a any API that is run on the server. Even when it is searched for specifically, the server returns a 404 Error.
I've consulted a number of articles on the matter including Spring's own documentation but I haven't been able to find an answer on my own. I'll go into the details of my attempts below.
Articles and Attempts
While I've consulted a large amount of material on the matter, I'll trim it down to only a few that I think are most relevant.
The the most promising suggestion that I received was to force a refresh of the Entity's cache and the most cited way to do this was with Entity Manager's em.refresh(); method. Judging by the documentation, it does sound like it should do the job quite well. Sadly, this is not a method available in standard JpaRepository libraries since JpaRepository is an abstraction over Entity Manager so I've been working to implement Entity Manager into my code.
The primary source I used was here sicne it provided a way to implement a refresh ability in any repository that needed it and I followed the code given in the answer very closely but it had no effect. It was a four year old answer so it might be possible that it's out of date. I can't quite tell.
I've tried a few different ideas but this one has had the most promise.
Code:
Entity
#Entity
public class Book {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
int bookID;
#NotEmpty
String title;
String isbn;
Integer series_part;
Integer edition;
int format;
int pages;
int authorID;
int publisherID;
int seriesID;
int genreID;
int languageID;
int copyright;
Service Implementation
#Service
public class BookServiceImpl implements BookService{
#Autowired BookRepository bookRepository;
#Override
public Book createBook(Book book) {
Book savedBook=bookRepository.save(book);
bookRepository.refresh(savedBook);
return savedBook;
}
}
Service Controller
#RestController
#RequestMapping(path = "/books")
#CrossOrigin(origins = "http://localhost:8080")
public class BookServiceController {
#Autowired BookService bookServiceImpl;
#PostMapping(path = "/create")
public ResponseEntity<Book> createBook(#RequestBody Book book) {
try {
Book newBook = bookServiceImpl.createBook(book);
return new ResponseEntity<>(newBook, HttpStatus.CREATED);
} catch (Exception e) {
return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
Get APIs for Book
#GetMapping(path = "/all")
#Transactional(readOnly = false)
public ResponseEntity<Object> getBooks() {
return new ResponseEntity<>(bookServicesImpl.getBooks(), HttpStatus.OK);
}
#GetMapping(path = "/ten")
public ResponseEntity<Object> getTenBooks() {
return new ResponseEntity<>(bookServicesImpl.getTenBooks(), HttpStatus.OK);
}
#GetMapping(path = "/specific/{id}")
public ResponseEntity<Object> getSpecificBook(#PathVariable("id") int id) {
return new ResponseEntity<>(bookServicesImpl.getSpecificBook(id), HttpStatus.OK);
}
#GetMapping(path = "/search/{term}")
#Transactional(readOnly = false)
public ResponseEntity<Object> searchForBook(#PathVariable("term") String term) {
return new ResponseEntity<>(bookServicesImpl.searchForBook(term), HttpStatus.OK);
}
Generic Repository that contains refresh method
I copied it quite faithfully (possibly too faithfully).
public class ElementRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID>
implements ElementRepository<T, ID> {
private final EntityManager entityManager;
public ElementRepositoryImpl(JpaEntityInformation entityInformation, EntityManager entityManager) {
super(entityInformation, entityManager);
this.entityManager = entityManager;
}
#Override
#Transactional
public void refresh(T t) {
entityManager.refresh(t);
}
These seemed like the most relevant classes to include but I'll include any others that might be needed.
I apologize for the frequency of questions but I fully admit to being a novice of JPA and a lot of this (including large sections of the documentation) goes over my head from time to time.
Thanks in advance for any help at all!
public class Student {
public int studentName;
public String Addr1;
public String Addr2;
public String Addr2;
//getter setter
}
I have one repository class which contains following methods
class StudentRepoImpl{
#Cacheable(value = "Students")
public List<Students> findAllStudents() {
//fetching all cust and putting in the Students cache
}
#Cacheable(value = "Students")
public List<Students> findStudentsBystudentNameAndAddresses() {
//fetching all cust data by **Name/Address1/Address2/Address3** basis of field available studentName/Address1/Address2/Address3 and putting in Student table
}
}
Current Output :
Fetch All data from DB and adding in Students cache in findAllStudents() method
But while searching for data based on some criteria (Name/Address1/Address2/Address3) using findStudentsBystudentNameAndAddresses() method it is fetching data from DB instead of cache.
Note: Not added Key while caching because there are 4 fields in search criteria (Name/Address1/Address2/Address3) and these are conditional fields means some time only Address1 will be in search criteria or sometime Address1+Address2 or sometimes all Address1+Address2+Address3 fields and I want to fetch exact match on the basis of Name and available Addresses.
did you add #EnableCaching annotation in your configuration class
Try to add configuration like this
#Configuration
public class CachingConfig {
#Bean(name = "springCM")
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("Students");
}
}
Hope useful
My #Cacheable method has next signature:
#Component
public class UpcomingFilter implements Filter<Entity> {
#Cacheable(value = {"upcoming"})
#Override
public List<Entity> filter(int limit) {
//retrieve from repository
}
}
This filter use the reporisoty, take limit as parameter for pagination and return List of Entities.
I'm trying to update cache when add Entity to the system:
#CachePut(value={"upcoming", "popular", "recentlyAdded", "recommendations", "thisWeek", "topRated"})
public Entity addEntity(RequestDto dto, User user) {
//do work, create and save entity to repository
return entity;
}
But after adding new entity to the system it is not updated. Filters returns old values.
I saw examples, where for CachePut and Cacheable the word 'key' is used, but how can I add
#Cacheable(key="#entity.id")
to the Filter signature ?
UPDATE
Tried to add my key:
#CachePut(value={"upcoming","popular", "recentlyAdded", "recommendations", "thisWeek", "topRated"},
key = "#root.target.FILTER_KEY", condition = "#result != null")
public Entity addEntity(RequestDto dto, User user) {
//do work, create and save entity to repository
return entity;
}
and also add key to #Cacheable:
public static final String FILTER_KEY = "filterKey";
#Cacheable(value = {"recentlyAdded"}, key = "#root.target.FILTER_KEY")
#Override
public List<Entity> filter(int limit) {
and than I get
java.lang.ClassCastException: com.java.domain.Entity cannot be cast to
java.util.List
Instead #CachePut the #CacheEvict should be used.
It works for me:
#CacheEvict(value={"upcoming", "popular", "recentlyAdded", "recommendations", "thisWeek", "topRated"},
allEntries = true, condition = "#result != null")
public Entity addEntity(RequestDto dto, User user) {
//do work, create and save entity to repository
return entity;
}
I'm trying to add caching to a CRUD app, I started doing something like this:
#Cacheable("users")
List<User> list() {
return userRepository.findAll()
}
#CachePut(value = "users", key = "#user.id")
void create(User user) {
userRepository.create(user)
}
#CachePut(value = "users", key = "#user.id")
void update(User user) {
userRepository.update(user)
}
#CacheEvict(value = "users", key = "#user.id")
void delete(User user) {
userRepository.delete(user)
}
The problem I have is that I would like that create/update/delete operations can update the elements already stored in the cache for the list() operation (note that list() is not pulling from database but an data engine), but I am not able to do it.
I would like to cache all elements returned by list() individually so all other operations can update the cache by using #user.id. Or perhaps, make all operations to update the list already stored in cache.
I read that I could evict the whole cache when it is updated, but I want to avoid something like:
#CacheEvict(value = "users", allEntries=true)
void create(User user) {
userRepository.create(user)
}
Is there any way to create/update/remove values within a cached collection? Or to cache all values from a collection as individual keys?
I'll self answer my question since no one gave any and could help others.
The problem I had when dealing with this issue was a problem of misconception of Cache usage. My need posted on this question was related to how to update members of a cached list (method response). This problem cannot be solved with cache, because the cached value was the list itself and we cannot update a cached value partially.
The way I wanted to tackle this problem is related to a "map" or a distributed map, but I wanted to use the #Cacheable annotation. By using a distributed map would have achieved what I asked in my question without using #Cacheable. So, the returned list could have been updated.
So, I had (wanted) to tackle this problem using #Cacheable from other angle. Anytime the cache needed to update I refreshed it with this code.
I used below code to fix my problem:
#Cacheable("users")
List<User> list() {
return userRepository.findAll()
}
// Refresh cache any time the cache is modified
#CacheEvict(value = "users", allEntries = true")
void create(User user) {
userRepository.create(user)
}
#CacheEvict(value = "users", allEntries = true")
void update(User user) {
userRepository.update(user)
}
#CacheEvict(value = "users", allEntries = true")
void delete(User user) {
userRepository.delete(user)
}
In addition, I have enabled the logging output for spring cache to ensure/learn how the cache is working:
# Log Spring Cache output
logging.level.org.springframework.cache=TRACE
Not Sure if, using Spring's #Cacheable is a hard constraint for you, but this essentially worked for me.
I tried using Spring's RedisTemplate and Redis HasMap data-structure for storing the list elements.
Store a single User:
redisTemplate.opsForHash().put("usersRedisKey" ,user.id,user);
Storing List of Users:
Need to map this with user.id first
Map<Long, User> userMap = users.stream()
.collect(Collectors.toMap(User::getId, Function.identity()));
redisTemplate.opsForHash().putAll("usersRedisKey", userMap);
Get single user from Cache:
redisTemplate.opsForHash().get("usersRedisKey",user.id);
Get list of users:
redisTemplate.opsForHash().multiGet("usersRedisKey", userIds); //userIds is List of ids
Delete user from List:
redisTemplate.opsForHash().delete("usersRedisKey",user.id);
Similarly you could try using other operations from Redis HashMap to update individual objects based on ids.
I understand I am quite late to the party here, but do let me know if this works for you.
Try below given solution:
#Caching(put = #CachePut(cacheNames = "product", key = "#result.id"),
evict = #CacheEvict(cacheNames = "products", allEntries = true))
public Product create(ProductCreateDTO dto) {
return repository.save(mapper.asProduct(dto));
}
#Caching(put = #CachePut(cacheNames = "product", key = "#result.id"),
evict = #CacheEvict(cacheNames = "products", allEntries = true))
public Product update(long id, ProductCreateDTO dto) {
return repository.save(mapper.merge(dto, get(id)));
}
#Caching(evict = {
#CacheEvict(cacheNames = "product", key = "#result.id"),
#CacheEvict(cacheNames = "products", allEntries = true)
})
public void delete(long id) {
repository.delete(get(id));
}
#Cacheable(cacheNames = "product", key = "#id")
public Product get(long id) {
return repository.findById(id).orElseThrow(() -> new RuntimeException("product not found"));
}
#Cacheable(cacheNames = "products", key = "#pageable")
public Page<Product> getAll(Pageable pageable) {
return repository.findAll(pageable);
}
Hi I have Hibernate project with QUeryDSL 3.6.0, when I have in my service class only findAll() methods, everyThing was OK. But whe I add findByID an error appears.
public class ArticleServiceImpl extends ArticleService {
QArticle article = QArticle.article;
#Override
public List<Article> findAll() {
return query.from(article).fetchAll().list(article);
}
public Article findById(#Nonnull final long id) {
return query.from(article).where(article.id.eq(id)).uniqueResult(article);
}
}
An the error is:
Exception in thread "main" java.lang.IllegalStateException: article is already used
at com.mysema.query.DefaultQueryMetadata.addJoin(DefaultQueryMetadata.java:160)
at com.mysema.query.support.QueryMixin.from(QueryMixin.java:189)
at com.mysema.query.jpa.JPAQueryBase.from(JPAQueryBase.java:88)
at com.mysema.query.jpa.JPAQueryBase.from(JPAQueryBase.java:32)
at com.example.hibernate.services.ArticleServiceImpl.findById(ArticleServiceImpl.java:30)
at com.example.hibernate.core.Main.main(Main.java:42)
What happens? I saw that queries are not tread-safe. But how to use Q-class in the two different methods?
Edit:
protected JPQLQuery query = new JPAQuery(entityManager);
it is protected variable that comes from ArticleService.
This exception is thrown whenever there is a repeatable call to the same generated QEntity in from() clause for the same instance of JPAQuery().
Here is an example (DISCLAIMER: This is going to be very, very dumb example just to illustrate the problem).
Let's say we have an entity called MyEntity, and we try to get two MyEntity's from a database in a way, that the first result will be for a given id, and the second result will be the one which has id+1
public List<MyEntity> findMyDumbEntities(long id) {
QMyEntity qMyEntity = QMyEntity.myEntity;
JPAQuery jpaQuery = new JPAQuery(entityManager);
MyEntity myFirstEntity = jpaQuery.from(qMyEntity).where(qMyEntity.id.eq(id)).uniqueResult(qMyEntity);
MyEntity mySecondEntity = jpaQuery.from(qMyEntity).where(qMyEntity.id.eq(id+1)).uniqueResult(qMyEntity);
return Arrays.asList(myFirstEntity, mySecondEntity);
}
And when trying to call this method we will see the following exception:
java.lang.IllegalStateException: qMyEntity is already used
Why? Because we have one instance of JPAQuery and we are repeating call to the same entity (we have two jpqQuery.from(qMyEntity)). To solve the problem we just need to get JPAQuery instance each time we want to query something, so we need to change our code to
public List<MyEntity> findMyDumbEntities(long id) {
QMyEntity qMyEntity = QMyEntity.myEntity;
MyEntity myFirstEntity = new JPAQuery(entityManager).from(qMyEntity).where(qMyEntity.id.eq(id)).uniqueResult(qMyEntity);
MyEntity mySecondEntity = new JPAQuery(entityManager).from(qMyEntity).where(qMyEntity.id.eq(id+1)).uniqueResult(qMyEntity);
return Arrays.asList(myFirstEntity, mySecondEntity);
}
So to fix your problem, instead of having JPQQuery initialized once
protected JPQLQuery query = new JPAQuery(entityManager);
Change that you get each time new JPAQuery, for example
protected JPQLQuery jpaQuery() {
return new JPAQuery(entityManager);
}
And then in your service implementation
#Override
public List<Article> findAll() {
return jpaQuery().from(article).fetchAll().list(article);
}
public Article findById(#Nonnull final long id) {
return jpaQuery().from(article).where(article.id.eq(id)).uniqueResult(article);
}