I have the Spring Boot application with Spring Data REST.
I have the following classes in it:
Data JPA repository for authentication purposes:
public interface UserRepository extends JpaRepository<User, Long> {
User findByUsername(String username);
}
Secured Data REST repository for API usages:
#RepositoryRestResource
#Secured(Role.ROLE_USER_READ)
public interface UserDataRestRepository extends PagingAndSortingRepository<User, Long> {
#Override
#Secured(Role.ROLE_USER_WRITE)
<S extends User>S save(S entity);
#Override
#Secured(Role.ROLE_USER_DELETE)
void delete(Long id);
}
Repository REST configurer adapter:
#Configuration
public class RepositoryRestConfig extends RepositoryRestConfigurerAdapter {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.setRepositoryDetectionStrategy(RepositoryDetectionStrategies.ANNOTATED);
config.setReturnBodyOnCreate(true);
config.setReturnBodyOnUpdate(true);
config.setReturnBodyForPutAndPost(true);
}
}
The problem is when I start up my application, Data REST repository for API is unavailable from time to time. I guess it's because Spring rewrites repository bean for type User with the first JPA repository.
In Actuator beans endpoint I can see both beans even if REST API says 404 for /users page.
Again, this behavior is unpredictable for me - sometimes it works, sometimes doesn't.
Do you know the way how to tell to Spring to use the exact bean for Data REST?
Thanks in advance.
After some investigation, I've found exactly the same issue posted here.
Also, see this one.
Finally, I've merged 2 repositories into one and it solved my problem. But this is definitely not the way I prefer.
Related
I have a following problem. I made an application which uses spring-data and exposes it as a REST service using spring-data-rest. Everything went smooth till I wanted to have a custom implementation. I've created a CustomSomethingRepository and SomethingRepositoryImpl with one additional method. Spring data repository interface extended CustomSomethingRepository and everything was fine, I was able to execute my method from test directly, custom implementation was executed as well. Then I tried to get it through REST api and here I was surprised that this method is not available through /somethings/search . I'm almost hundred percent sure that it worked fine in spring boot 1.3.x and JpaRepositories. Now I'm using boot 1.5.x and MongoRepository. Please take a look at my example code:
#RepositoryRestResource
public interface SomethingRepository extends CrudRepository<Something>, CustomSomethingRepository {
//this one is available in /search
#RestResource(exported = true)
List<Something> findByEmail(String email);
}
and custom interface
public interface CustomSomethingRepository {
//this one will not be available in /search which is my problem :(
List<Something> findBySomethingWhichIsNotAnAttribute();
}
and implementation
#RepositoryRestResource
public class SomethingRepositoryImpl implements CustomSomethingRepository {
#Override
public List<Something> findBySomethingWhichIsNotAnAttribute() {
return new ArrayList<>(); //dummy code
}
}
Could you please give me a hint how can I expose CustomSomethingImpl as a part of Rest endpoint without creating another regular spring mvc bean which will be just handling this single request?
I've read questions like this: Implementing custom methods of Spring Data repository and exposing them through REST which state that this is not possible to achieve, but believe me or not, I had a project with spring-boot 1.3.x and those implementations were exposed as well :).
Thank you!
Since your custom method is returning a List you should put it in SomethingRepository which spring data rest will put it on the /search path. Add List findByNotAttribute()
#RepositoryRestResource public interface SomethingRepository extends CrudRepository<Something> {
#RestResource(exported = true)
List<Something> findByEmail(String email);
List<Something> findByNotAttribute(#Param String attribute);
}
So, I have the exact same question as you. I have a not fully tested solution because Im still working on it. I don't like it because it seems to be a bit hacky...Also I haven't tested it out fully. This is how far I have gotten. In your CustomSomethingRepository add the #Query annotation to the method you want to expose. Inside the annotation add a valid query.
public interface CustomSomethingRepository {
#Query("select smt from Something smt")
List<Something> findBySomethingWhichIsNotAnAttribute();
Now in the class that implements your CustomSomethingRepository
#Repositorypublic
#Transactional(readOnly = true)
class SomethingRepositoryImpl implements CustomSomethingRepository {
#PersistenceContext
private EntityManager entityManager;
#Override
public List<Something> findBySomethingWhichIsNotAnAttribute() {
System.out.println("HELLO");
}
}
Now when you go to http://localhost/something/search you should see something like
{
"_links" : {
"findByEmail" : {
"href" : "http://localhost/something/search/findByEmail{?email}"
},
"findBySomethingWhichIsNotAnAttribute" : {
"href" : "http://localhost/something/search/findBySomethingWhichIsNotAnAttribute"
},
"self" : {
"href" : "http://localhost/something/search/"
}
}
}
When you point your browser to http://localhost/something/search/findBySomethingWhichIsNotAnAttribute you will see HELLO printed and the query inside the #Query annotation will NOT run.
I'm facing another problem. In the SomethingRepositoryImpl I want to be able to call the findAll() method(s) in the SomethingRepository but if I autowire the SomethingRepository to SomethingRepositoryImpl the application errors out because it detects a cycle.
The dependencies of some of the beans in the application context form
a cycle:
I'm using Google Guava Cache + Spring cache abstraction for caching purpose.
I'm trying to make use of Guava's Loading Cache interface for the same.
I know Spring provides support for Guava Cache, but I was wondering whether I can make use of spring's cacheable annotation alongwith Loading Cache?
Basically I wanted to keep the business layer separate from the Cache.
Kindly help. Thanks.
Guava Cache is deprecated. If you'd existing code, that'd be another matter, but for new code, use Caffeine.
Put a #Cacheable("myCacheName") on the method that you want to cache the return value for.
Put a #EnableCaching on your application class if using Spring Boot, otherwise on some #Configuration class.
Set the spec in application.properties if using Spring Boot, like so: spring.cache.caffeine.spec=maximumSize=10000,expireAfterWrite=5m. If not using Boot, use #PropertySources annotation on the same class as in #3 above.
Add org.springframework.boot:spring-boot-starter-cache and com.github.ben-manes.caffeine:caffeine to your build file. If not using Boot, you'll need to set up the dependencies differently.
You're done.
So you want both butter and jam. Okay. I will help you use loading cache along with keeping caching logic separate.
Consider you have a service class SampleServiceImpl which implements SampleService interface.
Service interface:
public interface SampleService {
User getUser(int id);
}
Service Implementation:
#Service
public class SampleServiceImpl implements SampleService {
public User getUser(int id) {
// fetch user from database
return user;
}
}
Create one more class SampleServiceCache
public class SampleServiceCache extends ServiceCacheImpl {
#Autowired
public SampleServiceCache(int expiryTime, int maximumSize) {
loadingCache =
CacheBuilder.newBuilder().maximumSize(maximumSize).expireAfterAccess(expiryTime, TimeUnit.HOURS).build(
new CacheLoader<Integer, User>() {
#Override
public User load(#Nonnull Integer userId) {
return SampleServiceCache.super.getUser(userId);
}
});
}
#Override
public User getUser(int userId) {
return loadingCache.getUnchecked(userId);
}
}
In you bean config:
#Bean
public SampleService sampleService() {
return new SampleServiceCache(expiry, maxSize);
}
The day you want to remove cache, you have to do two things:
1. Remove the cache class.
2. Change bean config to return actual implementation object rather than cache implementation object.
P.S. You can define multiple loading caches for different behaviors say user retrieval, article retrieval, etc.
I am running a spring boot 1.5.2 app.
I wanted to add caching to my service methods
I have added the spring-boot-starter-cache maven dependency and I am using the # Cacheable annotations but it is not taking effect
I created my service beans in a # Configuration class - here is an example
#Bean(name = "policyService")
public IPolicyService policyService() {
policyService = new PolicyServiceImpl();
return policyService;
}
Here is an example of my service method
#Cacheable(value="policiesCache")
public List<PolicyDBO> findAllPolicies() {
LOG.info("Entered findAllPolicies");
List<PolicyDBO> policyList = policyRespoitory.findAll();
LOG.info("Exiting findAllPolicies");
return policyList;
}
My repoistory interface method is as follows
#Repository
public interface PolicyRepository extends CrudRepository<PolicyDBO, Long>{
/** Find policy by id **/
PolicyDBO findById(Long policyId);
}
Whenever I call this service method from a RestController - the caching is never triggered - it looks like it is not setup properly
Any ideas what I can do to get caching setup correctly?
Thanks
Damien
Assuming that caching is active, Spring Caching is working correctly, but not as you expect. #Cacheable caches arguments against results.
In your case, the cache is storing no arguments against the result of List<PolicyDBO>.
However when you call findById, the cache doesn't find anything against an argument of Long and so doesn't return a cached result.
1 quick question on Spring JPA repositories transactionality.
I have a service that is not marked as transactional and calls Spring JPA repository method
userRegistrationRepository.deleteByEmail(email);
And it is defined as
#Repository
public interface UserRegistrationRepository extends JpaRepository<UserRegistration, Long> {
UserRegistration findByEmail(String email);
void deleteByEmail(String email);
}
The problem is that it fails with "No EntityManager with actual transaction available for current thread - cannot reliably process 'remove' call; nested exception is javax.persistence.TransactionRequiredException" exception.
Ok, I can solve it by marking the service or deleteByEmail(..) method as transactional, but I just can't understand why it crashes now. Spring documentation explicitly states that "CRUD methods on repository instances are transactional by default." (http://docs.spring.io/spring-data/jpa/docs/current/reference/html/#transactions), but apparently this one is not... So Is this statement related to only members of CrudRepository?
ps: that's for Spring Data JPA 1.9.4
You are right. Only CRUD methods (CrudRepository methods) are by default marked as transactional.
If you are using custom query methods you should explicitly mark it with #Transactional annotation.
#Repository
public interface UserRegistrationRepository extends JpaRepository<UserRegistration, Long> {
UserRegistration findByEmail(String email);
#Transactional
void deleteByEmail(String email);
}
You should also be aware about consequences of marking repository interface methods instead of service methods. If you are using default transaction propagation configuration (Propagation.REQUIRED) then:
The transaction configuration at the repositories will be neglected
then as the outer transaction configuration determines the actual one
used.
http://docs.spring.io/spring-data/jpa/docs/current/reference/html/#transactions
If you want more information about how it is implemented, take a look at default CrudRepository / JpaRepository implementation - SimpleJpaRepository (which you are probably using):
https://github.com/spring-projects/spring-data-jpa/blob/master/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java
The interesting lines are here:
#Transactional(readOnly = true)
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {
and some of transactional methods here:
#Transactional
public void deleteById(ID id) {
#Transactional
public <S extends T> S save(S entity) {
I want to make a simple java application and I want to use CRUDREPOSITORY and my own repository. I have this:
#RestController
#Transactional
#ExposesResourceFor(Person.class)
#RequestMapping("/prueba")
public class PersonController {
#RequestMapping(value="/prueba", method=RequestMethod.GET)
public String person(#RequestParam(value="id", defaultValue="1") int id) {
return "hola"+id;
}
}
this:
#RepositoryRestResource
public interface IClientRepository extends CrudRepository<Client, Long> {
}
The problem is that the CRUD works well, but I canĀ“t call my localhost:8080/prueba/prueba because it gives a 404 error. I have try all and I cant access it, please help me!!
By default, Spring Data REST serves up REST resources at the root URI, "/". There are multiple ways to change the base path.
With Spring Boot 1.2+, add below to application.properties:
spring.data.rest.basePath=/api
In your case:
spring.data.rest.basePath=/prueba/prueba
, assuming there is no override for server.contextPath in application.properties
Reference:
http://docs.spring.io/spring-data/rest/docs/current/reference/html/