Tools:
Spring-Boot : 1.5.9.RELEASE
Spring-Data-JPA : 1.11.9.RELEASE
Issue:
Currently I have a repository that extended from JpaRepository. In order to avoid frequent DB access, I want to cache some of the CRUD methods in the JpaRepository.
I tried a few ways from what I can find with Mr.Google but non of them working except one.
EDITED
1. Solution mentioned in this link is workable. However, there is a bad practice (redundancy to me) at here. Imagine if I have 50 repositories extending the JpaRepository, this means that I have to override the save method in 50 repositories.
public interface UserRepository extends CrudRepository<User, Long> {
#Override
#CacheEvict("user")
<S extends User> S save(S entity);
#Cacheable("user")
User findByUsername(String username);
}
EDITED
2. Extend the JpaRepository interface. I saw something that might works at link2.
In the link, it mentioned 3 different ways to caching the JpaRepository methods. the 1st method is same as what I mentioned in #1. However, I want something similar to 2nd/3rd method so that I no need to keep repeating overriding the CRUD methods in all repositories.
Below is some sample code that I have written.
#NoRepositoryBean
public interface BaseRepository<T, ID extends Serializable> extends
JpaRepository<T, ID> {
#CacheEvict
<S extends User> S save(S entity);
#Cacheble
T findOne(ID id);
}
#Repository
#CacheConfig("user")
public interface UserRepository extends BaseRepository<User, Integer> {
// when I calling findOne/save method from UserRepository, it should
// caching the methods based on the CacheConfig name defined in the
// child class.
}
However, it seems like the code (above) ain't working as I getting below exception. I understand the issue mainly happened because there is no name being assigned to the cacheable annotation in the BaseRepository. But I would need to cache the CRUD methods in the BaseRepository that extend from JpaRepository.
java.lang.IllegalStateException: No cache could be resolved for 'Builder[public abstract java.util.List com.sdsap.app.repository.BaseRepository.findAll()] caches=[] | key='' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless='' | sync='false'' using resolver 'org.springframework.cache.interceptor.SimpleCacheResolver#30a9fd0'. At least one cache should be provided per cache operation.
I have been asking Mr.Google for few days and yet can't find any suitable solution. I hope someone can help me at here. Sorry if my question isn't clear or missing something as this is my first time posting at here. Thanks!
I am assuming that you have required configuration already set up and the stack trace you have posted is the problem. So let's dig it.
There are two problems I see:
java.lang.IllegalStateException: No cache could be resolved, At least one cache should be provided per cache operation.
Resolution: Whenever you want to cache the data or evict the data you MUST provide the name of the cache, which I don't see provided in your code.
#Cacheable's cacheNames or value should be defined in order to get the cache working.
Example : #Cacheable(value = "usersCache")
The proper cache key
Because cache works on key-value pair, you should provide a proper cache key. If you don't provide the cache key then, by default, a default key generation strategy that creates a SimpleKey that consists of all the parameters with which the method was called.
Suggestion: You should provide the cache key manually.
Example :
#Cacheable(value = "usersCache", key = "#username")
User findByUsername(String username);
Note: Make sure username is unique because cache key must be unique.
You can read more Spring cache annotations: some tips & tricks
Use #CachedResult on method you want to cache.
In your main class use #EnableCaching.
Sample code:
Main class
#SpringBootApplication
#EnableCaching
#RestController
public class SpringBootCacheApplication {
#Autowired
SomeBean someBean;
#RequestMapping(value = "/cached/{key}")
public int getCachedMethod(#PathVariable("key") String key) {
System.out.println("Got key as " + key);
return someBean.someCachedResult(key);
}
public static void main(String[] args) {
SpringApplication.run(SpringBootCacheApplication.class, args);
}
}
SomeBean class containig method which I wish to cache
#Component
public class SomeBean {
#CacheResult
public int someCachedResult(String key) {
System.out.println("Generating random number");
int num = new Random().nextInt(200);
return num;
}
}
In the someCachedResult method I'm always returning some random value. Since its cached, you'll get random value the first time only.
Here the SomeBean should correspond to your CachingUserRepository class.
This is a great idea. I ended up trying this and getting it to work.
I created a BaseRepository:
#NoRepositoryBean
public interface BaseRepository<T, ID extends Serializable> extends JpaRepository<T, ID> {
#Cacheable(cacheResolver = "cachingConfig")
Optional<T> findById(UUID id);
#CachePut(cacheResolver = "cachingConfig", key = "#p0.id")
// Worth noting - add multiple cache puts if caching by different keys(queries)
// This gets hard when caching special queries per resource - best I've
// found so far is to override this method in resource repositories and add all
// the puts/evicts needed
<S extends T> S save(S entity);
}
Note the cacheResolve = "cachingConfig"
Then CachingConfig (CacheResolver) to resolve your cache exception issue:
#Configuration
#EnableCaching
#Log4j2
public class CachingConfig implements CacheResolver {
private final CacheManager cacheManager;
private final ObjectMapper objectMapper;
public CachingConfig(ObjectMapper objectMapper) {
this.cacheManager = new ConcurrentMapCacheManager();
this.objectMapper = objectMapper;
}
#Bean
public CacheManager cacheManager() {
return cacheManager;
}
#Override
public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context) {
Collection<Cache> caches = new ArrayList<>();
String cacheName = (context.getTarget() instanceof BaseRepository)
// When BaseRepository, first interface in list is specific Repository Interface
? context.getTarget().getClass().getInterfaces()[0].getSimpleName()
// I've standardized around all uppercase domain (UserRepository = USER)
.replace("Repository", "").toUpperCase(Locale.ROOT)
// Fallback to class name (you may have different ideas here)
: context.getTarget().getClass().getSimpleName();
caches.add(cacheManager.getCache(cacheName));
return caches;
}
// Periodic cache dump - used to see what caches exist and contents when dumped
#Scheduled(fixedDelay = 10000)
public void cacheEvict() {
cacheManager.getCacheNames().forEach(cacheName -> {
final Cache cache = cacheManager.getCache(cacheName);
if (log.isTraceEnabled()) {
Map<String, Object> nativeCache = (Map) cache.getNativeCache();
nativeCache.forEach((k, v) -> {
try {
log.trace(String.format("Clearing %s:%s:%s", cacheName, k, objectMapper.writeValueAsString(v)));
} catch (JsonProcessingException e) {
log.trace("Error", e);
}
});
}
Objects.requireNonNull(cache).clear();
});
}
}
Example repositories:
#Repository
public interface UserRepository extends BaseRepository<User, Long> {
}
#Repository
public interface TestRepository extends BaseRepository<Test, Long> {
}
Example log statement from the dump:
14:06:46.033 [scheduling-1] TRACE CachingConfig - Clearing USER:1:{"id":1,"firstName":"test","lastName":"user"}
14:06:46.033 [scheduling-1] TRACE CachingConfig - Clearing TEST:5:{"id":5,"cool":true}
Related
I'm building my first Spring Boot app using JPA and have setup my data repositories and services like this:
#Repository
public interface FooRepository extends JpaRepository<Foo, Long> {
Set<Foo> findAllByActiveInstallationIsNull();
}
Then a CrudService
public interface CrudService<T extends BaseEntity> {
Set<T> findAll();
T findById(Long id);
T save(T object);
void delete(T object);
void deleteById(Long id);
}
along with an example class service interface that extends it
public interface FooService extends CrudService<Foo> {
Set<Foo> findAllAvailable();
Foo getIfAvailable(Long id);
}
an abstract class for service implementations
public abstract class AbstractJpaService<T extends BaseEntity, R extends JpaRepository<T, Long>> implements CrudService<T> {
protected R repository;
public AbstractJpaService(R repository) {
this.repository = repository; }
#Override
public Set<T> findAll() {
return new HashSet<>(repository.findAll()); }
#Override
public T findById(Long id) {
return repository.findById(id).orElse(null); }
#Override
public T save(T object) {
return repository.save(object); }
#Override
public void delete(T object) {
repository.delete(object); }
#Override
public void deleteById(Long id) {
repository.deleteById(id); }
}
and finally an example of an actual service class that extends the above-mentioned one:
#Service
#Transactional
public class FooJpaService extends AbstractJpaService<Foo, FooRepository> implements FooService {
public FooJpaService(FooRepository repository) {
super(repository);
}
///
}
I wrote some service layer logic, controllers and once I was happy with the first iteration I've done some postman testing that worked without a hitch.
Then I took a step back and started writing some unit tests for my service classes only to realize that while findAll() in my services returns Set as I intended, the JpaRepository methods and by extension my own repos give List.
#Test
void findAll() {
Set<Foo> returnFooSet = new HashSet<>();
returnFooSet.add(new Foo(boo, 1d, 2d));
returnFooSet.add(new Foo(baz, 3d, 4d));
when(fooRepository.findAll()).thenReturn(returnFooSet);
Set<Foo> foos = service.findAll();
assertNotNull(foos);
assertEquals(2, foos.size());
}
resulting in thenReturn() method expecting a List.
Sorry for the wall of code, but I'm pretty new at this and very much confused so figured I'll provide excessive context even if most could have been assumed, since my newbie implementations may be weird and faulty.
So what gives?
I've read about the benefits of using Sets in JPA and most of the code examples I've seen use them.
My own findAllByArgument methods with Set returns like the ones you see in the repository have been working just fine, so I assume nothing stops me from overriding basic FindAll() methods in all of my repos (since CrudRepository seems to have just Iterable there), but that seems... off?
Should I be using Sets with JPA? What are good practices in this case?
I believe the only rule of thumb regarding List or Set in JPA world (with Hibernate under the hood) is to always use Set on a #ManyToMany relationship and never List.
Other than that I am not aware of anything else. Still, I can guess that maybe Set is better in terms of performance since it is unordered while List is ordered. Given that JpaRepository has a method that returns List this eventually better performance might not be relevant enough.
I will try to be as detailed as possible. I have many DAO and my service needs to use one of them based on the key i get. For instance -
if(key.equals("abc") {
obj = abcDAO.getOne(id);
} else if(key.equals("xyz") {
obj = xyzDAO.getOne(id);
}
The object is of type parent class and abc, xyz.. are all child classes.
My idea is to create a Map<String, ParentCLass> to get the object just by passing the key instead of If-else so that it will be easy to add for further changes. In case it would've been a normal class, I wouldv'e initialized the map as
Map<String, ParentClass> map.
map.put("abc", new Abc());
But since DAO are interfaces and require to be #Autowired for using them, I don't know how to proceed. I am a beginner. Any help appreciated.
Spring is able to inject all beans with the same interface in a map if the map has String as key (will contain the bean names) and the interface as value.
public interface MyDao {
}
#Autowired
private Map<String, MyDao> daos;
EDIT: If you use Spring Data Repository, there is already a tagging Interface: Repository. You can use below code to inject all DAOs in one bean.
#Autowired
private Map<String, Repository> daos;
EDIT2: Example
public interface UserRepo extends JpaRepository<User, Long> { ... }
#Service
public class MyService {
#Autowired
private Map<String, Repository> daos;
public List<User> findAll() {
return daos.get("userRepo").findAll();
}
}
You can create different bean for each of your Dao with a specific name
#Bean(name = "daoImpl1")
public Dao daoImpl1(){
return new DaoImpl1();
#Bean(name = "daoImpl2")
public Dao daoImpl2(){
return new DaoImpl2();
And then #Autowire them using #Qualifier with that name
#Autowired
#Qualifier("daoImpl1")
private Dao daoImpl1;
#Autowired
#Qualifier("daoImpl2")
private Dao daoImpl2;
i made methods to make simple jpa things,
i stored all of repositories in the HashMap
you just have to pass the child class and you get the corresponding JpaRepository
you can see more at https://github.com/fajaralmu/base_web_app
example :
public List<Page> getAllPages() {
List<Page> allPages = entityRepository.findAll(Page.class);
return allPages;
}
This question already has answers here:
Generic Repository in Spring JPA
(2 answers)
Closed 4 years ago.
I'm beginning to work with Spring Data JPA repositories. We have an application already using Spring MVC (No spring boot or spring data JPA), where we have written a Generic DAO class that handles basic CRUD operations for virtually all entities that we have. Any other special operations can be handled by writing custom DAOs.
Now, Spring data JPA has made things very easy by requiring us to write only an interface and the rest is taken care of.
public interface PersonRepository extends JpaRepository<Person, Long> {
}
This is cool, but I was wondering if I can introduce generics here.
The reason is, my application has a number of entities for which we need to perform only basic CRUD operations and nothing more. Which means, for every entity, we need to write an interface. Though the code is minimal, it results in one file for each entity, which I guess can be avoided (true?).
My question is, can I write a generic Repository class like
public interface GenericRepository<T> extends JpaRepository<T, Long> {
}
so that my service class can look like this
#Autowired
private GenericRepository<Person> personRepository;
public List<Person> findAll() {
return this.personRepository.findAll();
}
This will be a much cleaner approach for basic operations, as one Repository interface handles a number of entities.
EDIT
It turns out that I can indeed create a repository interface as I illustrated above, but when the application starts, I get an error which says
Error creating bean with name 'genericRepository': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Not a managed type: class java.lang.Object
This is probably because of the Generic Type
I have to say that my entities are separate classes in themselves and do not implement or extend a super entity/entities. Would it help if they did?
Please guide me in the right direction.
Thanks!
I think you could do it like this:
#NoRepositoryBean
public interface GenericRepository<T, ID extends Serializable> extends JpaRepository<T, ID> {
//added custom common functionality for all the GenericRepository implementations
public List<T> findByAttributeContainsText(String attributeName, String text);
}
public class GenericRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID>
implements GenericRepository<T, ID> {
private EntityManager entityManager;
public GenericRepositoryImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
super(entityInformation, entityManager);
this.entityManager = entityManager;
}
#Transactional
public List<T> findByAttributeContainsText(String attributeName, String text) {
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<T> cQuery = builder.createQuery(getDomainClass());
Root<T> root = cQuery.from(getDomainClass());
cQuery.select(root).where(builder.like(root.<String>get(attributeName), "%" + text + "%"));
TypedQuery<T> query = entityManager.createQuery(cQuery);
return query.getResultList();
}
}
public interface MyOtherRepository extends GenericRepository<Role, Long> {
}
And in your config class:
#Configuration
#EnableJpaRepositories(basePackages="com.myProject", repositoryBaseClass =
GenericRepositoryImpl.class)
public class JpaConfig {
}
I need to override the Pageable class provided by spring data and then override the findAll method provided by the SimpleNeo4jRepository.
But on doing so, I am getting an error on server startup
Caused by: java.lang.IllegalArgumentException: Paging query needs to have a Pageable parameter! Offending method public abstract com.app.backend.repository.pagination.AppPage com.app.backend.repository.BaseRepository.findAll(com.app.backend.repository.pagination.AppPageRequest)
at org.springframework.util.Assert.isTrue(Assert.java:116) ~[spring-core-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.data.repository.query.QueryMethod.<init>(QueryMethod.java:99) ~[spring-data-commons-2.0.9.RELEASE.jar:2.0.9.RELEASE]
at org.springframework.data.neo4j.repository.query.GraphQueryMethod.<init>(GraphQueryMethod.java:41) ~[spring-data-neo4j-5.0.9.RELEASE.jar:5.0.9.RELEASE]
at org.springframework.data.neo4j.repository.query.GraphQueryLookupStrategy.resolveQuery(GraphQueryLookupStrategy.java:49) ~[spring-data-neo4j-5.0.9.RELEASE.jar:5.0.9.RELEASE]
Here is the code
public class AppPageRequest extends PageRequest implements Pageable {
private AppPageRequest(int page, int size, Sort sort) {
super(page - 1, size, sort);
}
public static AppPageRequest of(int page, int size) {
return of(page, size, Sort.unsorted());
}
public static AppPageRequest of(int page, int size, Sort sort) {
return new AppPageRequest(page, size, sort);
}
}
#NoRepositoryBean
public interface BaseRepository<T, ID extends Serializable> extends Neo4jRepository<T, ID> {
Page<T> findAll(AppPageRequest appPageRequest);
}
#NoRepositoryBean
public class BaseRepositoryImpl<T, ID extends Serializable> extends SimpleNeo4jRepository<T, ID> implements BaseRepository<T, ID> {
public BaseRepositoryImpl(Class<T> domainClass, Session session) {
super(domainClass, session);
}
public Page<T> findAll(AppPageRequest appPageRequest) {
return super.findAll(appPageRequest);
}
}
assuming you want to make sure that no-one is able to call findAll and related with the default implementation of Pageable, there are two things you have to take care of:
You cannot override the signature of findAll and related by extending your BaseRepository from Neo4jRepository, the methods are not overwritten but overloaded and can be called as before.
To make Spring Data aware of the your custom repository implementation you have to specify the new base class when enabling Neo4j (or any other repository) (as described here).
With that in mind, here is a solution that works for us. Tested with Spring Boot 2.0.4, Spring Data Kay and OGM 3.1.0, running on Java 10. Find the complete solution in this Gist.
Keypoints:
Extend Spring Datas CrudRepository at max:
#NoRepositoryBean
interface BaseRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {
Page<T> findAll(AppPageRequest appPageRequest);
}
CrudRepository does not contain findAll, so your users cannot use it. Keep your BaseRepositoryImpl as is (see gist).
Make your domain repository extend BaseRepository and again not Neo4jRepository as such:
interface ThingRepository extends BaseRepository<ThingEntity, Long> {
}
Then the important step, make SDN aware of the new base implementation through #EnableNeo4jRepositories:
#SpringBootApplication
#EnableNeo4jRepositories(repositoryBaseClass = BaseRepositoryImpl.class)
public class CustomPagerequestApplication {
public static void main(String[] args) {
SpringApplication.run(CustomPagerequestApplication.class, args);
}
}
And then you're able to use your repo like this:
#Component
class ExampleUsage implements CommandLineRunner {
private final ThingRepository thingRepository;
public ExampleUsage(ThingRepository thingRepository) {
this.thingRepository = thingRepository;
}
#Override
public void run(String... args) {
var things = IntStream.iterate(1, i -> i <= 10, i -> i + 1)
.mapToObj(ThingEntity::new)
.collect(toList());
this.thingRepository.saveAll(things);
var page = this.thingRepository.findAll(AppPageRequest.of(1, 5));
page.stream().map(ThingEntity::getName).forEach(System.out::println);
}
}
Please let me know, if this helps. Again, here the link to the complete example:
Enforce a concrete implementation of Pageable for paged Queries with Spring Data (Neo4j)
It was a configuration miss. Mentioning BaseRepositoryImpl as the repository base class fixed the issue.
I changed
#EnableNeo4jRepositories
to
#EnableNeo4jRepositories(repositoryBaseClass = BaseRepositoryImpl.class)
I have some entity type that needs additional logic on saving (to be precise, I want to save position at the moment of saving). I don't want to do it with any DB-specific features, like triggers, because I'm not sure what will be the data storage used in future.
So I would like to override save() method.
In Spring Data JPA documentation I can see two ways of providing own implementation for repository classes:
Extend base repository class and tell Spring Data to use it.
Defining an interface (in my case I assume PositionedRepository) with an implementation class (PositionedRepositoryImpl).
Problem with first way - I don't want to implement it for all repositories, only two entity types are positioned.
Problem with second way - I don't have access to base repository methods, so apart from position calculation I would need to somehow build all of the queries, normally provided by base repository.
Any way to extend base repository class just for specific repository types?
Don't do that logic in the repository itself. Think about repositories as a dumb layer between java and the database. It just passes data from end to the other.
Instead you should handle that case in a different layer. A more intelligent one. The business logic layer.
See this example:
#Service
public class MyEntityService{
private final MyEntityRepository myEntityRepository;
private final OtherEntityRepository otherEntityRepository;
#Autowired
public MyEntityService(MyEntityRepository myEntityRepository,
OtherEntityRepository otherEntityRepository){
this.myEntityRepository = myEntityRepository;
this.otherEntityRepository = otherEntityRepository;
}
public void save(MyEntity myEntity){
// do stuff with otherEntityRepository
myEntitiyRepository.save(myEntity);
}
}
you can :
public class CustomJpaRepository<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> {
private final JpaEntityInformation<T, ?> entityInformationWrap;
private final EntityManager emWrap;
public CustomJpaRepository(JpaEntityInformation entityInformation, EntityManager entityManager) {
super(entityInformation, entityManager);
entityInformationWrap=entityInformation;
emWrap=entityManager;
}
#Override
public <S extends T> S save(S entity) {
//doing
}
}
then main class add:
#EnableJpaRepositories(repositoryBaseClass = CustomJpaRepository.class)
As third option you can extend SimpleJpaRepository that implements JpaRepository and JpaSpecificationExecutor.
In this way, you could benefit from the default implementation of JpaRepository while being the ability to override these methods.
For example :
#Repository
public class PositionedRepository extends SimpleJpaRepository<Positioned, Long> {
#Override
public Positioned save(Positioned positioned) {
...
}
}
As fourth option you can also define your own savePositioned() method that uses under the hood the JpaRepository.save().