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
Related
I'm writing a REST API using Spring and have certain clients to the service that cannot or will not change how they call my service.
Normally when sending a query param with a list of values you would just comma delimit the parameter and Spring will do the rest
curl http://host.com/api/endpoint?listParam=1,2,3
And the controller
#GetMapping("/api/endpoint")
public ResponseEntity endpoint(#RequestParam("listParam" List<String> listParam){
// Here, listParam is populated with 1,2,3
}
Unfortunately my clients are going to be passing lists with the bar | delimiter and it simply isn't possible to get them to change that.
Example: curl http://host.com/api/endpoint?listParam=1%7C2%7C3%7C
I would still like to use Spring to break these calls out into lists so I don't have to clutter my code with manual String.split() calls.
What I've already tried:
I found the #InitBinder annotation and wrote the following
#InitBinder
public void initBinder(WebDataBinder dataBinder){
dataBinder.registerCustomEditor(String[].class, new StringArrayPropertyEditor("|"));
}
However, this code doesn't seem to ever be called (watching with breakpoints) and requests using the bar as the delimiter fail with a 400 BAD REQUEST.
Any suggestions would be much appreciated, thanks!
404 is coming due to URL encoding issue.
You need to encode | then it will work, but it will create another problem, params would not be split.
To work around this you need to create a custom conversion that can convert String to Collection. For the custom conversion, you can check the StringToCollectionConverter class. Once you have custom conversion then you can register that service, in any of the configuration classes add following function
#Autowired
void conversionService(GenericConversionService genericConversionService) {
genericConversionService.addConverter(myStringToCollectionConvert());
}
#Bean
public MyStringToCollectionConvert myStringToCollectionConvert() {
return new MyStringToCollectionConvert();
}
In this MyStringToCollectionConvert is class that will parse String and converts to a collection of Strings.
I've accepted Sonus21's answer since his suggestion allowed me to hunt down an example that worked, but my solution was not exactly his.
The class StringToCollectionConverter did in fact exist for me, but it wasn't accessible and I couldn't use it in any way. However, in looking at the interface it implemented (ConditionalGenericConverter) and searching for more examples with Spring converters I eventually settled on the following solution.
The listParam in my question actually refers to a set of Enum values. The first thing I did was rewrite my controller to actually use the Enum values instead of raw Integers.
#GetMapping("/api/endpoint")
public ResponseEntity endpoint(#RequestParam("listParam" List<EnumClass> listParam){
// ...
}
Next, I wrote a Spring Custom Converter (Baeldung Doc)
public class CustomStringToEnumClassListConverter implements Converter<String, List<EnumClass>> {
#Override
public List<EnumClass> convert(String str) {
return Stream.of(
str.split("\\|")) // Here is where we manually delimit the incoming string with bars instead of commas
.map(i -> EnumClass.intToValue(Integer.parseInt(i))) // intToValue is a method I wrote to get the actual Enum for a given int
.collect(Collectors.toList());
}
}
Finally, I wrote a Config Bean and registered this Custom Converter with Spring:
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Override
public void addFormatters(FormatterRegistry registry){
registry.addConverter(new CustomStringToEnumClassListConverter());
}
}
Once all of this was done, Spring automatically populated the listParam list with EnumClass objects.
Similar question has been asked and answered back in 2015 here.
However , rather than storing queries in XML files , I would like to store them in external .sql files and read query from there.
To achieve this in our spring boot application - 2.1.5.RELEASE with Java 8 , we are storing queries in resources folder as below
src/main/resources
- queries
- A.sql
- B.sql
To read the above queries , I'm reading them in QueryReader class as below
#Component
public class QueryReader {
public String getQueryFromFile(String fileName) {
System.out.println("getQueryFromFile() : " + fileName);
return this.convertInputStreamToString(this.getTemplateAsStream(fileName));
}
private InputStream getTemplateAsStream(String queryFileName){
ClassLoader classLoader = getClass().getClassLoader();
InputStream iStream =classLoader.getResourceAsStream("queries/" + queryFileName);
return iStream;
}
}
And to use it anywhere in the code , i'm having below class , so that i can call it's methods
#Component
public class MyQueries {
#Autowired
private QueryReader qfr;
public String queryErroredRecords() {
return qfr.getQueryFromFile("A.sql");
}
While using it with JDBCTemplate , this works as expected but when I'm trying to use this method from #Query annotation from Repository , i'm not able to do so with errors as below.
Repository Code
#Repository
public interface AdminRepository extends JpaRepository<OrderBackupRecordEO, BigDecimal>{
#Autowired
public MyQueries queries;
#Query(value = queries.queryErroredRecords() , nativeQuery= true)
List<String> findX();
}
Above repository is giving errors as below :
1.The value for annotation attribute Query.value must be a constant expression (For #Query annotation)
2.The blank final field queries may not have been initialized (For #Autowired annotation)
How can I make it work?
I would just add a note that may be helpful for you.
Your solution cannot work just because in java in an interface all the fields (variables) are by default public, static and final and that why you cannot #Autowire any dependencies inside the interface.
That's why it works for your case for JDBCTemplate (which is a class) and doesn't work for spring data repository (which is an interface).
Spring already has a solution "from the box", you don't need to make all these manipulations.
#Repository
public interface AdminRepository extends JpaRepository<OrderBackupRecordEO, BigDecimal> {
List<String> findX();
}
File src/main/resources/META-INF/jpa-named-queries.properties:
OrderBackupRecordEO.findX=\
SELECT record FROM OrderBackupRecordEO record WHERE ...
That's all! No any loading, manipulations, manual handling - an implementation is hidden in Spring - very simple and reliable.
In my app I have a foreign key relation between Things and Stuff where a given Thing may contain hundreds of Stuffs and I'm using Spring Data JPA to expose the Thing and Stuff repository's.
I want to display all the Stuff associated with the users selected Thing, but because of the size of the return I want to page the Stuff result.
Searching showed that it is not possible to add paging functionality to the embedded stuff links from a Thing return, so the below link as returned from my Thing repository can never be paged:
"stuff": {
"href": "http://localhost:8080/api/things/1/stuff"
}
So I have added a custom method to my Stuff repository to get all Stuff by thing Id, and that works fine when called directly.
I want to add a Link to the Thing Resource return pointing at the custom search method to get all the assocated Stuff, but when I use the ControllerLinkBuilder.linkTo() method it fails with
java.lang.IllegalArgumentException: 'uriTemplate' must not be null
at org.springframework.util.Assert.hasText(Assert.java:181) ~[spring-core-4.3.11.RELEASE.jar:4.3.11.RELEASE]
at org.springframework.web.util.UriTemplate.<init>(UriTemplate.java:61) ~[spring-web-4.3.11.RELEASE.jar:4.3.11.RELEASE]
Stuff Repo:
public interface StuffRepo extends JpaRepository<Stuff, Long> {
Page<Stuff> findByThingId(#Param("thingId") Long thingId, Pageable pageable);
}
Configuration:
#Bean
ThingProcessor getThingProcessor()
{
return new ThingProcessor();
}
public static class ThingProcessor implements ResourceProcessor<Resource<Thing>>{
#Override
public Resource<Thing> process(Resource<Thing> resource) {
ControllerLinkBuilder.linkTo(ControllerLinkBuilder.methodOn(StuffRepo.class).findByThingId(resource.getContent().id, null));
return resource;
}
}
Am I missing some annotation or configuration? I have tried annotating the Repo and the method with #RestResource and it makes no difference. Also is there a better way to get a paged result for the sub objects?
I got around this using the RepositoryEntityLinks class, that gives you a list of Link objects, and from there I find the one I need using link.getRel()
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);
}
I am working on three different tables. I am using Hibernate to query these tables. I implemented successfully the DAO and the service layers, but i have few problems with the controller package. Here is my code, my controller package contains 3 classes , each should handle a table (i have 3 tables as i said before).
#Controller
public class Ods_Gis_Actel_Controller {
Param_Gis_Actel_Controller Param = new Param_Gis_Actel_Controller();
Tbl_Dim_Actel_Controller Dim = new Tbl_Dim_Actel_Controller();
#Autowired
Ods_Gis_Actel_metier service;
#RequestMapping(value="/index")
public String pageIndex(Model model)
{
addOdsTable(model);
Param.addParamTable(model);
Dim.addDimTable(model);
return "Affichage";
}
public void addOdsTable(Model model)
{
model.addAttribute("listeOds",service.getAll());
}
}
#Controller
public class Param_Gis_Actel_Controller {
#Autowired
Param_Gis_Actel_metier service;
public void addParamTable(Model model)
{
model.addAttribute("listeParam",service.getAll());
}
}
#Controller
public class Tbl_Dim_Actel_Controller {
#Autowired
Tbl_Dim_Actel_metier service;
public void addDimTable(Model model)
{
model.addAttribute("listeDim",service.getAll());
}
}
The request mapping is done in the 1st class, whose method calls 2 other methods from the other classes. But it seems, that the autowiring works only in the class, where the RequestMapping is performed.
Is this true?
how can i use the other methods from the classes which don't contain the RequestMapping if the autowiring doesn't work for them?
I gone through your problem , I think you are not so much aware the objective of #Controller , #RequestMapping . So First of all you need to know , why we use #Controller?, this is used to give business logic to your request. When request is hited from user , then your DispatcherServlet match the url from your request to value of RequestMapping annotation of all defined controller. And according to that , the matched mapping method is called and further procees done by framework. Now come to #Autowire, this is used to load the bean class definition from the xml configuration. So the #Autowire and #RequestMapping having different objective . So it's wrong to say here that the
**autowiring** works only in the class where the RequestMapping is performed.
Now your second question , How you can use simple class? there are two ways to achieve that as far as I know,
1) To create the Object of that class inside your class as you done in your code
2) To create the instance of that class using factory-method.
for the second point , you have to first define your class inside the configuration file by following the below format
<bean id="paramGis" class="<whatever_package_detail>.Param_Gis_Actel_Controller" factory-method="createInstance"/>
here one things you have to care that this method should be static .
and your class would look like that
#Service
public class Param_Gis_Actel_Controller {
private static Param_Gis_Actel_Controller paramGis;
public static Param_Gis_Actel_Controller createInstance(){
if(paramGis==null){
return new Param_Gis_Actel_Controller();
}
return paramGis;
}
public void addParamTable(Model model)
{
model.addAttribute("listeParam",service.getAll());
}
}
If you are still getting problem let me know.
I think you are having difficulties with the Java/Spring way. We don't use #Controller/#Autowired like that.
It's kind of hard to explain shortly (I strongly recommend you read the official document for that), but in short, you shouldn't create a Controller object inside another controller. The objects with annotation marks (#Controller, #Service...) should be generated and managed by Spring. At initiation time they will be injected with the #Autowired services by "Spring" way. Of courses you can intervene into that process, but by other special methods.
P/s: your naming convention is not for Java ;). If you create a program for personal use it maybe ok, but you will have difficult times collaborating with other Java developers.
Through method name as default one for access that particular method or use #Qualifier annotations.