How to add cache feature in Spring Data JPA CRUDRepository - spring

I want to add "Cacheable" annotation in findOne method, and evict the cache when delete or happen methods happened.
How can I do that ?

virsir, there is one more way if you use Spring Data JPA (using just interfaces). Here what I have done, genereic dao for similar structured entities:
public interface CachingDao<T, ID extends Serializable> extends JpaRepository<T, ID>, JpaSpecificationExecutor<T> {
#Cacheable(value = "myCache")
T findOne(ID id);
#Cacheable(value = "myCache")
List<T> findAll();
#Cacheable(value = "myCache")
Page<T> findAll(Pageable pageable);
....
#CacheEvict(value = "myCache", allEntries = true)
<S extends T> S save(S entity);
....
#CacheEvict(value = "myCache", allEntries = true)
void delete(ID id);
}

I think basically #seven's answer is correct, but with 2 missing points:
We cannot define a generic interface, I'm afraid we have to declare every concrete interface separately since annotation cannot be inherited and we need to have different cache names for each repository.
save and delete should be CachePut, and findAll should be both Cacheable and CacheEvict
public interface CacheRepository extends CrudRepository<T, String> {
#Cacheable("cacheName")
T findOne(String name);
#Cacheable("cacheName")
#CacheEvict(value = "cacheName", allEntries = true)
Iterable<T> findAll();
#Override
#CachePut("cacheName")
T save(T entity);
#Override
#CacheEvict("cacheName")
void delete(String name);
}
Reference

I solved the this in the following way and its working fine
public interface BookRepositoryCustom {
Book findOne(Long id);
}
public class BookRepositoryImpl extends SimpleJpaRepository<Book,Long> implements BookRepositoryCustom {
#Inject
public BookRepositoryImpl(EntityManager entityManager) {
super(Book.class, entityManager);
}
#Cacheable(value = "books", key = "#id")
public Book findOne(Long id) {
return super.findOne(id);
}
}
public interface BookRepository extends JpaRepository<Book,Long>, BookRepositoryCustom {
}

Try provide MyCRUDRepository (an interface and an implementation) as explained here: Adding custom behaviour to all repositories. Then you can override and add annotations for these methods:
findOne(ID id)
delete(T entity)
delete(Iterable<? extends T> entities)
deleteAll()
delete(ID id)

Related

How to handle field injection to Couchbase repository

In order to add functionality to the couchbase repository I implemented the below class.
Inside this class is another #componenet name extensionHelper. Since this class is not standard spring bean I can't find a way to inject this field in order to use it in save()
Any idea how to inject such a class?
public class SimpleCustomRepository<T, ID> extends SimpleCouchbaseRepository<T, ID> {
#Autowired
ExtensionHelper extensionHelper;
public SimpleCustomRepository(CouchbaseEntityInformation<T, String> entityInformation,
CouchbaseOperations couchbaseOperations, Class<?> repositoryInterface) {
super(entityInformation, couchbaseOperations, repositoryInterface);
}
#Override
public <S extends T> S save(S entity) {
extensionHelper.applyExtensions(entity);
return super.save(entity);
}
#Override
public void delete(T entity) {
super.delete(entity);
}
}

Spring Boot & Data Repositories - fragment implementation x does not implement x

