Spring Data JPA returning wrong results - spring-boot

I am testing Spring-Data-JPA with Spring-Boot, using hibernate-core 5.4.2.Final version.
For example, I have a Users table containing address and other columns.
My repository interface for this table is as below.
public interface UsersRepository extends JpaRepository<Users, Integer> {
List<Users> findByAddressContaining(String keyword)
}
To my knowledge, if I name the method like I did, the query below will be executed.
SELECT * FROM Users u WHERE address LIKE '%keyword%';
The problem is, this partly works.
For example, there are two datas in users table, each datas having address of "abc" and "cde".
If i test the findByAddressContaing("abc"), the result size of List<> is 2.
Right after if I run findByAddressContaining("de"), the result should be 1, but it is 2.
If i run the same method findByAddressContaining("de") again, then the result becomes 1.
I can't find the way to solve this problem. Any suggestions would be helpful. :)
THE PROBLEM WAS NOT WITH THE Spring-Data-JPA, it was with Request Parameter.
This is my controller method.
#GetMapping("/v2/warehouses")
public void getAllWarehouses(#RequestParam(name = "address") String address,
#RequestParam(name = "limit") Integer limit,
#RequestParam(name = "offset") Integer offset,
HttpServletResponse response) {
System.err.println("Request Param Address : " + address");
If I send request to the correct path using Postman, sometimes address comes as blank.("")
Does this happen normally??

So the basic problem was not with the #RequestParam annotation, it was with the application.properties file.
After I added server.tomcat.uri-encoding=UTF-8 to the properties file, the bug didn't happen anymore.
Thanks!

Related

Is there a way to map a Retrofit #QueryMap to some object for a Spring Rest Service?

The client interface looks like this
#GET("v3/users/posts")
Call<User> loadPosts(#QueryMap Map<String,String> data);
The RestController should then process the map of query data returning the user's post. There are multiple parameters that can be put in the map as shown in the UserService.findUserPosts(). Is it possible to use a map to pass data to the Spring Rest controller? The restriction I have is this is inherited from code using #Query parameters but it has now grown to quite a number and a query map would limit the changes on the client. I would be really grateful for some feedback. Many thanks
#RestController
public class UsersController{
#RequestMapping(value = "/user/posts", method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<User> getUserPosts(.......What here) {
List<Posts> posts = userService.findPostsBy(id,postKey,offset,when);
}
Solved it. For anyone in the future who wants to do what I am doing , simply provide Request parameters for every key pair in the map
i.e #RequestParam("id") String id,
#RequestParam("postKey") Long, #RequestParam("offset") etc.

Spring RestController - findById and findByEmail request method not working (Ambiguous handler)

I want to retrieve user's info either based on the ID or the Email. Below is the controller class I wrote:
#RestController
#RequestMapping("/users")
public class UserController {
#Autowired
private UserDao userDao;
#GetMapping(value = "/{id:\\d+}")
public User findOneById(#PathVariable("id") final Integer userId) {
return userDao.findById(userId).get();
}
#GetMapping(value = "/{email}")
public User findOneByEmail(#PathVariable("email") final String email) {
return userDao.findByEmail(email).get();
}
The code is not working and giving error
java.lang.IllegalStateException: Ambiguous handler methods mapped for HTTP path 'http://localhost:8080/users/12223': {public com.example.persistence.model.User com.example.rest.controller.UserController.findOneById(java.lang.Integer), public com.example.persistence.model.User com.example.rest.controller.UserController.findOneByEmail(java.lang.String)}.
I thought Regex would solve this issue but unfortunately it didn't.
The reason for error I understood, but what is the way to handle such requirement?
Your problem is that http://localhost:8080/users/12223 matches both /users/{id:\\d+} and /users/{email}. 12223 is a valid parameter for both methods:
12223 matches {id:\\d+} because it has all digits
12223 matches {email} because regex expression is not specified and any parameter will match email.
Spring can't select an appropriate endpoint and gives an error: Ambiguous handler methods mapped for HTTP path.
If you try another parameter, say: http://localhost:8080/users/somebody#example.com, there will be no error. Spring will be able to find out, that somebody#example.com doesn't match id and matches email.
As JB Nizet mentioned in the comments, you have 2 ways to solve this:
Specify regex for the e-mail to match e-mail format, something like {email:.+#.+\..+}
Clarify endpoints like pDer666 recommended:
#GetMapping(value = "/email/{email}")
#GetMapping(value = "/id/{id:\d+}")
There are different ways to solve this. It is possible to provide two GetMappings with different paths or you use query parameters in only one get request. If the Email is Set you retrieve data by Email If the other is Set retrieve it by the other parameter. With this solution you can easily add more parameters to search by and you have the posibility to query your data by OR or AND without adding a new controller method.
Url : http://localhost:8080/users?email=asdf#somewhere.com OR http://localhost:8080/users?id=1234
#GetMapping
#ResponseBody
public String findOne(#RequestParam("id") long id, #RequestParam("email") String email) {
...
}

Spring RestController url for findById and findByIds

In my Spring Boot application I have a following REST controller:
#RestController
#RequestMapping("/v1.0/decisions")
public class CriterionController {
#Autowired
private CriterionService criterionService;
#RequestMapping(value = "/{decisionId}/criteria/{criterionId}", method = RequestMethod.GET)
public CriterionResponse findById(#PathVariable #NotNull #DecimalMin("0") Long decisionId, #PathVariable #NotNull #DecimalMin("0") Long criterionId) {
Criterion criterion = criterionService.findById(criterionId);
return new CriterionResponse(criterion);
}
}
Everything is working fine and I'm able to retrieve Criterion by its ID.
Right now I need to add additional logic to my CriterionController that will retrieve Criterion by a set of IDs.
Right now I'm in doubt how it can be implemented.. For example should I add a separated endpoint something like:
/{decisionId}/criteria/{criterionIds}
or for example reuse existing one for this purpose or in some other way. Please advise how to implement it according to a best practice of REST.
This is a tricky question, but there are 2 options I can suggest:
/{decisionId}/criteria?id=1&id=2&id=3
or
/{decisionId}/criteria?id=1,2,3
The former could be seen as more RESTful but can end up with a very long URL since you'll be specifying the query parameter each time.
The latter aggregates the ids in a comma separated list. I personally prefer this option and would go for this.
Although not about REST, both URLs are accepted in Section 3.2.8 of RFC 6570

How does Spring Data JPA work internally

I was going through Spring Data JPA Tutorial.
I am confused on how does this framework work internally.
Let me state specific scenario
There was specific code
/**
* Custom finder
*/
public List<Location> getLocationByStateName(String name) {
#SuppressWarnings("unchecked")
List<Location> locs = entityManager
.createQuery("select l from Location l where l.state like :state")
.setParameter("state", name + "%").getResultList(); // note
return locs;
}
This was simply replaced by following interface
#Repository
public interface LocationJPARepository extends JpaRepository<Location, Long> {
List<Location> findByStateLike(String stateName);
}
And corresponding test case worked fine
#Test
public void testFindWithLike() throws Exception {
List<Location> locs = locationRepository.getLocationByStateName("New");
assertEquals(4, locs.size());
}
New test case
#Test
public void testFindWithLike() throws Exception {
List<Location> locs = locationJPARepository.findByStateLike("New");
assertEquals(4, locs.size());
}
My question
How does framework know if i am looking for exact match using = or partial match using SQL like operator (it cant be method name ?)
if it somehow decide I am looking for partial match then still there are sub options ... like name% or %name or %name% …
Also how it decides case is important in like ? ( i can have case-insensitive by using SQL like with toUpper() i.e. by comparing everything in upper case )
(added ques) is there a way i can check the EXACT SQL in log some where ??
Hope i was able to explain my question properly. Let me know if i need to add in more clarity.
I recommend to take a look at Query Creation section of the reference guide. It explains the rules pretty clearly.
For instance when you want to find User by first name and ignore case, you would use method name like findByFirstnameIgnoreCase which would translate into condition like UPPER(x.firstame) = UPPER(?1).
By default when you have findByProperty method, the match is exact, so if you want to have LIKE functionality you would use method name findByFirstnameLike which would in turn translate into condition where x.firstname like ?1.
You can combine these keywords, but it can get a little crazy. Personally I prefer using #Query annotation for more complicated queries to avoid super long repository method names.

Return custom-typed object from JpaRepository

I have the following repository:
public interface UserRepository extends BaseDAO<User> {
Collection<User> findByEmail(#Param("email") String email);
#Query("select new com.data.CustomUser(upper(substring(u.lastName, 1, 1)) as initial, count(*)) from User u join u.chats c where c.business=:business group by upper(substring(u.lastName, 1, 1)) order by initial")
List<CustomUser> getContactsIndex(#Param("email") String email);
}
which is exposed with Spring Data REST. The User object is a managed entity, while CustomUser not and as you can see, it's build on-fly by using custom query.
Once I want to call that function, it fails with Persistent entity must not be a null! exception. Is there any way to implement this behavior?
P.S. Expose CustomUser with separate repository is impossible because it is not a managed entity.
One challenge with using Spring Data Rest is when you hit an edge case and you don't know whether you've hit a bug or whether you're just outside the scope of what the library is intended for. In this case I think you are at the edge of what SDR will easily do for you, and it's time to implement your own controller.
Spring Data Rest is looking for an Entity - in your case a User - as the return type for ALL methods in the repository to expose under /entities/search, and breaks when it doesn't find that entity type. The User it wants to serialize isn't there, hence the "Persistent entity must not be null".
The way around this is to write a simple #Controller that has a #RequestMapping for the exact same url exposed by the repository method. This will override the SDR generated implementation for that url, and from that you can return whatever you want.
Your implementation might look something like this:
#Controller
public class CustomUserController {
private final UserRepository repository;
#Inject
public CustomUserController(UserRepository repo) {
repository = repo;
}
#RequestMapping(value = "/users/search/getContactsIndex", method = GET, produces = {MediaType.APPLICATION_JSON_VALUE})
public #ResponseBody List<CustomUser> getContactsIndex(#RequestParam String email) {
return repository.getContactsIndex(email);
}
}
Be aware that there is a "recommended" way to override functionality this way. There is an open issue to document the best way to do this.

Resources