Defining custom methods with path "/{resourcename}/search/" using spring-data-rest - java

I am confused. I could not find out, how to define together custom "search" methods with methods that were loaded with help of spring-data-rest.
Could you answer me, does the framework has this possibility "out-of-box"?
And if has, could you tell me, where can i find it?
For a deeper understanding my situation i describe my issue:
class UserService {
public String getListOfWaitingUsers() {
return userRepository.findAll(UserSpecification.isWaiting());
}
}
public interface UserRepository extends PagingAndSortingRepository<User, Long>{
Page<User> findByNameLike(#Param("name") String name, Pageable pageable);
}
I want that it to be like:
/user/search/
findByNameLike
findWaitingUsers
How to implement that my methods of specifications or services (there is not method in repository) will define with path "/resource_name/search/METHOD_NAME" ( methods of repository + ( METHODS SERVICES OR SPECIFICATIONS)

Spring Data REST framework is based on Spring Data Respository, so your service class can be ignored here.
All methods that are not part of CRUD/Paging Repository as exposed as "search" methods provided you annotated all parameters with #Param annotation. So in your case, you need to implement your method following the conventions outline in Spring Data commons docs. So once you have implementation for findByNameLike method, the method would be exposed as ../search/findByNameLike URL. If needed, you could customize the rel and path with #RestResource annotation.
Also note your UserRepository should ideally be working only on User object and hence the methods you defined. In your case, UserRepository is returning Process/ProcessContext objects? Instead it should be like below
public interface UserRepository extends PagingAndSortingRepository<User, Long>{
Page<User> findByNameLike(#Param("name") String name, Pageable pageable);
}

Related

Spring MVC: issue between xml and annotation configurations

I have created a simple controller
#GetMapping("/playerAccount")
public Iterable<PlayerAccount> getPlayerAccounts(com.querydsl.core.types.Predicate predicate) {
return repository.findAll(predicate);
}
When I call the GET /playerAccount API, I get the exception IllegalStateException "No primary or default constructor found for interface com.querydsl.core.types.Predicate" (thrown by org.springframework.web.method.annotation.ModelAttributeMethodProcessor#createAttribute).
After some (deep!) digging, I found out that if I delete the following line in my spring.xml file:
<mvc:annotation-driven />
And if I add the following line in my Spring.java file:
#EnableWebMvc
then the problem disappears.
I really don't understand why. What could be the cause of that ? I thought that these were really equivalent (one being a xml based configuration, the other being java/annotation based).
I read this documentation on combining Java and Xml configuration, but I didn't see anything relevant there.
edit:
from the (few) comments/answers that I got so far, I understand that maybe using a Predicate in my API is not the best choice.
Although I would really like to understand the nature of the bug, I first want to address the initial issue I'm trying to solve:
Let's say I have a MyEntity entity that is composed of 10 different fields (with different names and types). I would like to search on it easily. If I create the following (empty) interface:
public interface MyEntityRepository extends JpaRepository<MyEntity, Long>, QuerydslPredicateExecutor<MyEntity> {
}
then without any other code (apart from the xml configuration ), I am able to easily search a myEntity entity in the database.
Now I just want to expose that functionality to a Rest endpoint. And ideally, if I add a new field to my MyEntity, I want that API to automatically work with that new field, just like the MyEntityRepository does, without modifying the controller.
I thought this was the purpose of Spring Data and a good approach, but please tell me if there's a better / more common way of creating a search API to a given Entity.
I didn't see that it returned an exception, that's why I thought it was a dependency problem.
Try to make your code look like this, and it will do it.
#RestController
public class MyClass {
#Autowired
private final MyRepository repository;
#GetMapping("/playerAccount")
public Iterable<PlayerAccount> getPlayerAccounts() {
return repository.findAll();
}
If you have a parameter in your request you add #RequestParam.
Code time (yaaaaaay) :
#RestController
public class MyClass {
#Autowired
private final MyRepository repository;
#GetMapping("/playerAccount")
public Iterable<PlayerAccount> getPlayerAccounts(#RequestParam(required = false) Long id) {
return repository.findById(id);
}
Ps: the request should keep the same variable name e.g
.../playerAccount?id=6

Spring Data Couchbase example can't resolve Repository instance

Using Couchbase Enterprise Edition 5.0.0 build 2873 and Spring Data Couchbase 2.1.2, I am getting the error explained at the bottom of the question. Go forward if you just need the short story.
If you want a little more explanation, here it comes:
Imagine that the CrudRepository methods are fine to me. I mean, I don't need to add any more methods.
How the repository would look like? Would I declare an empty repository just like this?
#Repository
public interface PersonRepository extends CrudRepository<Person, String> {
// Empty
}
Or would I use directly CrudRepositoy as my Base repository. I don't think so, because you need to pass Type and ID to the CrudRepository.
But the question is not that. Here comes the question:
How Spring knows how to instantiate PersonRepository, taking into account that there is no implementation of that base repository? Take a look to PersonService and PersonServiceImpl interface/implementation.
Interface:
#Service
public interface PersonService {
Person findOne (String id);
List<Person> findAll();
//...
}
Implementation:
public class PersonServiceImpl implements PersonService {
// This is the variable for the repository
#Autowired
private PersonRepository personRepository;
public Person findOne(String id){
return personRepository(id);
}
public List<Person> findAll(){
List<Hero> personList = new ArrayList<>();
Iterator<Person> it = personRepository.iterator();
while (it.hasNext()){
personList.add(it.next());
}
return personList;
}
//...
}
Is it really enough with declaring an empty PersonRepository extending from CrudRepository? No need to implement and say anything about each method of CrudRepository?. At least something to tell Spring about some constructor...
This doubt are all because I am getting this error when Spring tries to inject the personRepository variable:
Error creating bean with name 'personRepository': Could not resolve matching constructor (hint: specify index/type/name arguments for simple parameters to avoid type ambiguities).
Apparently, it is asking to have some class defining at least the constructor of the implementation. How do I tell Spring to avoid those type ambiguities mentioned in the error?
As for the repository you'll need to extend CouchbaseRepository if you're strictly working with Couchbase, by that your repository will expose lower level couchbase objects/functionality.
For example
public interface PersonRepository extends CouchbaseRepository<Person, String> {
}
For your service, you don't need to define findOne() and findAll(), those are strictly the responsibility of the repository.
For example
public interface PersonService {
void doOperationOnPerson(String personId);
}
#Service
public class PersonServiceImpl implements PersonService {
#Autowired
PersonRepository personRepo;
#Override
void doOperationOnPerson(String personId) {
Person person = personRepo.findById(personId);
//do operation
}
}
Note that the #Service annotation goes on the implementation. (It actually should work either way but I think having the annotation on the implementation is more proper)
If you need to define custom queries then it should be done inside the repository itself.
You also might need to define an empty constructor on your Person class if you have a non-default constructor.
I suggest you to read more about Spring Data.

Spring CommandLineRunner Interface Implementation

So I was following the tutorial here: https://spring.io/guides/gs/accessing-data-jpa/
and it works fine, I'm trying to implement it in my application (Because it makes JPA so easy to use), but I'm confused.
Where it has
#Bean
public CommandLineRunner demo(CustomerRepository repository)
and then it acts on the repository
repository.save(new Customer("Jack", "Bauer"));
repository.save(new Customer("Chloe", "O'Brian"));
How can it act on an interface? CustomerRepository is an interface, and I can't instantiate it
CustomerRepository c = new CustomerRepository()
cant be done, and I don't have any classes that implement the interface. I just wanted to do something like
CustomerRepository c = new CustomerRepository()
c.save(new Customer("whatever", "whatever")
but I can only use it in the CommandLineRunner bean method. Why can I do this with commandlinerunner but cant do it with another class?
I was thinking I could just make a class that extends CustomerRepository, but then I read that the interface actually does all the method implementation itself (JPA does it) so you don't have to worry about it.
public interface CustomerRepository extends CrudRepository<Customer, Long> {
List<Customer> findByLastName(String lastName);
}
so if I extended it, wouldn't I have to override the findbylastname() method, meaning JPA wouldn't do it itself?
Thanks for any assistance.
so if I extended it, wouldn't I have to override the findbylastname()
method, meaning JPA wouldn't do it itself?
No, it is not JPA which does the job but Spring which generates by APO some JPA processings.
With Spring Repository, you have multiples ways of doing :
write your own SQL/JPQL query.
use naming convention in the method name to write the query
In both cases, you don't need to implement directly the interface you declare here :
public interface CustomerRepository extends CrudRepository<Customer, Long> {
List<Customer> findByLastName(String lastName);
}
Because as you understood, the job is performed for you by Spring.
For example, in the case you quote, you use naming convention in the method name to write the query.
When Spring inspects your interface and sees the method findByLastName(String lastName), it associates the method name to a query which does a find with a match by lastName field. So, it generate a JPQL query like that :
select c from Customer c where c.lastName=%lastName
and it sets the lastName param with the effective parameter used when the method is call.
I extended it, wouldn't I have to override the findbylastname()
method, meaning JPA wouldn't do it itself ?
No, you don't need to implement the methods as spring-data-jpa will take care of it, you can look here on how Spring data repository interfaces are actually implemented by proxy at runtime.
The main point that you need to remember is that spring data has got few rules to derive the sql queries from the method names (like findByLastName(), findByLastnameOrderByFirstnameAsc(), etc..), you can look here to understand how method names work and they are related to field names of your entity bean.
If you wanted to write some complex queries which can't be derived from method names you can use #Query for your methods.
If I made a class public class Cust implements CustomerRepository what
would I do when it asks me I have to implement the
findByLastName(String lastName); method that JPA is supposed to take
care of ?
If you wanted to implement repository to provide your custom behaviour for few of the methods for few of your methods, you can do that (like class Cust implements CustomerRepository), you can refer the section "Using JpaContext in custom implementations", it is well explained in the ref doc.

PreAuthorize issue on common spring NoRepositoryBean base interface

I'm trying to write a generic SecurePagingAndSorting repository which will check security on CRUD operations to save repeating the same PreAuthorize (with different authorities) throughout all JPA repositories.
Here is a simple example below:
#NoRepositoryBean
public interface SecuredPagingAndSortingRepository<T, ID extends Serializable> extends PagingAndSortingRepository<T, ID> {
#Override
#PreAuthorize("hasPermission(#id, domainType, 'delete'))
void delete(ID id);
}
Now it's the domainType argument that's the problem here, since this is a generic interface, it can't be hard coded. What is the best approach here to get the domain type from repositories that derive from SecurePagingRepository.
The best solution I see is writing your own implementation of the interface PermissionEvaluator and then inject it in the security context replacing the default one.
Should you try this way, extending class AclPermissionEvaluator will save you lots of code already managed by Spring, and ensures back compatibility.
I settled on this solution in the end. PreAuthorize has the facility to use any bean within the Spel expression using the # character.
#Override
#PreAuthorize("hasPermission(#id, #security.getDeletePermission(#id,#this.this)))
void delete(ID id);
}
So when the 'security' bean's getDeletePermission function is called, the #this.this parameter translates to the SimpleJpaRepository in question. This allows us determine the concrete repository in question and returned the desired permission name

How to call SimpleJpaWriter methods from RepositoryItemWriter?

I want to use RepositoryItemWriter to write batch entities using the default implementation of SimpleJpaRepository.
#Autowired
private MyCrudRepository crudDao;
RepositoryItemWriter<HrsGiataId> w = new RepositoryItemWriter<>();
w.setRepository(crudDao);
w.setMethodName("deleteInBatch");
public interface MyCrudRepository extends CrudRepository<MyEntity, Long> {}
But the code above will not work as the w.setMethodName requires a method name from the CrudRepository interface, even though the default implementation for the crudrepository is SimpleJpaRepository, which has the deleteInBatch() method.
So, what can I do to make use of the spring crud repository specific jpa method?
I don't believe that the writer requires a method from the CrudRepository. You should be able to specify any method name you want. If not, I'd raise that as a bug in Jira (https://jira.spring.io/browse/BATCH/).

Categories