I upgraded from Spring Boot v.1.5.8 to v.2.1.5. When I try to start the application I get the following error:
IllegalArgumentException: Fragment implementation .OtmUniParentRepository2019GeneratedImpl$$EnhancerBySpringCGLIB$$cdf9e294 does not implement x.OtmUniParentRepository2019Generated!
Why I can't start it anymore?
The files:
OtoUniChildRepository2019
#RepositoryRestResource(collectionResourceRel = "OtoUniChild2019", path = "OtoUniChild2019")
public interface OtoUniChildRepository2019 extends OtoUniChildRepository2019Generated {
}
#Transactional
class OtoUniChildRepository2019Impl extends HeartcoreRepositoryImpl<OtoUniChild> {
#PostConstruct
private void setIni() {
super.setIni(OtoUniChild.TABLENAME, OtoUniChild.getColumnName(), OtoUniChild.class, "AGRIDB2019");
}
}
OtoUniChildRepository2019Generated
public interface OtoUniChildRepository2019Generated extends HeartcoreRepository<OtoUniChild> {
OtoUniChild findByIdAndOtoUniParentIsNotNull(#Param("id") String id);
OtoUniChild findByOtoUniParentId(#Param("id") String id);
}
#Transactional
class OtoUniChildRepository2019GeneratedImpl extends HeartcoreRepositoryImpl<OtoUniChild> {
#PostConstruct
private void setIni() {
super.setIni(OtoUniChild.TABLENAME, OtoUniChild.getColumnName(), OtoUniChild.class, "AGRIDB2019");
}
}
HeartcoreRepository
#NoRepositoryBean
public interface HeartcoreRepository<T extends Heartcore> extends RevisionRepository<T, String, Integer>, PagingAndSortingRepository<T, String>, HeartcoreCustomRepository<T> {
#Override
T findOne(String id);
boolean existsById(String id);
#Override
Collection<T> findAll();
List<T> findAllByKanton(#Param("kanton") String kanton);
}
HeartcoreCustomRepository
public interface HeartcoreCustomRepository<T extends Heartcore> {
List<T> findCustom(String sqlQuery);
List<T> findCustom(String select, String where);
Class<T> getType();
T findOne(String id);
Collection<T> findAll();
String getSequence(String sequenceName);
}
HeartcoreCustomRepositoryImpl
#Transactional
public class HeartcoreRepositoryImpl<T extends Heartcore> implements HeartcoreCustomRepository<T> {
#PersistenceContext
protected EntityManager entityManager;
// irrelevant code
public void setIni(String tablename, List<String> columns, Class<T> type, String schema) {
this.tablename = tablename;
this.columns = columns;
this.type = type;
this.schema = schema;
MultitenantDataSource multitenantDataSource = (MultitenantDataSource) entityManager.getEntityManagerFactory().getProperties().get("hibernate.connection.datasource");
DataSource dataSource = (DataSource) multitenantDataSource.determineTargetDataSource();
try {
this.dbDriver = dataSource.getConnection().getMetaData().getDriverName();
}
catch (SQLException e) {
e.printStackTrace();
}
}
// irrelevant code
With 1.5.8 it works fine and I couldn't find infos about breaking changes.
EDIT: Is something wrong with this inheritance structure of the repositories? I tried some different approaches but none worked. Is there an other way to implement some basic functionality for repositories?

Strange behaviour when integrate Spring JPA Data and Spring Cache

When I integrate Spring JPA Data and Spring Cache, there is a strange behaviour I can't explain.
I am using Spring Boot to setup my demo project. The code is below.
My config bean:
#Configuration
public class AppConfig {
#Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("Person");
}
}
My entity bean.
#Entity
public class Person implements Serializable {
private static final long serialVersionUID = 2263555241909370718L;
#Id
#GeneratedValue
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
My JPA interface. I overwrite some methods from JpaRepository and add #cachable annotation.
public interface PersonRepository extends JpaRepository<Person, Long> {
#Override
#CacheEvict(value = "Person", allEntries = true)
public void delete(Long id);
#Cacheable("Person")
public Person findByName(String name);
#Override
#Query("select p from Person p where p.id = :id + 1L")
#Cacheable("Person")
public Person findOne(#Param("id") Long id);
}
My unit test class
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = SpringDataDemoApplication.class)
public class SpringDataDemoApplicationTests {
#Autowired
private PersonRepository personRepository;
#Autowired
private CacheManager cacheManager;
#Before
public void setup() {
Person p1 = new Person();
p1.setName("Chris");
personRepository.save(p1);
}
#Test
public void test2() {
JpaRepository<Person, Long> jpaRepository = personRepository;
Person p = personRepository.findOne(0L);
Assert.assertNotNull(p);
p = personRepository.findOne(0L);
Assert.assertNotNull(p);
System.err.println("---------------------------------------");
p = jpaRepository.findOne(0L);
Assert.assertNotNull(p);
p = jpaRepository.findOne(0L);
Assert.assertNotNull(p);
}
}
The output is very strange.
Hibernate: insert into person (id, name) values (default, ?)
Hibernate: select person0_.id as id1_0_, person0_.name as name2_0_ from person person0_ where person0_.id=?+1
---------------------------------------
Hibernate: select person0_.id as id1_0_, person0_.name as name2_0_ from person person0_ where person0_.id=?+1
Hibernate: select person0_.id as id1_0_, person0_.name as name2_0_ from person person0_ where person0_.id=?+1
It should only print out one sql statement for my expect. The jpaRepository.findOne(0L) doesn't use the cache object.
The cache annotation is not working after I assign the PersonRepository interface to its parent interface, JpaRepository.
These 2 variables are exactly point to the same reference, even it a proxy object. Why call the same reference's method causing 2 difference result?
I also notice that the #Query annotation is working well. Both the JpaRepository and PersonRepository references use the customized SQL.
I guess there maybe some differences between how Spring Cache and Spring JPA Data generate the proxy advisor. Is that possible a bug here?
Add #EnableCaching to your configuration:
#EnableCaching
#Configuration
public class AppConfig {
#Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("Person");
}
}
Declaring the cache annotations does not automatically trigger their actions per se, you should declaratively enable the Caching behavior by using EnableCaching annotation. One of the advantages of this approach is you can disable it by removing only one configuration line rather than all the annotations in your code.
I think I find the reason why this happened. I add some aop code to track what method be called in the repository.
#Aspect
#Configuration
public class AppConfig {
#Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager();
}
#AfterReturning("execution(* org..*Repository.*(..))")
public void logServiceAccess(JoinPoint joinPoint) {
Arrays.asList(joinPoint.getTarget().getClass().getMethods()) //
.stream() //
.filter(m -> m.getName().startsWith("findOne")) //
.forEach(m -> System.err.println(m));
System.err.println("Completed: " + joinPoint);
}
}
The output is
public final java.lang.Object com.sun.proxy.$Proxy66.findOne(java.io.Serializable)
public final org.chris.demo.domain.Person com.sun.proxy.$Proxy66.findOne(java.lang.Long)
Spring container proxy 2 findOne methods with different arguments. I think that caused by generic code as all generic code will be removed after compile.
When I use the parent interface call the method, it call the public final java.lang.Object com.sun.proxy.$Proxy66.findOne(java.io.Serializable)
method. And on that method, there is not way to add #cacheable annotation.
I don't know whether there is way to focus Spring container generate only one findOne method when there is a sub-interface overwrite the method by generic programming.

How to prevent some HTTP methods from being exported from my MongoRepository?

I'm using spring-data-rest and I have a MongoRepository like this:
#RepositoryRestResource
interface MyEntityRepository extends MongoRepository<MyEntity, String> {
}
I would like to allow the GET methods but disable PUT, POST, PATCH and DELETE (read only web service).
According to http://docs.spring.io/spring-data/rest/docs/2.2.2.RELEASE/reference/html/#repository-resources.collection-resource I should be able to do that like this:
#RepositoryRestResource
interface MyEntityRepository extends MongoRepository<MyEntity, String> {
#Override
#RestResource(exported = false)
public MyEntity save(MyEntity s);
#Override
#RestResource(exported = false)
public void delete(String id);
#Override
#RestResource(exported = false)
public void delete(MyEntity t);
}
It doesn't seem to work as I can still do PUT, POST, PATCH and DELETE requests.
Thanks to Oliver, here are the methods to override:
#RepositoryRestResource(collectionResourceRel = "people", path = "people")
public interface PersonRepository extends MongoRepository<Person, String> {
// Prevents GET /people/:id
#Override
#RestResource(exported = false)
public Person findOne(String id);
// Prevents GET /people
#Override
#RestResource(exported = false)
public Page<Person> findAll(Pageable pageable);
// Prevents POST /people and PATCH /people/:id
#Override
#RestResource(exported = false)
public Person save(Person s);
// Prevents DELETE /people/:id
#Override
#RestResource(exported = false)
public void delete(Person t);
}
This is late reply, but if you need to prevent the global http method for a entity, try it.
#Configuration
public class DataRestConfig implements RepositoryRestConfigurer {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.getExposureConfiguration()
.forDomainType(Person.class)
.withItemExposure(((metdata, httpMethods) -> httpMethods.disable(HttpMethod.PUT, HttpMethod.POST, ... )))
.withCollectionExposure((metdata, httpMethods) -> httpMethods.disable(HttpMethod.PUT, HttpMethod.POST, ...));
}
}
Why not just use like this?
#Configuration
public class SpringDataRestConfiguration implements RepositoryRestConfigurer {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration restConfig) {
restConfig.disableDefaultExposure();
}
}

QueryDslMongoRepository for different collections

I am trying to implement a QueryDslMongoRepository for a model "Document"
#QueryEntity
#Document(collection="currentDocuments")
public class DocumentImpl extends TranslatableObjectImpl implements Document
In our current implementation a to be deleted document moves von "currentDocuments" into "deletedDocuments" collection.
I cant find a solution to create a repository like this
public interface DocumentRepository extends MongoRepository<DocumentImpl, String> ,QueryDslPredicateExecutor<DocumentImpl> {}
with a dynamic collection name.
My goal is to have the advantages of queryDsl in one Repository for different collections and to be able to move models from one collection into another like
public move(DocumentImpl entity, String sourceCollection, String targetCollection){
repository.delete(entity,sourceCollection);
repository.save(entity,targetCollection);
}
or something like
public List<Document> findAllDocumentsWithAttachments(String collectionName){
return repository.findAll(QDocumentImpl.documentImpl.attachments.isNotEmpty(), collectionName);
}
Any suggestions?
I implemented this feature by creating an own FactoryBean extending MongoRepositoryFactoryBean.
According to the Answer of this -> Question <- I implemeted following solution.
Entity
#QueryEntity
public class Document extends AbstractObject {
}
Custom QuerydslMongoRepository
public interface CustomQuerydslMongoRepository<T extends AbstractObject,ID extends Serializable> extends MongoRepository<T, ID> ,QueryDslPredicateExecutor<T>{
List<T> findAll(Predicate predicate, String collectionName);
T save(T entity, String collectionName);
...
}
Custom QuerydslMongoRepository Implementation
public class CustomQuerydslMongoRepositoryImpl<T extends AbstractObject,ID extends Serializable> extends QueryDslMongoRepository<T,ID> implements CustomQuerydslMongoRepository<T,ID> {
//All instance variables are available in super, but they are private
private static final EntityPathResolver DEFAULT_ENTITY_PATH_RESOLVER = SimpleEntityPathResolver.INSTANCE;
private final EntityPath<T> path;
private final PathBuilder<T> pathBuilder;
private final MongoOperations mongoOperations;
public CustomQuerydslMongoRepositoryImpl(MongoEntityInformation<T, ID> entityInformation, MongoOperations mongoOperations) {
this(entityInformation, mongoOperations,DEFAULT_ENTITY_PATH_RESOLVER);
}
public CustomQuerydslMongoRepositoryImpl(MongoEntityInformation<T, ID> entityInformation, MongoOperations mongoOperations, EntityPathResolver resolver) {
super(entityInformation, mongoOperations, resolver);
this.path=resolver.createPath(entityInformation.getJavaType());
this.pathBuilder = new PathBuilder<T>(path.getType(), path.getMetadata());
this.mongoOperations=mongoOperations;
}
#Override
public List<T> findAll(Predicate predicate, String collectionName) {
MongodbQuery<T> query = createQueryFor(predicate,collectionName);
return query.list();
}
#Override
public T save(T entity,String collectionName){
Assert.notNull(entity, "Entity must not be null!");
mongoOperations.save(entity, collectionName);
return entity;
}
private MongodbQuery<T> createQueryFor(Predicate predicate,String collectionName) {
Class<T> domainType = getEntityInformation().getJavaType();
MongodbQuery<T> query = new SpringDataMongodbQuery<T>(getMongoOperations(), domainType,collectionName);
return query.where(predicate);
}
}
Custom Repository Factory
public class CustomQueryDslMongodbRepositoryFactoryBean<R extends QueryDslMongoRepository<T, I>, T, I extends Serializable> extends MongoRepositoryFactoryBean<R, T, I> {
#Override
protected RepositoryFactorySupport getFactoryInstance(MongoOperations operations) {
return new CustomQueryDslMongodbRepositoryFactory<T,I>(operations);
}
public static class CustomQueryDslMongodbRepositoryFactory<T, I extends Serializable> extends MongoRepositoryFactory {
private MongoOperations operations;
public CustomQueryDslMongodbRepositoryFactory(MongoOperations mongoOperations) {
super(mongoOperations);
this.operations = mongoOperations;
}
#SuppressWarnings({ "rawtypes", "unchecked" })
protected Object getTargetRepository(RepositoryMetadata metadata) {
return new CustomQuerydslMongoRepositoryImpl(getEntityInformation(metadata.getDomainType()), operations);
}
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
return CustomQuerydslMongoRepository.class;
}
}
}
Entity Repository
public interface DocumentRepository extends CustomQuerydslMongoRepository<Document, String>{
}
Usage in Service
#Autowired
DocumentRepository repository;
public List<Document> getAllDocuments(Predicate predicate){
return repository.findAll(predicate,"myCustomCollection");
}

Resources